1use super::TtyBridge;
2use crate::WasiTtyState;
3
4#[derive(Debug, Default, Clone)]
6pub struct SysTty;
7
8impl TtyBridge for SysTty {
9 fn reset(&self) {
10 sys::reset().ok();
11 }
12
13 fn tty_get(&self) -> WasiTtyState {
14 let echo = sys::is_mode_echo();
15 let line_buffered = sys::is_mode_line_buffering();
16 let line_feeds = sys::is_mode_line_feeds();
17 let stdin_tty = sys::is_stdin_tty();
18 let stdout_tty = sys::is_stdout_tty();
19 let stderr_tty = sys::is_stderr_tty();
20 let (cols, rows) = sys_terminal_size::get_terminal_size();
21
22 WasiTtyState {
23 cols,
24 rows,
25 width: 800,
26 height: 600,
27 stdin_tty,
28 stdout_tty,
29 stderr_tty,
30 echo,
31 line_buffered,
32 line_feeds,
33 }
34 }
35
36 fn tty_set(&self, tty_state: WasiTtyState) {
37 if tty_state.echo {
38 sys::set_mode_echo().ok();
39 } else {
40 sys::set_mode_no_echo().ok();
41 }
42 if tty_state.line_buffered {
43 sys::set_mode_line_buffered().ok();
44 } else {
45 sys::set_mode_no_line_buffered().ok();
46 }
47 if tty_state.line_feeds {
48 sys::set_mode_line_feeds().ok();
49 } else {
50 sys::set_mode_no_line_feeds().ok();
51 }
52 }
53}
54
55mod sys_terminal_size {
56 static DEFAULT_SIZE: (u32, u32) = (80, 25);
57
58 #[cfg(not(target_arch = "wasm32"))]
59 pub fn get_terminal_size() -> (u32, u32) {
60 if let Some((terminal_size::Width(width), terminal_size::Height(height))) =
61 terminal_size::terminal_size()
62 {
63 (width.into(), height.into())
64 } else {
65 DEFAULT_SIZE
66 }
67 }
68
69 #[cfg(target_arch = "wasm32")]
70 pub fn get_terminal_size() -> (u32, u32) {
71 DEFAULT_SIZE
72 }
73}
74
75#[allow(unused_mut, unused_imports)]
76#[cfg(all(unix, not(target_os = "ios")))]
77mod sys {
78 use {
79 libc::{
80 ECHO, ECHOCTL, ECHOE, ECHOK, ECHONL, ICANON, ICRNL, IEXTEN, IGNCR, INLCR, ISIG, IXON,
81 ONLCR, OPOST, TCSANOW, c_int, tcsetattr, termios,
82 },
83 std::mem,
84 std::os::unix::io::AsRawFd,
85 };
86
87 fn io_result(ret: libc::c_int) -> std::io::Result<()> {
88 match ret {
89 0 => Ok(()),
90 _ => Err(std::io::Error::last_os_error()),
91 }
92 }
93
94 pub fn reset() -> Result<(), anyhow::Error> {
95 let mut termios = mem::MaybeUninit::<termios>::uninit();
96 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
97 let mut termios = unsafe { termios.assume_init() };
98
99 termios.c_lflag |= ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL;
100 set_line_buffering(&mut termios, true);
101
102 unsafe { tcsetattr(0, TCSANOW, &termios) };
103 Ok(())
104 }
105
106 pub fn is_stdin_tty() -> bool {
107 ::termios::Termios::from_fd(0).is_ok()
108 }
109
110 pub fn is_stdout_tty() -> bool {
111 ::termios::Termios::from_fd(1).is_ok()
112 }
113
114 pub fn is_stderr_tty() -> bool {
115 ::termios::Termios::from_fd(2).is_ok()
116 }
117
118 pub fn is_mode_echo() -> bool {
119 if let Ok(termios) = ::termios::Termios::from_fd(0) {
120 (termios.c_lflag & ::termios::ECHO) != 0
121 } else {
122 false
123 }
124 }
125
126 pub fn is_mode_line_buffering() -> bool {
127 if let Ok(termios) = ::termios::Termios::from_fd(0) {
128 (termios.c_lflag & ::termios::ICANON) != 0
129 } else {
130 false
131 }
132 }
133
134 pub fn is_mode_line_feeds() -> bool {
135 if let Ok(termios) = ::termios::Termios::from_fd(0) {
136 (termios.c_lflag & ::termios::ONLCR) != 0
137 } else {
138 false
139 }
140 }
141
142 pub fn set_mode_no_echo() -> Result<(), anyhow::Error> {
143 let mut termios = mem::MaybeUninit::<termios>::uninit();
144 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
145 let mut termios = unsafe { termios.assume_init() };
146
147 termios.c_lflag &= !ECHO;
148 termios.c_lflag &= !ECHOE;
149 termios.c_lflag &= !ECHOK;
150 termios.c_lflag &= !ECHOCTL;
151 termios.c_lflag &= !IEXTEN;
152 unsafe { tcsetattr(0, TCSANOW, &termios) };
160 Ok(())
161 }
162
163 pub fn set_mode_echo() -> Result<(), anyhow::Error> {
164 let mut termios = mem::MaybeUninit::<termios>::uninit();
165 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
166 let mut termios = unsafe { termios.assume_init() };
167
168 termios.c_lflag |= ECHO;
169 termios.c_lflag |= ECHOE;
170 termios.c_lflag |= ECHOK;
171 termios.c_lflag |= ECHOCTL;
172 termios.c_lflag |= IEXTEN;
173 unsafe { tcsetattr(0, TCSANOW, &termios) };
181 Ok(())
182 }
183
184 pub fn set_mode_no_line_buffered() -> Result<(), anyhow::Error> {
185 let mut termios = mem::MaybeUninit::<termios>::uninit();
186 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
187 let mut termios = unsafe { termios.assume_init() };
188
189 set_line_buffering(&mut termios, false);
190
191 unsafe { tcsetattr(0, TCSANOW, &termios) };
192 Ok(())
193 }
194
195 pub fn set_mode_line_buffered() -> Result<(), anyhow::Error> {
196 let mut termios = mem::MaybeUninit::<termios>::uninit();
197 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
198 let mut termios = unsafe { termios.assume_init() };
199
200 set_line_buffering(&mut termios, true);
201
202 unsafe { tcsetattr(0, TCSANOW, &termios) };
203 Ok(())
204 }
205
206 fn set_line_buffering(termios: &mut termios, enabled: bool) {
207 if enabled {
208 termios.c_lflag |= ICANON;
209 termios.c_iflag |= ICRNL;
210 termios.c_iflag &= !(INLCR | IGNCR);
211 } else {
212 termios.c_lflag &= !ICANON;
213 termios.c_iflag &= !(ICRNL | INLCR | IGNCR);
216 }
217 }
218
219 pub fn set_mode_no_line_feeds() -> Result<(), anyhow::Error> {
220 let mut termios = mem::MaybeUninit::<termios>::uninit();
221 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
222 let mut termios = unsafe { termios.assume_init() };
223
224 termios.c_lflag &= !ONLCR;
225
226 unsafe { tcsetattr(0, TCSANOW, &termios) };
227 Ok(())
228 }
229
230 pub fn set_mode_line_feeds() -> Result<(), anyhow::Error> {
231 let mut termios = mem::MaybeUninit::<termios>::uninit();
232 io_result(unsafe { ::libc::tcgetattr(0, termios.as_mut_ptr()) })?;
233 let mut termios = unsafe { termios.assume_init() };
234
235 termios.c_lflag |= ONLCR;
236
237 unsafe { tcsetattr(0, TCSANOW, &termios) };
238 Ok(())
239 }
240
241 #[cfg(test)]
242 mod tests {
243 use super::*;
244
245 fn blank_termios() -> termios {
246 unsafe { mem::zeroed() }
249 }
250
251 #[test]
252 fn noncanonical_input_preserves_carriage_returns_and_line_feeds() {
253 let mut state = blank_termios();
254 state.c_lflag = ICANON | ECHO | ISIG;
255 state.c_iflag = ICRNL | INLCR | IGNCR | IXON;
256 state.c_oflag = OPOST;
257
258 set_line_buffering(&mut state, false);
259
260 assert_eq!(state.c_lflag & ICANON, 0);
261 assert_eq!(state.c_iflag & (ICRNL | INLCR | IGNCR), 0);
262 assert_ne!(state.c_lflag & ECHO, 0);
263 assert_ne!(state.c_lflag & ISIG, 0);
264 assert_ne!(state.c_iflag & IXON, 0);
265 assert_ne!(state.c_oflag & OPOST, 0);
266 }
267
268 #[test]
269 fn cooked_input_translates_carriage_returns_to_newlines() {
270 let mut state = blank_termios();
271 state.c_iflag = INLCR | IGNCR | IXON;
272
273 set_line_buffering(&mut state, true);
274
275 assert_ne!(state.c_lflag & ICANON, 0);
276 assert_ne!(state.c_iflag & ICRNL, 0);
277 assert_eq!(state.c_iflag & (INLCR | IGNCR), 0);
278 assert_ne!(state.c_iflag & IXON, 0);
279 }
280
281 fn read_exact_with_timeout(fd: c_int, output: &mut [u8]) {
282 let mut offset = 0;
283 while offset < output.len() {
284 let mut descriptor = libc::pollfd {
285 fd,
286 events: libc::POLLIN,
287 revents: 0,
288 };
289 assert_eq!(
290 unsafe { libc::poll(&mut descriptor, 1, 1_000) },
291 1,
292 "timed out waiting for PTY input"
293 );
294 assert_ne!(descriptor.revents & libc::POLLIN, 0);
295
296 let read = unsafe {
297 libc::read(
298 fd,
299 output[offset..].as_mut_ptr().cast(),
300 output.len() - offset,
301 )
302 };
303 assert!(read > 0, "failed to read PTY input");
304 offset += read as usize;
305 }
306 }
307
308 #[test]
309 fn noncanonical_pty_input_distinguishes_enter_from_line_feed() {
310 let mut master = -1;
311 let mut slave = -1;
312 assert_eq!(
313 unsafe {
314 libc::openpty(
315 &mut master,
316 &mut slave,
317 std::ptr::null_mut(),
318 std::ptr::null_mut(),
319 std::ptr::null_mut(),
320 )
321 },
322 0
323 );
324
325 struct Pty {
326 master: c_int,
327 slave: c_int,
328 }
329
330 impl Drop for Pty {
331 fn drop(&mut self) {
332 unsafe {
333 libc::close(self.master);
334 libc::close(self.slave);
335 }
336 }
337 }
338
339 let pty = Pty { master, slave };
340 let mut state = blank_termios();
341 assert_eq!(unsafe { libc::tcgetattr(pty.slave, &mut state) }, 0);
342 state.c_iflag |= ICRNL | INLCR | IGNCR;
343 set_line_buffering(&mut state, false);
344 assert_eq!(unsafe { libc::tcsetattr(pty.slave, TCSANOW, &state) }, 0);
345
346 let input = [b'\r', b'\n'];
347 assert_eq!(
348 unsafe { libc::write(pty.master, input.as_ptr().cast(), input.len()) },
349 input.len() as isize
350 );
351
352 let mut output = [0_u8; 2];
353 read_exact_with_timeout(pty.slave, &mut output);
354 assert_eq!(output, input);
355
356 assert_eq!(unsafe { libc::tcgetattr(pty.slave, &mut state) }, 0);
357 set_line_buffering(&mut state, true);
358 assert_eq!(unsafe { libc::tcsetattr(pty.slave, TCSANOW, &state) }, 0);
359 let input = [b'\r'];
360 assert_eq!(
361 unsafe { libc::write(pty.master, input.as_ptr().cast(), input.len()) },
362 1
363 );
364 let mut output = [0_u8];
365 read_exact_with_timeout(pty.slave, &mut output);
366 assert_eq!(output, [b'\n']);
367 }
368 }
369}
370
371#[cfg(any(not(unix), target_os = "ios"))]
372mod sys {
373 pub fn reset() -> Result<(), anyhow::Error> {
374 Ok(())
375 }
376
377 pub fn is_stdin_tty() -> bool {
378 false
379 }
380
381 pub fn is_stdout_tty() -> bool {
382 false
383 }
384
385 pub fn is_stderr_tty() -> bool {
386 false
387 }
388
389 pub fn is_mode_echo() -> bool {
390 true
391 }
392
393 pub fn is_mode_line_buffering() -> bool {
394 true
395 }
396
397 pub fn is_mode_line_feeds() -> bool {
398 true
399 }
400
401 pub fn set_mode_no_echo() -> Result<(), anyhow::Error> {
402 Ok(())
403 }
404
405 pub fn set_mode_echo() -> Result<(), anyhow::Error> {
406 Ok(())
407 }
408
409 pub fn set_mode_no_line_buffered() -> Result<(), anyhow::Error> {
410 Ok(())
411 }
412
413 pub fn set_mode_line_buffered() -> Result<(), anyhow::Error> {
414 Ok(())
415 }
416
417 pub fn set_mode_no_line_feeds() -> Result<(), anyhow::Error> {
418 Ok(())
419 }
420
421 pub fn set_mode_line_feeds() -> Result<(), anyhow::Error> {
422 Ok(())
423 }
424}