Skip to main content

running_process_platform_internal/platform/
terminal.rs

1//! Terminal, PTY, console, input, and terminal-I/O primitives.
2
3use std::ffi::OsString;
4use std::io::{self, Read, Write};
5use std::path::Path;
6use std::sync::{Arc, Mutex};
7
8/// Caller-facing PTY dimensions.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct PtySize {
11    pub rows: u16,
12    pub cols: u16,
13    pub pixel_width: u16,
14    pub pixel_height: u16,
15}
16
17/// One chunk read from the host terminal input source.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct PtyInputChunk {
20    pub data: Vec<u8>,
21    pub submit: bool,
22}
23
24/// Independently lockable writer used by the PTY session policy layer.
25pub type SharedPtyWriter = Arc<Mutex<Box<dyn Write + Send>>>;
26
27type PtyInterruptOperation = Box<dyn FnOnce(&SharedPtyWriter) -> io::Result<bool> + Send + 'static>;
28
29/// Prepared, owned interrupt operation that can outlive a borrow of the PTY master.
30///
31/// Native descriptors and process-group identifiers remain captured inside the
32/// selected concrete implementation rather than crossing this facade.
33pub struct PtyInterruptTarget(PtyInterruptOperation);
34
35impl PtyInterruptTarget {
36    pub(crate) fn new(
37        send: impl FnOnce(&SharedPtyWriter) -> io::Result<bool> + Send + 'static,
38    ) -> Self {
39        Self(Box::new(send))
40    }
41
42    pub fn send(self, writer: &SharedPtyWriter) -> io::Result<bool> {
43        (self.0)(writer)
44    }
45}
46
47/// Platform-neutral handle for the master side of a pseudo-terminal.
48pub trait PtyMaster: Send + 'static {
49    fn try_clone_reader(&mut self) -> io::Result<Box<dyn Read + Send>>;
50    fn take_writer(&mut self) -> io::Result<Box<dyn Write + Send>>;
51    fn resize(&self, size: PtySize) -> io::Result<()>;
52    fn get_size(&self) -> io::Result<PtySize>;
53
54    /// Legacy Unix process-group accessor retained for source compatibility.
55    ///
56    /// Platform mechanics do not consume this value; use facade operations for
57    /// control. This method will be removed in the next major release.
58    #[deprecated(note = "use facade PTY control operations; removal planned for 5.0")]
59    fn process_group_leader(&self) -> Option<i32> {
60        None
61    }
62
63    /// Legacy Unix descriptor accessor retained for source compatibility.
64    ///
65    /// The primitive representation avoids a host-native type in the neutral
66    /// signature, and shared callers must not use it for platform mechanics.
67    #[deprecated(note = "use facade PTY operations; removal planned for 5.0")]
68    fn as_raw_fd(&self) -> Option<i32> {
69        None
70    }
71
72    /// Prepare an owned interrupt operation using the selected host's PTY mechanics.
73    #[cfg(feature = "pty")]
74    fn interrupt_target(&self) -> io::Result<PtyInterruptTarget> {
75        Ok(PtyInterruptTarget::new(|writer| {
76            let mut writer = writer
77                .lock()
78                .map_err(|_| io::Error::other("pty writer mutex poisoned"))?;
79            writer.write_all(&[0x03])?;
80            writer.flush()?;
81            Ok(true)
82        }))
83    }
84
85    /// Kill the selected host's PTY process group, when one exists.
86    #[cfg(feature = "pty")]
87    fn kill_process_group(&self) -> io::Result<()> {
88        Ok(())
89    }
90
91    /// Select the externally meaningful PID for this PTY.
92    #[cfg(feature = "pty")]
93    fn preferred_pid(&self, child: &dyn PtyChild) -> Option<u32> {
94        Some(child.pid())
95    }
96}
97
98/// Platform-neutral handle for a child process running inside a PTY.
99pub trait PtyChild: Send + 'static {
100    fn pid(&self) -> u32;
101    fn try_wait(&mut self) -> io::Result<Option<u32>>;
102    fn wait(&mut self) -> io::Result<u32>;
103    fn kill(&mut self) -> io::Result<()>;
104
105    /// Legacy Windows process-handle accessor retained for source compatibility.
106    ///
107    /// Platform mechanics do not consume this value. The facade-owned process
108    /// preparation operation keeps native handles inside the concrete tree.
109    #[deprecated(note = "use facade PTY operations; removal planned for 5.0")]
110    fn as_raw_handle(&self) -> Option<*mut std::ffi::c_void> {
111        None
112    }
113
114    /// Apply selected-host containment and priority mechanics after spawn.
115    #[cfg(feature = "pty")]
116    fn prepare_process(
117        &self,
118        context: PtySpawnContext,
119        nice: Option<i32>,
120    ) -> io::Result<PtyProcessGuard> {
121        crate::prepare_unmanaged_pty_child(context, nice)
122    }
123}
124
125/// Platform-neutral handle for the slave side of a pseudo-terminal.
126pub trait PtySlave: Send + 'static {
127    type Child: PtyChild;
128
129    fn spawn(
130        self,
131        argv: &[OsString],
132        cwd: Option<&Path>,
133        env: Option<&[(OsString, OsString)]>,
134    ) -> io::Result<Self::Child>;
135}
136
137/// Factory trait implemented by the selected host PTY backend.
138pub trait PtyBackend {
139    type Master: PtyMaster;
140    type Slave: PtySlave;
141
142    fn openpty(size: PtySize) -> io::Result<(Self::Master, Self::Slave)>;
143}
144
145/// Raw replies returned by a bounded active terminal-graphics probe.
146#[derive(Debug, Clone, Default, PartialEq, Eq)]
147pub struct TerminalGraphicsProbe {
148    pub sixel_xtsmgraphics: Option<String>,
149    pub sixel_da1: Option<String>,
150    pub kitty_graphics: Option<String>,
151    pub iterm2_capabilities: Option<String>,
152}
153
154/// Probe the controlling terminal without exposing terminal descriptors.
155pub fn active_graphics_probe(timeout: std::time::Duration) -> TerminalGraphicsProbe {
156    crate::active_graphics_probe(timeout)
157}
158
159pub mod input {
160    pub use crate::terminal_input::*;
161}
162
163#[cfg(feature = "pty")]
164pub use crate::{
165    Backend, ChildProcessInfo, ConPtyBackendKind, OrphanConhostInfo, PtyProcessGuard,
166    PtySpawnContext, TerminalInputSession,
167};
168
169#[cfg(feature = "pty")]
170pub use crate::current_backend_kind;
171
172#[cfg(feature = "pty")]
173pub fn before_pty_spawn() -> PtySpawnContext {
174    crate::before_pty_spawn()
175}
176
177#[cfg(feature = "pty")]
178pub fn prepare_pty_child(
179    context: PtySpawnContext,
180    child: &dyn PtyChild,
181    nice: Option<i32>,
182) -> io::Result<PtyProcessGuard> {
183    child.prepare_process(context, nice)
184}
185
186#[cfg(feature = "pty")]
187pub fn input_payload(data: &[u8]) -> Vec<u8> {
188    crate::input_payload(data)
189}
190
191#[cfg(feature = "pty")]
192pub fn query_responses(data: &[u8]) -> Vec<Vec<u8>> {
193    crate::query_responses(data)
194}
195
196#[cfg(feature = "pty")]
197pub fn shell_argv(command: &str) -> Vec<String> {
198    crate::shell_argv(command)
199}
200
201#[cfg(feature = "pty")]
202pub fn wait_before_close_supported() -> bool {
203    crate::wait_before_pty_close_supported()
204}
205
206#[cfg(feature = "pty")]
207pub fn is_ignorable_process_control_error(error: &io::Error) -> bool {
208    crate::is_ignorable_process_control_error(error)
209}
210
211#[cfg(feature = "pty")]
212pub fn send_pty_interrupt(
213    target: PtyInterruptTarget,
214    writer: &SharedPtyWriter,
215) -> io::Result<bool> {
216    target.send(writer)
217}
218
219#[cfg(feature = "pty")]
220pub fn kill_pty_process_group(master: &dyn PtyMaster) -> io::Result<()> {
221    master.kill_process_group()
222}
223
224#[cfg(feature = "pty")]
225pub fn terminate_pty_child(pid: u32) -> io::Result<bool> {
226    crate::terminate_pty_child(pid)
227}
228
229#[cfg(feature = "pty")]
230pub fn signal_pty_tree(pid: u32, force: bool) -> io::Result<bool> {
231    crate::signal_pty_tree(pid, force)
232}
233
234#[cfg(feature = "pty")]
235pub fn resize_pty(master: &dyn PtyMaster, size: PtySize) -> io::Result<()> {
236    crate::resize_pty(master, size)
237}
238
239#[cfg(feature = "pty")]
240pub fn preferred_pty_pid(master: &dyn PtyMaster, child: &dyn PtyChild) -> Option<u32> {
241    master.preferred_pid(child)
242}
243
244#[cfg(feature = "pty")]
245pub fn find_child_processes(parent_pid: u32) -> Vec<ChildProcessInfo> {
246    crate::find_child_processes(parent_pid)
247}
248
249#[cfg(feature = "pty")]
250pub fn find_orphan_conhosts() -> Vec<OrphanConhostInfo> {
251    crate::find_orphan_conhosts()
252}