aboutsummaryrefslogtreecommitdiff
path: root/vendor/portable-pty/examples
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-23 21:43:23 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-23 21:43:23 +0300
commita66f4c64e9d1591f042a94d6f3bef3b4298a4d1c (patch)
tree747de08b6ada4300afb4124da9feff0cc66b96a7 /vendor/portable-pty/examples
parent5981ba426e8ed8f6785195a03cc0d8a190173e61 (diff)
fix: force system conpty on windows
refs #761
Diffstat (limited to 'vendor/portable-pty/examples')
-rw-r--r--vendor/portable-pty/examples/bash.rs85
-rw-r--r--vendor/portable-pty/examples/narrow.rs92
-rw-r--r--vendor/portable-pty/examples/whoami.rs94
-rw-r--r--vendor/portable-pty/examples/whoami_async.rs73
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(())
+ })
+}