1use std::collections::HashMap;
7use std::ffi::OsString;
8use std::path::PathBuf;
9use std::time::Duration;
10
11#[cfg(unix)]
13use libc;
14
15#[derive(Debug, Clone)]
29pub struct PtyConfig {
30 pub working_directory: Option<PathBuf>,
32
33 pub env: Option<HashMap<OsString, OsString>>,
36
37 pub env_add: HashMap<OsString, OsString>,
39
40 pub env_remove: Vec<OsString>,
42
43 pub window_size: (u16, u16),
45
46 pub new_session: bool,
48
49 pub spawn_timeout: Option<Duration>,
51
52 #[cfg(unix)]
54 pub controlling_terminal: bool,
55
56 #[cfg(windows)]
58 pub allocate_console: bool,
59}
60
61impl Default for PtyConfig {
62 fn default() -> Self {
63 Self {
64 working_directory: None,
65 env: None,
66 env_add: HashMap::new(),
67 env_remove: Vec::new(),
68 window_size: (80, 24),
69 new_session: true,
70 spawn_timeout: None,
71 #[cfg(unix)]
72 controlling_terminal: true,
73 #[cfg(windows)]
74 allocate_console: true,
75 }
76 }
77}
78
79impl PtyConfig {
80 #[must_use]
82 pub fn builder() -> PtyConfigBuilder {
83 PtyConfigBuilder::new()
84 }
85
86 #[must_use]
88 pub fn new() -> Self {
89 Self::default()
90 }
91
92 #[must_use]
97 pub fn effective_env(&self) -> HashMap<OsString, OsString> {
98 let mut env = self
99 .env
100 .clone()
101 .unwrap_or_else(|| std::env::vars_os().collect());
102
103 env.extend(self.env_add.clone());
105
106 for key in &self.env_remove {
108 env.remove(key);
109 }
110
111 env
112 }
113}
114
115#[derive(Debug, Clone, Default)]
117pub struct PtyConfigBuilder {
118 config: PtyConfig,
119}
120
121impl PtyConfigBuilder {
122 #[must_use]
124 pub fn new() -> Self {
125 Self::default()
126 }
127
128 #[must_use]
130 pub fn working_directory(mut self, path: impl Into<PathBuf>) -> Self {
131 self.config.working_directory = Some(path.into());
132 self
133 }
134
135 #[must_use]
139 pub fn env_clear(mut self) -> Self {
140 self.config.env = Some(HashMap::new());
141 self
142 }
143
144 #[must_use]
146 pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
147 self.config.env_add.insert(key.into(), value.into());
148 self
149 }
150
151 #[must_use]
153 pub fn env_remove(mut self, key: impl Into<OsString>) -> Self {
154 self.config.env_remove.push(key.into());
155 self
156 }
157
158 #[must_use]
160 pub const fn window_size(mut self, cols: u16, rows: u16) -> Self {
161 self.config.window_size = (cols, rows);
162 self
163 }
164
165 #[must_use]
167 pub const fn new_session(mut self, value: bool) -> Self {
168 self.config.new_session = value;
169 self
170 }
171
172 #[must_use]
174 pub const fn spawn_timeout(mut self, timeout: Duration) -> Self {
175 self.config.spawn_timeout = Some(timeout);
176 self
177 }
178
179 #[cfg(unix)]
181 #[must_use]
182 pub const fn controlling_terminal(mut self, value: bool) -> Self {
183 self.config.controlling_terminal = value;
184 self
185 }
186
187 #[cfg(windows)]
189 #[must_use]
190 pub fn allocate_console(mut self, value: bool) -> Self {
191 self.config.allocate_console = value;
192 self
193 }
194
195 #[must_use]
197 pub fn build(self) -> PtyConfig {
198 self.config
199 }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
207#[non_exhaustive]
208pub enum PtySignal {
209 Interrupt,
213
214 Quit,
218
219 Terminate,
223
224 Kill,
228
229 Hangup,
233
234 WindowChange,
238
239 #[cfg(unix)]
243 Stop,
244
245 #[cfg(unix)]
249 Continue,
250
251 #[cfg(unix)]
255 User1,
256
257 #[cfg(unix)]
261 User2,
262}
263
264impl PtySignal {
265 #[cfg(unix)]
267 #[must_use]
268 pub const fn as_unix_signal(self) -> Option<i32> {
269 match self {
270 Self::Interrupt => Some(libc::SIGINT),
271 Self::Quit => Some(libc::SIGQUIT),
272 Self::Terminate => Some(libc::SIGTERM),
273 Self::Kill => Some(libc::SIGKILL),
274 Self::Hangup => Some(libc::SIGHUP),
275 Self::WindowChange => Some(libc::SIGWINCH),
276 Self::Stop => Some(libc::SIGTSTP),
277 Self::Continue => Some(libc::SIGCONT),
278 Self::User1 => Some(libc::SIGUSR1),
279 Self::User2 => Some(libc::SIGUSR2),
280 }
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub struct WindowSize {
287 pub cols: u16,
289 pub rows: u16,
291 pub xpixel: u16,
293 pub ypixel: u16,
295}
296
297impl WindowSize {
298 #[must_use]
300 pub const fn new(cols: u16, rows: u16) -> Self {
301 Self {
302 cols,
303 rows,
304 xpixel: 0,
305 ypixel: 0,
306 }
307 }
308
309 #[must_use]
311 pub const fn with_pixels(cols: u16, rows: u16, xpixel: u16, ypixel: u16) -> Self {
312 Self {
313 cols,
314 rows,
315 xpixel,
316 ypixel,
317 }
318 }
319}
320
321impl Default for WindowSize {
322 fn default() -> Self {
323 Self::new(80, 24)
324 }
325}
326
327impl From<(u16, u16)> for WindowSize {
328 fn from((cols, rows): (u16, u16)) -> Self {
329 Self::new(cols, rows)
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn config_builder() {
339 let config = PtyConfig::builder()
340 .working_directory("/tmp")
341 .env("FOO", "bar")
342 .window_size(120, 40)
343 .build();
344
345 assert_eq!(config.working_directory, Some(PathBuf::from("/tmp")));
346 assert_eq!(config.window_size, (120, 40));
347 assert!(config.env_add.contains_key(&OsString::from("FOO")));
348 }
349
350 #[test]
351 fn window_size_default() {
352 let size = WindowSize::default();
353 assert_eq!(size.cols, 80);
354 assert_eq!(size.rows, 24);
355 }
356}