use anyhow::Result;
use crossterm::terminal;
use tokio::io::AsyncReadExt;
use tokio::select;
use tokio::sync::mpsc;
use crate::commands::ssh::common::{parse_server_error, reset_terminal, SessionTermination};
use crate::controllers::terminal::TerminalClient;
pub async fn setup_signal_handlers() -> Result<(
tokio::signal::unix::Signal,
tokio::signal::unix::Signal,
tokio::signal::unix::Signal,
)> {
let sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())?;
let sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
let sigwinch = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::window_change())?;
Ok((sigint, sigterm, sigwinch))
}
enum UiMessage {
ServerDone(SessionTermination),
ReadyForInput(bool),
ShellReady(bool),
}
enum ServerMessage {
SendData(String),
SendSignal(u8),
WindowResize(u16, u16),
}
pub async fn run_interactive_session(mut client: TerminalClient) -> Result<SessionTermination> {
let mut stdin = tokio::io::stdin();
let mut stdin_buf = [0u8; 1024];
let mut termination = None;
let (ui_tx, mut ui_rx) = mpsc::channel::<UiMessage>(100);
let (server_tx, mut server_rx) = mpsc::channel::<ServerMessage>(100);
let mut shell_ready = client.is_ready();
let mut ready_for_input = client.is_ready_for_input();
tokio::spawn(async move {
loop {
select! {
Some(msg) = server_rx.recv() => {
match msg {
ServerMessage::SendData(data) => {
if let Err(e) = client.send_data(&data).await {
eprintln!("Error sending data: {}", e);
let _ = ui_tx.send(UiMessage::ServerDone(
SessionTermination::SendError(e.to_string())
)).await;
break;
}
},
ServerMessage::SendSignal(signal) => {
if let Err(e) = client.send_signal(signal).await {
eprintln!("Error sending signal: {}", e);
}
},
ServerMessage::WindowResize(cols, rows) => {
if let Err(e) = client.send_window_size(cols, rows).await {
eprintln!("Error resizing window: {}", e);
}
}
}
}
result = client.handle_server_messages() => {
match result {
Ok(()) => {
let _ = ui_tx.send(UiMessage::ServerDone(
SessionTermination::Complete
)).await;
},
Err(e) => {
let _ = ui_tx.send(UiMessage::ServerDone(
parse_server_error(e.to_string())
)).await;
}
}
break;
}
}
if shell_ready != client.is_ready() {
shell_ready = client.is_ready();
let _ = ui_tx.send(UiMessage::ShellReady(shell_ready)).await;
}
if ready_for_input != client.is_ready_for_input() {
ready_for_input = client.is_ready_for_input();
let _ = ui_tx.send(UiMessage::ReadyForInput(ready_for_input)).await;
}
}
});
let (mut sigint, mut sigterm, mut sigwinch) = setup_signal_handlers().await?;
loop {
select! {
_ = sigwinch.recv() => {
if let Ok((cols, rows)) = terminal::size() {
if shell_ready {
let _ = server_tx.send(ServerMessage::WindowResize(cols, rows)).await;
}
}
continue;
}
_ = sigint.recv() => {
if shell_ready {
let _ = server_tx.send(ServerMessage::SendSignal(2)).await; }
continue;
}
_ = sigterm.recv() => {
if shell_ready {
let _ = server_tx.send(ServerMessage::SendSignal(15)).await; }
break;
}
result = stdin.read(&mut stdin_buf) => {
if ready_for_input {
match result {
Ok(0) => break, Ok(n) => {
let data = String::from_utf8_lossy(&stdin_buf[..n]).to_string();
let _ = server_tx.send(ServerMessage::SendData(data)).await;
}
Err(e) => {
eprintln!("Error reading from stdin: {}", e);
termination = Some(SessionTermination::StdinError(e.to_string()));
break;
}
}
}
}
Some(msg) = ui_rx.recv() => {
match msg {
UiMessage::ServerDone(term) => {
termination = Some(term);
break;
},
UiMessage::ShellReady(ready) => {
shell_ready = ready;
},
UiMessage::ReadyForInput(input_ready) => {
ready_for_input = input_ready;
}
}
}
}
}
reset_terminal(false)?;
Ok(termination.unwrap_or(SessionTermination::Complete))
}