1use std::io::{Read, Write};
4
5use portable_pty::{native_pty_system, Child, CommandBuilder, MasterPty, PtySize};
6
7use crate::shell::Launch;
8
9pub struct Pty {
10 master: Option<Box<dyn MasterPty + Send>>,
11 writer: Option<Box<dyn Write + Send>>,
12 child: Box<dyn Child + Send + Sync>,
13}
14
15pub struct SpawnOptions {
16 pub cols: u16,
17 pub rows: u16,
18 pub cwd: Option<String>,
19 pub env: Vec<(String, String)>,
20}
21
22impl Pty {
23 pub fn spawn(
26 target: &str,
27 args: &[String],
28 opts: &SpawnOptions,
29 ) -> anyhow::Result<(Pty, Box<dyn Read + Send>)> {
30 let pty_system = native_pty_system();
31 let pair = pty_system.openpty(PtySize {
32 rows: opts.rows,
33 cols: opts.cols,
34 pixel_width: 0,
35 pixel_height: 0,
36 })?;
37
38 let mut cmd = CommandBuilder::new(target);
39 for arg in args {
40 cmd.arg(arg);
41 }
42 for (k, v) in std::env::vars() {
43 cmd.env(k, v);
44 }
45 cmd.env("TERM", "xterm-256color");
46 for (k, v) in &opts.env {
47 cmd.env(k, v);
48 }
49 if let Some(cwd) = &opts.cwd {
50 cmd.cwd(cwd);
51 } else if let Ok(cwd) = std::env::current_dir() {
52 cmd.cwd(cwd);
53 }
54
55 let child = pair.slave.spawn_command(cmd)?;
56 drop(pair.slave);
57
58 let reader = pair.master.try_clone_reader()?;
59 let writer = pair.master.take_writer()?;
60
61 Ok((
62 Pty {
63 master: Some(pair.master),
64 writer: Some(writer),
65 child,
66 },
67 reader,
68 ))
69 }
70
71 pub fn spawn_launch(
73 launch: &Launch,
74 cols: u16,
75 rows: u16,
76 cwd: Option<String>,
77 ) -> anyhow::Result<(Pty, Box<dyn Read + Send>)> {
78 let opts = SpawnOptions {
79 cols,
80 rows,
81 cwd,
82 env: launch.env.clone(),
83 };
84 Pty::spawn(&launch.target, &launch.args, &opts)
85 }
86
87 pub fn write(&mut self, data: &[u8]) -> std::io::Result<()> {
88 let writer = self
89 .writer
90 .as_mut()
91 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::BrokenPipe, "PTY is closed"))?;
92 writer.write_all(data)?;
93 writer.flush()
94 }
95
96 pub fn resize(&mut self, cols: u16, rows: u16) -> anyhow::Result<()> {
97 let master = self
98 .master
99 .as_ref()
100 .ok_or_else(|| anyhow::anyhow!("PTY is closed"))?;
101 master.resize(PtySize {
102 rows,
103 cols,
104 pixel_width: 0,
105 pixel_height: 0,
106 })?;
107 Ok(())
108 }
109
110 pub fn pid(&self) -> Option<u32> {
111 self.child.process_id()
112 }
113
114 pub fn kill(&mut self) {
115 let _ = self.child.kill();
116 }
117
118 pub fn close(&mut self) {
119 self.kill();
120 self.writer.take();
121 self.master.take();
122 }
123
124 pub fn signal(&mut self, name: &str) -> anyhow::Result<()> {
127 let upper = name.trim_start_matches("SIG").to_uppercase();
128 match upper.as_str() {
129 "INT" => self.write(b"\x03")?,
130 "TERM" | "KILL" | "QUIT" => self.kill(),
131 other => anyhow::bail!("unsupported signal: {other}"),
132 }
133 Ok(())
134 }
135
136 pub fn try_wait(&mut self) -> std::io::Result<Option<i32>> {
138 self.child
139 .try_wait()
140 .map(|status| status.map(|status| status.exit_code() as i32))
141 }
142}