1use std::{
2 borrow::Cow,
3 sync::{Arc, Mutex},
4};
5
6use futures::future::BoxFuture;
7use virtual_fs::{AsyncWriteExt, NullFile, VirtualFile};
8use wasmer_wasix_types::wasi::{Signal, Snapshot0Clockid};
9
10use crate::syscalls::platform_clock_time_get;
11
12use super::task::signal::SignalHandlerAbi;
13
14const TTY_MOBILE_PAUSE: u128 = std::time::Duration::from_millis(200).as_nanos();
15
16pub mod tty_sys;
17
18#[derive(Debug)]
19pub enum InputEvent {
20 Key,
21 Data(String),
22 Raw(Vec<u8>),
23}
24
25#[derive(Clone, Debug)]
26pub struct ConsoleRect {
27 pub cols: u32,
28 pub rows: u32,
29}
30
31impl Default for ConsoleRect {
32 fn default() -> Self {
33 Self { cols: 80, rows: 25 }
34 }
35}
36
37#[derive(Clone, Debug)]
38pub struct TtyOptionsInner {
39 echo: bool,
40 line_buffering: bool,
41 line_feeds: bool,
42 rect: ConsoleRect,
43}
44
45#[derive(Debug, Clone)]
46pub struct TtyOptions {
47 inner: Arc<Mutex<TtyOptionsInner>>,
48}
49
50impl Default for TtyOptions {
51 fn default() -> Self {
52 Self {
53 inner: Arc::new(Mutex::new(TtyOptionsInner {
54 echo: true,
55 line_buffering: true,
56 line_feeds: true,
57 rect: ConsoleRect { cols: 80, rows: 25 },
58 })),
59 }
60 }
61}
62
63impl TtyOptions {
64 pub fn cols(&self) -> u32 {
65 let inner = self.inner.lock().unwrap();
66 inner.rect.cols
67 }
68
69 pub fn set_cols(&self, cols: u32) {
70 let mut inner = self.inner.lock().unwrap();
71 inner.rect.cols = cols;
72 }
73
74 pub fn rows(&self) -> u32 {
75 let inner = self.inner.lock().unwrap();
76 inner.rect.rows
77 }
78
79 pub fn set_rows(&self, rows: u32) {
80 let mut inner = self.inner.lock().unwrap();
81 inner.rect.rows = rows;
82 }
83
84 pub fn echo(&self) -> bool {
85 let inner = self.inner.lock().unwrap();
86 inner.echo
87 }
88
89 pub fn set_echo(&self, echo: bool) {
90 let mut inner = self.inner.lock().unwrap();
91 inner.echo = echo;
92 }
93
94 pub fn line_buffering(&self) -> bool {
95 let inner = self.inner.lock().unwrap();
96 inner.line_buffering
97 }
98
99 pub fn set_line_buffering(&self, line_buffering: bool) {
100 let mut inner = self.inner.lock().unwrap();
101 inner.line_buffering = line_buffering;
102 }
103
104 pub fn line_feeds(&self) -> bool {
105 let inner = self.inner.lock().unwrap();
106 inner.line_feeds
107 }
108
109 pub fn set_line_feeds(&self, line_feeds: bool) {
110 let mut inner = self.inner.lock().unwrap();
111 inner.line_feeds = line_feeds;
112 }
113}
114
115#[derive(Debug)]
116pub struct Tty {
117 stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
118 stdout: Box<dyn VirtualFile + Send + Sync + 'static>,
119 signaler: Option<Box<dyn SignalHandlerAbi + Send + Sync + 'static>>,
120 is_mobile: bool,
121 last: Option<(String, u128)>,
122 options: TtyOptions,
123 line: String,
124}
125
126impl Tty {
127 pub fn new(
128 stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
129 stdout: Box<dyn VirtualFile + Send + Sync + 'static>,
130 is_mobile: bool,
131 options: TtyOptions,
132 ) -> Self {
133 Self {
134 stdin,
135 stdout,
136 signaler: None,
137 last: None,
138 options,
139 is_mobile,
140 line: String::new(),
141 }
142 }
143
144 pub fn stdin(&self) -> &(dyn VirtualFile + Send + Sync + 'static) {
145 self.stdin.as_ref()
146 }
147
148 pub fn stdin_mut(&mut self) -> &mut (dyn VirtualFile + Send + Sync + 'static) {
149 self.stdin.as_mut()
150 }
151
152 pub fn stdin_replace(
153 &mut self,
154 mut stdin: Box<dyn VirtualFile + Send + Sync + 'static>,
155 ) -> Box<dyn VirtualFile + Send + Sync + 'static> {
156 std::mem::swap(&mut self.stdin, &mut stdin);
157 stdin
158 }
159
160 pub fn stdin_take(&mut self) -> Box<dyn VirtualFile + Send + Sync + 'static> {
161 let mut stdin: Box<dyn VirtualFile + Send + Sync + 'static> = Box::<NullFile>::default();
162 std::mem::swap(&mut self.stdin, &mut stdin);
163 stdin
164 }
165
166 pub fn options(&self) -> TtyOptions {
167 self.options.clone()
168 }
169
170 pub fn set_signaler(&mut self, signaler: Box<dyn SignalHandlerAbi + Send + Sync + 'static>) {
171 self.signaler.replace(signaler);
172 }
173
174 pub fn on_event(mut self, event: InputEvent) -> BoxFuture<'static, Self> {
175 Box::pin(async move {
176 match event {
177 InputEvent::Key => {
178 self
180 }
181 InputEvent::Data(data) => {
182 if self.is_mobile {
185 let now = platform_clock_time_get(Snapshot0Clockid::Monotonic, 1_000_000)
186 .unwrap() as u128;
187 if let Some((what, when)) = self.last.as_ref()
188 && what.as_str() == data
189 && now - *when < TTY_MOBILE_PAUSE
190 {
191 self.last = None;
192 return self;
193 }
194 self.last = Some((data.clone(), now))
195 }
196
197 self.on_data(data.as_bytes().to_vec().into()).await
198 }
199 InputEvent::Raw(data) => self.on_data(data.into()).await,
200 }
201 })
202 }
203
204 fn on_enter(mut self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
205 Box::pin(async move {
206 let mut data = self.line.clone();
208 self.line.clear();
209 data.push('\n');
210
211 {
213 let echo = {
214 let options = self.options.inner.lock().unwrap();
215 options.echo
216 };
217 if echo {
218 let _ = self.stdout.write("\n".as_bytes()).await;
219 }
220 }
221
222 let _ = self.stdin.write(data.as_bytes()).await;
224 self
225 })
226 }
227
228 fn on_ctrl_c(mut self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
229 Box::pin(async move {
230 if let Some(signaler) = self.signaler.as_ref() {
231 signaler.signal(Signal::Sigint as u8).ok();
232
233 let (echo, _line_buffering) = {
234 let options = self.options.inner.lock().unwrap();
235 (options.echo, options.line_buffering)
236 };
237
238 self.line.clear();
239 if echo {
240 let _ = self.stdout.write("\n".as_bytes()).await;
241 }
242 }
243 self
244 })
245 }
246
247 fn on_backspace(mut self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
248 if self.line.is_empty() {
250 return Box::pin(async move { self });
251 }
252 let len = self.line.len();
253 self.line = self.line[..len - 1].to_string();
254
255 Box::pin(async move {
256 {
258 let echo = {
259 let options = self.options.inner.lock().unwrap();
260 options.echo
261 };
262 if echo {
263 let _ = self.stdout.write("\u{0008} \u{0008}".as_bytes()).await;
264 }
265 }
266 self
267 })
268 }
269
270 fn on_tab(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
271 Box::pin(async move { self })
272 }
273
274 fn on_cursor_left(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
275 Box::pin(async move { self })
276 }
277
278 fn on_cursor_right(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
279 Box::pin(async move { self })
280 }
281
282 fn on_cursor_up(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
283 Box::pin(async move { self })
284 }
285
286 fn on_cursor_down(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
287 Box::pin(async move { self })
288 }
289
290 fn on_home(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
291 Box::pin(async move { self })
292 }
293
294 fn on_end(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
295 Box::pin(async move { self })
296 }
297
298 fn on_ctrl_l(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
299 Box::pin(async move { self })
300 }
301
302 fn on_page_up(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
303 Box::pin(async move { self })
304 }
305
306 fn on_page_down(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
307 Box::pin(async move { self })
308 }
309
310 fn on_f1(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
311 Box::pin(async move { self })
312 }
313
314 fn on_f2(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
315 Box::pin(async move { self })
316 }
317
318 fn on_f3(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
319 Box::pin(async move { self })
320 }
321
322 fn on_f4(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
323 Box::pin(async move { self })
324 }
325
326 fn on_f5(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
327 Box::pin(async move { self })
328 }
329
330 fn on_f6(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
331 Box::pin(async move { self })
332 }
333
334 fn on_f7(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
335 Box::pin(async move { self })
336 }
337
338 fn on_f8(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
339 Box::pin(async move { self })
340 }
341
342 fn on_f9(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
343 Box::pin(async move { self })
344 }
345
346 fn on_f10(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
347 Box::pin(async move { self })
348 }
349
350 fn on_f11(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
351 Box::pin(async move { self })
352 }
353
354 fn on_f12(self, _data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
355 Box::pin(async move { self })
356 }
357
358 fn on_data(mut self, data: Cow<'static, [u8]>) -> BoxFuture<'static, Self> {
359 let options = { self.options.inner.lock().unwrap().clone() };
361 if options.line_buffering {
362 let echo = options.echo;
363 return match String::from_utf8_lossy(data.as_ref()).as_ref() {
364 "\r" | "\u{000A}" => self.on_enter(data),
365 "\u{0003}" => self.on_ctrl_c(data),
366 "\u{007F}" => self.on_backspace(data),
367 "\u{0009}" => self.on_tab(data),
368 "\u{001B}\u{005B}\u{0044}" => self.on_cursor_left(data),
369 "\u{001B}\u{005B}\u{0043}" => self.on_cursor_right(data),
370 "\u{0001}" | "\u{001B}\u{005B}\u{0048}" => self.on_home(data),
371 "\u{001B}\u{005B}\u{0046}" => self.on_end(data),
372 "\u{001B}\u{005B}\u{0041}" => self.on_cursor_up(data),
373 "\u{001B}\u{005B}\u{0042}" => self.on_cursor_down(data),
374 "\u{000C}" => self.on_ctrl_l(data),
375 "\u{001B}\u{005B}\u{0035}\u{007E}" => self.on_page_up(data),
376 "\u{001B}\u{005B}\u{0036}\u{007E}" => self.on_page_down(data),
377 "\u{001B}\u{004F}\u{0050}" => self.on_f1(data),
378 "\u{001B}\u{004F}\u{0051}" => self.on_f2(data),
379 "\u{001B}\u{004F}\u{0052}" => self.on_f3(data),
380 "\u{001B}\u{004F}\u{0053}" => self.on_f4(data),
381 "\u{001B}\u{005B}\u{0031}\u{0035}\u{007E}" => self.on_f5(data),
382 "\u{001B}\u{005B}\u{0031}\u{0037}\u{007E}" => self.on_f6(data),
383 "\u{001B}\u{005B}\u{0031}\u{0038}\u{007E}" => self.on_f7(data),
384 "\u{001B}\u{005B}\u{0031}\u{0039}\u{007E}" => self.on_f8(data),
385 "\u{001B}\u{005B}\u{0032}\u{0030}\u{007E}" => self.on_f9(data),
386 "\u{001B}\u{005B}\u{0032}\u{0031}\u{007E}" => self.on_f10(data),
387 "\u{001B}\u{005B}\u{0032}\u{0033}\u{007E}" => self.on_f11(data),
388 "\u{001B}\u{005B}\u{0032}\u{0034}\u{007E}" => self.on_f12(data),
389 _ => Box::pin(async move {
390 if echo {
391 let _ = self.stdout.write(data.as_ref()).await;
392 }
393 self.line
394 .push_str(String::from_utf8_lossy(data.as_ref()).as_ref());
395 self
396 }),
397 };
398 };
399
400 Box::pin(async move {
401 if options.echo {
403 let _ = self.stdout.write(data.as_ref()).await;
405 }
406
407 let _ = self.stdin.write(data.as_ref()).await;
409 self
410 })
411 }
412}
413
414#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
415pub struct WasiTtyState {
416 pub cols: u32,
417 pub rows: u32,
418 pub width: u32,
419 pub height: u32,
420 pub stdin_tty: bool,
421 pub stdout_tty: bool,
422 pub stderr_tty: bool,
423 pub echo: bool,
424 pub line_buffered: bool,
425 pub line_feeds: bool,
426}
427
428impl Default for WasiTtyState {
429 fn default() -> Self {
430 Self {
431 rows: 80,
432 cols: 25,
433 width: 800,
434 height: 600,
435 stdin_tty: true,
436 stdout_tty: true,
437 stderr_tty: true,
438 echo: false,
439 line_buffered: false,
440 line_feeds: true,
441 }
442 }
443}
444
445pub trait TtyBridge: std::fmt::Debug {
447 fn reset(&self);
449
450 fn tty_get(&self) -> WasiTtyState;
452
453 fn tty_set(&self, _tty_state: WasiTtyState);
455}