diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-23 21:43:23 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-23 21:43:23 +0300 |
| commit | a66f4c64e9d1591f042a94d6f3bef3b4298a4d1c (patch) | |
| tree | 747de08b6ada4300afb4124da9feff0cc66b96a7 /vendor/portable-pty/examples | |
| parent | 5981ba426e8ed8f6785195a03cc0d8a190173e61 (diff) | |
fix: force system conpty on windows
refs #761
Diffstat (limited to 'vendor/portable-pty/examples')
| -rw-r--r-- | vendor/portable-pty/examples/bash.rs | 85 | ||||
| -rw-r--r-- | vendor/portable-pty/examples/narrow.rs | 92 | ||||
| -rw-r--r-- | vendor/portable-pty/examples/whoami.rs | 94 | ||||
| -rw-r--r-- | vendor/portable-pty/examples/whoami_async.rs | 73 |
4 files changed, 344 insertions, 0 deletions
diff --git a/vendor/portable-pty/examples/bash.rs b/vendor/portable-pty/examples/bash.rs new file mode 100644 index 00000000..2066b7ec --- /dev/null +++ b/vendor/portable-pty/examples/bash.rs @@ -0,0 +1,85 @@ +//! This example demonstrates how to spawn a Bash shell using the `portable_pty` crate. +//! based on pty/examples/whoami.rs + +use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; +use std::io::{Read, Write}; +use std::sync::mpsc::channel; +use std::thread; + +fn main() { + let pty_system = NativePtySystem::default(); + + // Open the PTY with specified size. + let pair = pty_system + .openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }) + .unwrap(); + + // Set up the command to launch Bash. + let cmd = CommandBuilder::new("bash"); + let mut child = pair.slave.spawn_command(cmd).unwrap(); + + drop(pair.slave); + + // Set up channels for reading and writing. + let (tx, rx) = channel::<String>(); + let mut reader = pair.master.try_clone_reader().unwrap(); + let master_writer = pair.master.take_writer().unwrap(); + + // Thread to read from the PTY and send data to the main thread. + thread::spawn(move || { + let mut buffer = [0u8; 1024]; + loop { + match reader.read(&mut buffer) { + Ok(0) => break, // EOF + Ok(n) => { + let output = String::from_utf8_lossy(&buffer[..n]); + println!("{}", output); // Print to stdout for visibility. + } + Err(e) => { + eprintln!("Error reading from PTY: {}", e); + break; + } + } + } + }); + + // Thread to write input into the PTY. + let tx_writer = thread::spawn(move || { + handle_input_stream(rx, master_writer); + }); + + println!("You can now type commands for Bash (type 'exit' to quit):"); + + // Main thread sends user input to the writer thread. + loop { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + + if input.trim() == "exit" { + break; + } + + tx.send(input).unwrap(); + } + + drop(tx); + tx_writer.join().unwrap(); + + println!("Waiting for Bash to exit..."); + let status = child.wait().unwrap(); + println!("Bash exited with status: {:?}", status); +} + +fn handle_input_stream(rx: std::sync::mpsc::Receiver<String>, mut writer: Box<dyn Write + Send>) { + for input in rx.iter() { + if writer.write_all(input.as_bytes()).is_err() { + eprintln!("Error writing to PTY"); + break; + } + } +} diff --git a/vendor/portable-pty/examples/narrow.rs b/vendor/portable-pty/examples/narrow.rs new file mode 100644 index 00000000..5793b9b7 --- /dev/null +++ b/vendor/portable-pty/examples/narrow.rs @@ -0,0 +1,92 @@ +//! Runs a command with a fixed terminal size. +//! This is used by wezterm's doc building automation to keep +//! the --help output within a reasonable width +use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; +use std::sync::mpsc::channel; + +fn main() { + let pty_system = NativePtySystem::default(); + + let pair = pty_system + .openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }) + .unwrap(); + + let mut args = std::env::args_os().skip(1); + + let mut cmd = CommandBuilder::new(args.next().unwrap()); + cmd.args(args); + + let mut child = pair.slave.spawn_command(cmd).unwrap(); + + // Release any handles owned by the slave: we don't need it now + // that we've spawned the child. + drop(pair.slave); + + // Read the output in another thread. + // This is important because it is easy to encounter a situation + // where read/write buffers fill and block either your process + // or the spawned process. + let (tx, rx) = channel(); + let mut reader = pair.master.try_clone_reader().unwrap(); + std::thread::spawn(move || { + // Consume the output from the child + let mut s = String::new(); + reader.read_to_string(&mut s).unwrap(); + tx.send(s).unwrap(); + }); + + { + // Obtain the writer. + // When the writer is dropped, EOF will be sent to + // the program that was spawned. + // It is important to take the writer even if you don't + // send anything to its stdin so that EOF can be + // generated, otherwise you risk deadlocking yourself. + let mut writer = pair.master.take_writer().unwrap(); + + if cfg!(target_os = "macos") { + // macOS quirk: the child and reader must be started and + // allowed a brief grace period to run before we allow + // the writer to drop. Otherwise, the data we send to + // the kernel to trigger EOF is interleaved with the + // data read by the reader! WTF!? + // This appears to be a race condition for very short + // lived processes on macOS. + // I'd love to find a more deterministic solution to + // this than sleeping. + std::thread::sleep(std::time::Duration::from_millis(20)); + } + + // This example doesn't need to write anything, but if you + // want to send data to the child, you'd set `to_write` to + // that data and do it like this: + let to_write = ""; + if !to_write.is_empty() { + // To avoid deadlock, wrt. reading and waiting, we send + // data to the stdin of the child in a different thread. + std::thread::spawn(move || { + writer.write_all(to_write.as_bytes()).unwrap(); + }); + } + } + + // Wait for the child to complete + eprintln!("child status: {:?}", child.wait().unwrap()); + + // Take care to drop the master after our processes are + // done, as some platforms get unhappy if it is dropped + // sooner than that. + drop(pair.master); + + // Now wait for the output to be read by our reader thread + let output = rx.recv().unwrap(); + + let output = output.replace("\r\n", "\n"); + + print!("{output}"); +} diff --git a/vendor/portable-pty/examples/whoami.rs b/vendor/portable-pty/examples/whoami.rs new file mode 100644 index 00000000..f7e9cca0 --- /dev/null +++ b/vendor/portable-pty/examples/whoami.rs @@ -0,0 +1,94 @@ +//! This is a conceptually simple example that spawns the `whoami` program +//! to print your username. It is made more complex because there are multiple +//! pipes involved and it is easy to get blocked/deadlocked if care and attention +//! is not paid to those pipes! +use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; +use std::sync::mpsc::channel; + +fn main() { + let pty_system = NativePtySystem::default(); + + let pair = pty_system + .openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + }) + .unwrap(); + + let cmd = CommandBuilder::new("whoami"); + let mut child = pair.slave.spawn_command(cmd).unwrap(); + + // Release any handles owned by the slave: we don't need it now + // that we've spawned the child. + drop(pair.slave); + + // Read the output in another thread. + // This is important because it is easy to encounter a situation + // where read/write buffers fill and block either your process + // or the spawned process. + let (tx, rx) = channel(); + let mut reader = pair.master.try_clone_reader().unwrap(); + std::thread::spawn(move || { + // Consume the output from the child + let mut s = String::new(); + reader.read_to_string(&mut s).unwrap(); + tx.send(s).unwrap(); + }); + + { + // Obtain the writer. + // When the writer is dropped, EOF will be sent to + // the program that was spawned. + // It is important to take the writer even if you don't + // send anything to its stdin so that EOF can be + // generated, otherwise you risk deadlocking yourself. + let mut writer = pair.master.take_writer().unwrap(); + + if cfg!(target_os = "macos") { + // macOS quirk: the child and reader must be started and + // allowed a brief grace period to run before we allow + // the writer to drop. Otherwise, the data we send to + // the kernel to trigger EOF is interleaved with the + // data read by the reader! WTF!? + // This appears to be a race condition for very short + // lived processes on macOS. + // I'd love to find a more deterministic solution to + // this than sleeping. + std::thread::sleep(std::time::Duration::from_millis(20)); + } + + // This example doesn't need to write anything, but if you + // want to send data to the child, you'd set `to_write` to + // that data and do it like this: + let to_write = ""; + if !to_write.is_empty() { + // To avoid deadlock, wrt. reading and waiting, we send + // data to the stdin of the child in a different thread. + std::thread::spawn(move || { + writer.write_all(to_write.as_bytes()).unwrap(); + }); + } + } + + // Wait for the child to complete + println!("child status: {:?}", child.wait().unwrap()); + + // Take care to drop the master after our processes are + // done, as some platforms get unhappy if it is dropped + // sooner than that. + drop(pair.master); + + // Now wait for the output to be read by our reader thread + let output = rx.recv().unwrap(); + + // We print with escapes escaped because the windows conpty + // implementation synthesizes title change escape sequences + // in the output stream and it can be confusing to see those + // printed out raw in another terminal. + print!("output: "); + for c in output.escape_debug() { + print!("{}", c); + } +} diff --git a/vendor/portable-pty/examples/whoami_async.rs b/vendor/portable-pty/examples/whoami_async.rs new file mode 100644 index 00000000..e32b6a9c --- /dev/null +++ b/vendor/portable-pty/examples/whoami_async.rs @@ -0,0 +1,73 @@ +use anyhow::anyhow; +use futures::prelude::*; +use portable_pty::{native_pty_system, CommandBuilder, PtySize}; + +// This example shows how to use the `smol` crate to use portable_pty +// in an asynchronous application. + +fn main() -> anyhow::Result<()> { + smol::block_on(async { + let pty_system = native_pty_system(); + + let pair = pty_system.openpty(PtySize { + rows: 24, + cols: 80, + pixel_width: 0, + pixel_height: 0, + })?; + + let cmd = CommandBuilder::new("whoami"); + + // Move the slave to another thread to block and spawn a + // command. + // Note that this implicitly drops slave and closes out + // file handles which is important to avoid deadlock + // when waiting for the child process! + let slave = pair.slave; + let mut child = smol::unblock(move || slave.spawn_command(cmd)).await?; + + { + // Obtain the writer. + // When the writer is dropped, EOF will be sent to + // the program that was spawned. + // It is important to take the writer even if you don't + // send anything to its stdin so that EOF can be + // generated, otherwise you risk deadlocking yourself. + let writer = pair.master.take_writer()?; + + // Explicitly generate EOF + drop(writer); + } + + println!( + "child status: {:?}", + smol::unblock(move || child + .wait() + .map_err(|e| anyhow!("waiting for child: {}", e))) + .await? + ); + + let reader = pair.master.try_clone_reader()?; + + // Take care to drop the master after our processes are + // done, as some platforms get unhappy if it is dropped + // sooner than that. + drop(pair.master); + + let mut lines = smol::io::BufReader::new(smol::Unblock::new(reader)).lines(); + while let Some(line) = lines.next().await { + let line = line.map_err(|e| anyhow!("problem reading line: {}", e))?; + // We print with escapes escaped because the windows conpty + // implementation synthesizes title change escape sequences + // in the output stream and it can be confusing to see those + // printed out raw in another terminal. + print!("output: len={} ", line.len()); + for c in line.escape_debug() { + print!("{}", c); + } + println!(); + } + + Ok(()) + }) +} |
