mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-02 10:45:57 +00:00
Fix Ctrl+C signal handling and cross-server session compatibility
- Added proper PTY slave terminal configuration for signal interpretation - Enabled ISIG flag so Ctrl+C generates SIGINT instead of being treated as text - Configured ICANON and ECHO flags for proper terminal behavior - Applied configuration in both child process code paths (manual dup2 and login_tty) - Implemented hybrid proxy fallback system for cross-server session input - Rust server now proxies input to Node.js server when pipe write fails - Added reqwest HTTP client for seamless communication between servers - Reduced pipe timeout to 1 second for faster fallback detection - Added key translation for special keys when proxying to Node.js This fixes both: 1. Ctrl+C not interrupting processes in Rust-created sessions 2. "Device not configured" errors when accessing Node.js sessions from Rust server 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6391605267
commit
0b97181a04
4 changed files with 1260 additions and 9 deletions
1174
tty-fwd/Cargo.lock
generated
1174
tty-fwd/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -35,6 +35,7 @@ ctrlc = "3.4.7"
|
|||
data-encoding = "2.9"
|
||||
glob = "0.3"
|
||||
notify = "8.0"
|
||||
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.60", features = ["Win32_System_Console"] }
|
||||
|
|
|
|||
|
|
@ -164,10 +164,15 @@ pub fn send_key_to_session(
|
|||
_ => return Err(anyhow!("Unknown key: {}", key)),
|
||||
};
|
||||
|
||||
// Use a timeout-protected write operation that also checks for readers
|
||||
write_to_pipe_with_timeout(&stdin_path, key_bytes, Duration::from_secs(5))?;
|
||||
|
||||
Ok(())
|
||||
// Try to write to the pipe directly first
|
||||
match write_to_pipe_with_timeout(&stdin_path, key_bytes, Duration::from_secs(1)) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(pipe_error) => {
|
||||
// If pipe write fails, try to proxy to Node.js server
|
||||
eprintln!("Direct pipe write failed: {}, trying Node.js proxy for key", pipe_error);
|
||||
proxy_key_to_nodejs_server(session_id, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_text_to_session(
|
||||
|
|
@ -182,10 +187,56 @@ pub fn send_text_to_session(
|
|||
return Err(anyhow!("Session {} not found or not running", session_id));
|
||||
}
|
||||
|
||||
// Use a timeout-protected write operation that also checks for readers
|
||||
write_to_pipe_with_timeout(&stdin_path, text.as_bytes(), Duration::from_secs(5))?;
|
||||
// Try to write to the pipe directly first
|
||||
match write_to_pipe_with_timeout(&stdin_path, text.as_bytes(), Duration::from_secs(1)) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(pipe_error) => {
|
||||
// If pipe write fails, try to proxy to Node.js server
|
||||
eprintln!("Direct pipe write failed: {}, trying Node.js proxy", pipe_error);
|
||||
proxy_input_to_nodejs_server(session_id, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn proxy_input_to_nodejs_server(session_id: &str, text: &str) -> Result<(), anyhow::Error> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Create HTTP client
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
// Create request body
|
||||
let mut body = HashMap::new();
|
||||
body.insert("text", text);
|
||||
|
||||
// Send request to Node.js server
|
||||
let url = format!("http://localhost:3000/api/sessions/{}/input", session_id);
|
||||
let response = client
|
||||
.post(&url)
|
||||
.json(&body)
|
||||
.send()
|
||||
.map_err(|e| anyhow!("Failed to proxy to Node.js server: {}", e))?;
|
||||
|
||||
if response.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("Node.js server returned error: {}", response.status()))
|
||||
}
|
||||
}
|
||||
|
||||
fn proxy_key_to_nodejs_server(session_id: &str, key: &str) -> Result<(), anyhow::Error> {
|
||||
// Convert key to equivalent text sequence for Node.js server
|
||||
let text = match key {
|
||||
"arrow_up" => "\x1b[A",
|
||||
"arrow_down" => "\x1b[B",
|
||||
"arrow_right" => "\x1b[C",
|
||||
"arrow_left" => "\x1b[D",
|
||||
"escape" => "\x1b",
|
||||
"enter" | "ctrl_enter" => "\r",
|
||||
"shift_enter" => "\x1b\x0d",
|
||||
_ => return Err(anyhow!("Unknown key for proxy: {}", key)),
|
||||
};
|
||||
|
||||
proxy_input_to_nodejs_server(session_id, text)
|
||||
}
|
||||
|
||||
fn write_to_pipe_with_timeout(
|
||||
|
|
|
|||
|
|
@ -711,6 +711,18 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Error> {
|
|||
dup2(&slave_owned_fd, &mut stdout_fd).expect("Failed to dup2 stdout");
|
||||
dup2(&slave_owned_fd, &mut stderr_fd).expect("Failed to dup2 stderr");
|
||||
|
||||
// Configure the PTY slave for proper signal handling
|
||||
if let Ok(mut attrs) = tcgetattr(&slave_owned_fd) {
|
||||
// Enable signal interpretation (ISIG) so Ctrl+C generates SIGINT
|
||||
attrs.local_flags.insert(LocalFlags::ISIG);
|
||||
// Enable canonical mode for line editing but keep other flags
|
||||
attrs.local_flags.insert(LocalFlags::ICANON);
|
||||
// Keep echo enabled for interactive sessions
|
||||
attrs.local_flags.insert(LocalFlags::ECHO);
|
||||
// Apply the terminal attributes
|
||||
tcsetattr(&slave_owned_fd, SetArg::TCSANOW, &attrs).ok();
|
||||
}
|
||||
|
||||
// Forget the OwnedFd instances to prevent them from being closed
|
||||
std::mem::forget(stdin_fd);
|
||||
std::mem::forget(stdout_fd);
|
||||
|
|
@ -723,7 +735,24 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Error> {
|
|||
}
|
||||
} else {
|
||||
unsafe {
|
||||
let _slave_fd = pty.slave.as_raw_fd();
|
||||
login_tty_compat(pty.slave.into_raw_fd())?;
|
||||
|
||||
// Configure the PTY slave for proper signal handling after login_tty
|
||||
use std::os::fd::{FromRawFd, OwnedFd};
|
||||
let stdin_fd = OwnedFd::from_raw_fd(0); // stdin is now the slave
|
||||
if let Ok(mut attrs) = tcgetattr(&stdin_fd) {
|
||||
// Enable signal interpretation (ISIG) so Ctrl+C generates SIGINT
|
||||
attrs.local_flags.insert(LocalFlags::ISIG);
|
||||
// Enable canonical mode for line editing
|
||||
attrs.local_flags.insert(LocalFlags::ICANON);
|
||||
// Keep echo enabled for interactive sessions
|
||||
attrs.local_flags.insert(LocalFlags::ECHO);
|
||||
// Apply the terminal attributes
|
||||
tcsetattr(&stdin_fd, SetArg::TCSANOW, &attrs).ok();
|
||||
}
|
||||
std::mem::forget(stdin_fd); // Don't close stdin
|
||||
|
||||
// No stderr redirection since script_mode is always false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue