use std::io::{self, Read, Write};
use std::time::Duration;
use term_session_mock::{CHECK_PID_ALIVE, CHECK_PID_DEAD, process_is_alive};
use term_session_muxio_service_definitions::path_wire;
pub const OSC52_TEST_PAYLOAD: &[u8] = b"c;dGVzdA==";
#[cfg(windows)]
mod win_console {
use std::os::windows::io::AsRawHandle;
unsafe extern "system" {
fn GetConsoleMode(handle: *mut std::ffi::c_void, mode: *mut u32) -> i32;
fn SetConsoleMode(handle: *mut std::ffi::c_void, mode: u32) -> i32;
}
const ENABLE_LINE_INPUT: u32 = 0x0002;
const ENABLE_ECHO_INPUT: u32 = 0x0004;
const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x0200;
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
pub fn enable_raw_vt() {
unsafe {
let stdin_handle = std::io::stdin().as_raw_handle();
let mut mode = 0u32;
if GetConsoleMode(stdin_handle, &mut mode) != 0 {
mode &= !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
SetConsoleMode(stdin_handle, mode);
}
let stdout_handle = std::io::stdout().as_raw_handle();
if GetConsoleMode(stdout_handle, &mut mode) != 0 {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(stdout_handle, mode);
}
}
}
pub fn disable_stdout_vt_processing() {
unsafe {
let stdout_handle = std::io::stdout().as_raw_handle();
let mut mode = 0u32;
if GetConsoleMode(stdout_handle, &mut mode) != 0 {
mode &= !ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(stdout_handle, mode);
}
}
}
}
fn disable_stdin_echo() {
#[cfg(unix)]
unsafe {
let mut termios: libc::termios = std::mem::zeroed();
if libc::tcgetattr(libc::STDIN_FILENO, &mut termios) == 0 {
termios.c_lflag &= !(libc::ECHO | libc::ECHOE | libc::ECHOK | libc::ECHONL);
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios);
}
}
#[cfg(windows)]
unsafe {
use windows_sys::Win32::System::Console::{
ENABLE_ECHO_INPUT, GetConsoleMode, GetStdHandle, STD_INPUT_HANDLE, SetConsoleMode,
};
let handle = GetStdHandle(STD_INPUT_HANDLE);
let mut mode: u32 = 0;
if GetConsoleMode(handle, &mut mode) != 0 {
let _ = SetConsoleMode(handle, mode & !ENABLE_ECHO_INPUT);
}
}
}
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!(
"Usage: term_session_mock <echo|osc52|osc52_alive|sleep|exit|spawn_child|check_pid|pwd> [args]"
);
std::process::exit(1);
}
match args[1].as_str() {
"echo" => {
#[cfg(windows)]
win_console::enable_raw_vt();
disable_stdin_echo();
let mut buffer = [0u8; 4096];
let mut stdin = io::stdin();
let mut stdout = io::stdout();
loop {
match stdin.read(&mut buffer) {
Ok(0) => break,
Ok(n) => {
if stdout.write_all(&buffer[..n]).is_err() {
break;
}
if stdout.flush().is_err() {
break;
}
}
Err(_) => break,
}
}
}
"osc52" => {
#[cfg(windows)]
win_console::disable_stdout_vt_processing();
let mut stdout = io::stdout();
let _ = stdout.write_all(b"\x1b]52;");
let _ = stdout.write_all(OSC52_TEST_PAYLOAD);
let _ = stdout.write_all(b"\x07");
let _ = stdout.flush();
std::thread::sleep(Duration::from_millis(500));
}
"osc52_alive" => {
#[cfg(windows)]
win_console::enable_raw_vt();
#[cfg(windows)]
win_console::disable_stdout_vt_processing();
disable_stdin_echo();
let mut stdout = io::stdout();
let _ = stdout.write_all(b"\x1b]52;");
let _ = stdout.write_all(OSC52_TEST_PAYLOAD);
let _ = stdout.write_all(b"\x07");
let _ = stdout.flush();
let mut buffer = [0u8; 4096];
let mut stdin = io::stdin();
loop {
match stdin.read(&mut buffer) {
Ok(0) => break,
Ok(_) => {}
Err(_) => break,
}
}
}
"capture" => {
#[cfg(windows)]
win_console::enable_raw_vt();
let mut stdin = io::stdin();
let mut stdout = io::stdout();
let mut buf = [0u8; 1024];
loop {
match stdin.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
let _ = stdout.write_all(b"MOUSE_OK:");
let _ = stdout.write_all(&buf[..n]);
let _ = stdout.flush();
if buf[..n].windows(4).any(|w| w == b"ping") {
break;
}
}
Err(_) => break,
}
}
}
"sleep" => {
let ms: u64 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(1000);
std::thread::sleep(Duration::from_millis(ms));
}
"spawn_child" => {
#[cfg(windows)]
win_console::enable_raw_vt();
disable_stdin_echo();
let ms: u64 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(60000);
let exe = std::env::current_exe().expect("current exe");
let mut grandchild = std::process::Command::new(exe)
.arg("sleep")
.arg(ms.to_string())
.spawn()
.expect("spawn grandchild");
let pid = grandchild.id();
let mut stdout = io::stdout();
let _ = stdout.write_all(format!("GRANDCHILD_PID:{pid}\n").as_bytes());
let _ = stdout.flush();
let mut buffer = [0u8; 4096];
let mut stdin = io::stdin();
loop {
match stdin.read(&mut buffer) {
Ok(0) => break,
Ok(n) => {
if stdout.write_all(&buffer[..n]).is_err() {
break;
}
if stdout.flush().is_err() {
break;
}
}
Err(_) => break,
}
}
let _ = grandchild.kill();
let _ = grandchild.wait();
}
"check_pid" => {
let pid: u32 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
if process_is_alive(pid) {
std::process::exit(CHECK_PID_ALIVE);
}
std::process::exit(CHECK_PID_DEAD);
}
"exit" => {
let code: i32 = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
std::process::exit(code);
}
"pwd" => {
let file = std::env::args_os()
.nth(2)
.expect("pwd requires an absolute output file path");
let cwd = std::env::current_dir()
.map(|p| path_wire::encode_path(&p))
.unwrap_or_default();
if let Err(e) = std::fs::write(&file, &cwd) {
eprintln!("pwd: failed to write {:?}: {e}", file);
std::process::exit(1);
}
}
other => {
eprintln!("Unknown subcommand: {other}");
std::process::exit(1);
}
}
}