use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{mpsc, Arc};
use std::sync::atomic::AtomicBool;
use crate::types::{AppState, ForwardedPane, Node, LayoutKind};
use crate::tree;
pub fn handle_pane_forward_extract(
app: &mut AppState,
win_idx: usize,
pane_idx: usize,
resp: mpsc::Sender<String>,
) {
if win_idx >= app.windows.len() {
let _ = resp.send("ERR window out of range".to_string());
return;
}
let src_path = {
let mut leaves = Vec::new();
tree::collect_leaf_paths_pub(&app.windows[win_idx].root, &mut Vec::new(), &mut leaves);
if let Some((_, p)) = leaves.get(pane_idx) {
p.clone()
} else {
app.windows[win_idx].active_path.clone()
}
};
if let Some(saved) = app.windows[win_idx].zoom_saved.take() {
let win = &mut app.windows[win_idx];
for (p, sz) in saved.into_iter() {
if let Some(Node::Split { sizes, .. }) = tree::get_split_mut(&mut win.root, &p) {
*sizes = sz;
}
}
}
let src_root = std::mem::replace(
&mut app.windows[win_idx].root,
Node::Split { kind: LayoutKind::Horizontal, sizes: vec![], children: vec![] },
);
let (remaining, extracted) = tree::extract_node(src_root, &src_path);
let pane_node = match extracted {
Some(n) => n,
None => {
if let Some(rem) = remaining {
app.windows[win_idx].root = rem;
}
let _ = resp.send("ERR pane not found".to_string());
return;
}
};
let src_empty = remaining.is_none();
if let Some(rem) = remaining {
app.windows[win_idx].root = rem;
app.windows[win_idx].active_path = tree::first_leaf_path(&app.windows[win_idx].root);
}
if src_empty {
app.windows.remove(win_idx);
if app.active_idx >= app.windows.len() {
app.active_idx = app.windows.len().saturating_sub(1);
}
}
let pane = match pane_node {
Node::Leaf(p) => p,
_ => {
let _ = resp.send("ERR extracted non-leaf".to_string());
return;
}
};
let pid = pane.child_pid;
let title = pane.title.clone();
let rows = pane.last_rows;
let cols = pane.last_cols;
let screen_b64 = {
if let Ok(parser) = pane.term.lock() {
let buf = parser.screen().state_formatted();
use base64::Engine;
base64::engine::general_purpose::STANDARD.encode(&buf)
} else {
String::new()
}
};
let listener = match TcpListener::bind("127.0.0.1:0") {
Ok(l) => l,
Err(e) => {
let _ = resp.send(format!("ERR bind: {}", e));
return;
}
};
let listen_port = listener.local_addr().map(|a| a.port()).unwrap_or(0);
let shutdown = Arc::new(AtomicBool::new(false));
let fwd_id = app.next_forward_id;
app.next_forward_id += 1;
let pty_reader = match pane.master.try_clone_reader() {
Ok(r) => r,
Err(e) => {
let _ = resp.send(format!("ERR reader: {}", e));
return;
}
};
let pty_writer = pane.writer;
let sd_clone = shutdown.clone();
std::thread::spawn(move || {
if let Ok((stream, _)) = listener.accept() {
let _ = stream.set_nodelay(true);
let sd = sd_clone;
let mut tcp_writer = match stream.try_clone() {
Ok(s) => s,
Err(_) => return,
};
let mut pty_reader = pty_reader;
let sd2 = sd.clone();
std::thread::spawn(move || {
let mut buf = [0u8; 65536];
loop {
if sd2.load(std::sync::atomic::Ordering::Relaxed) { break; }
match pty_reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
if tcp_writer.write_all(&buf[..n]).is_err() { break; }
let _ = tcp_writer.flush();
}
Err(_) => break,
}
}
});
let mut tcp_reader = stream;
let mut pty_writer = pty_writer;
let mut buf = [0u8; 65536];
loop {
if sd.load(std::sync::atomic::Ordering::Relaxed) { break; }
match tcp_reader.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
if pty_writer.write_all(&buf[..n]).is_err() { break; }
let _ = pty_writer.flush();
}
Err(_) => break,
}
}
}
});
app.forwarded_panes.insert(fwd_id, ForwardedPane {
master: pane.master,
child: pane.child,
listener_port: listen_port,
pid,
title: title.clone(),
rows,
cols,
shutdown,
});
let title_wire = title.replace(' ', "\x01");
let response = format!(
"FORWARD {} {} {} {} {} {} {}",
fwd_id, listen_port, pid.unwrap_or(0), title_wire, rows, cols, screen_b64.len(),
);
if screen_b64.is_empty() {
let _ = resp.send(response);
} else {
let _ = resp.send(format!("{}\n{}", response, screen_b64));
}
}
pub fn handle_pane_forward_inject(
app: &mut AppState,
source_session: String,
source_addr: String,
source_key: String,
forward_id: u64,
fwd_port: u16,
pid: u32,
title: String,
rows: u16,
cols: u16,
screen_b64: String,
target_win: Option<usize>,
target_pane: Option<usize>,
horizontal: bool,
) {
let fwd_addr = format!("127.0.0.1:{}", fwd_port);
let stream = match TcpStream::connect(&fwd_addr) {
Ok(s) => s,
Err(e) => {
eprintln!("psmux: cross-session inject connect failed: {}", e);
return;
}
};
let _ = stream.set_nodelay(true);
let reader_stream = match stream.try_clone() {
Ok(s) => s,
Err(e) => {
eprintln!("psmux: cross-session inject clone failed: {}", e);
return;
}
};
let writer_stream = stream;
let screen_snapshot = if !screen_b64.is_empty() {
use base64::Engine;
base64::engine::general_purpose::STANDARD.decode(&screen_b64).ok()
} else {
None
};
let pane_id = {
let mut max_id = 0usize;
for win in &app.windows {
let mut leaves = Vec::new();
tree::collect_leaf_paths_pub(&win.root, &mut Vec::new(), &mut leaves);
for (id, _) in &leaves {
if *id > max_id { max_id = *id; }
}
}
max_id + 1
};
let proxy_pane = match crate::proxy_pane::create_proxy_pane(
reader_stream,
writer_stream,
source_addr.clone(),
source_key,
source_session,
forward_id,
if pid > 0 { Some(pid) } else { None },
title,
rows,
cols,
pane_id,
screen_snapshot,
) {
Ok(p) => p,
Err(e) => {
eprintln!("psmux: create_proxy_pane failed: {}", e);
return;
}
};
let reader = match proxy_pane.master.try_clone_reader() {
Ok(r) => r,
Err(e) => {
eprintln!("psmux: proxy reader clone failed: {}", e);
return;
}
};
crate::pane::spawn_reader_thread(
reader,
proxy_pane.term.clone(),
proxy_pane.data_version.clone(),
proxy_pane.cursor_shape.clone(),
proxy_pane.bell_pending.clone(),
proxy_pane.output_ring.clone(),
);
let tgt_idx = target_win.unwrap_or(app.active_idx);
if tgt_idx < app.windows.len() {
let tgt_path = if let Some(tp) = target_pane {
let mut leaves = Vec::new();
tree::collect_leaf_paths_pub(&app.windows[tgt_idx].root, &mut Vec::new(), &mut leaves);
if let Some((_, p)) = leaves.get(tp) {
p.clone()
} else {
app.windows[tgt_idx].active_path.clone()
}
} else {
app.windows[tgt_idx].active_path.clone()
};
let split_kind = if horizontal { LayoutKind::Horizontal } else { LayoutKind::Vertical };
tree::replace_leaf_with_split(
&mut app.windows[tgt_idx].root,
&tgt_path,
split_kind,
Node::Leaf(proxy_pane),
);
app.active_idx = tgt_idx;
}
}