//! The console "tty": keyboard input, and the `File` behind file //! descriptors 0, 1 and 2. //! //! Two input modes, like a Linux terminal: //! //! * **cooked** (canonical, the default): typed characters are echoed and //! collected in a line buffer (Backspace edits it); only when Enter is //! pressed does the whole line become readable from fd 0. So `read(0)` //! gets a complete, already-edited line. Programs like `cat` use this. //! * **raw**: every key goes to the reader immediately and nothing is echoed; //! arrow keys arrive as ANSI escape sequences (`ESC [ A` = up). The shell //! switches to raw while it edits its command line, so it can do its own //! echo, Backspace and up-arrow history. (Linux: `tcsetattr` / `ioctl`.) //! //! Output (`Console::write`) understands two control characters besides //! '\n': '\x08' erases the character before the cursor, and '\x0c' (form //! feed) clears the screen. use crate::errno::Errno; use crate::fs::file::{File, KIND_CONSOLE, Stat}; use crate::thread::WaitQueue; use crate::{print, serial_print, vga_buffer}; use alloc::string::String; use conquer_once::spin::OnceCell; use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use crossbeam_queue::ArrayQueue; use pc_keyboard::KeyCode; use spin::Mutex; /// The line being typed in cooked mode (not yet readable). static LINE: Mutex = Mutex::new(String::new()); /// Bytes waiting for `read(0, ...)`: finished lines (cooked) or keys (raw). static INPUT: OnceCell> = OnceCell::uninit(); /// Raw mode on? static RAW: AtomicBool = AtomicBool::new(false); /// pid of the process that switched raw mode on (0 = none). If it exits /// without switching back, the console goes back to cooked mode. static RAW_OWNER: AtomicU64 = AtomicU64::new(0); /// Threads blocked in `Console::read`, waiting for input. static INPUT_WAIT: WaitQueue = WaitQueue::new(); fn input() -> &'static ArrayQueue { INPUT.get_or_init(|| ArrayQueue::new(4096)) } /// Switch between raw (`true`) and cooked mode for process `pid`. Returns /// the old mode. pub fn set_raw(raw: bool, pid: u64) -> bool { LINE.lock().clear(); // a half-typed cooked line doesn't survive a switch RAW_OWNER.store(if raw { pid } else { 0 }, Ordering::SeqCst); RAW.swap(raw, Ordering::SeqCst) } /// A process ended: if it left the console in raw mode, undo that (so the /// next program, or a respawned shell, gets normal line input). pub fn process_exited(pid: u64) { if RAW_OWNER.compare_exchange(pid, 0, Ordering::SeqCst, Ordering::SeqCst).is_ok() { RAW.store(false, Ordering::SeqCst); } } pub fn is_raw() -> bool { RAW.load(Ordering::SeqCst) } /// Make `bytes` readable and wake readers. (Queue full: drop -- nobody reads.) fn deliver(bytes: &[u8]) { for &b in bytes { let _ = input().push(b); } INPUT_WAIT.wake_all(); } /// Called by the keyboard task for every typed character. pub fn input_char(c: char) { if is_raw() { let mut utf8 = [0; 4]; deliver(c.encode_utf8(&mut utf8).as_bytes()); return; } let mut line = LINE.lock(); match c { '\u{8}' => { // Backspace: only erase what's in the current line. if line.pop().is_some() { vga_buffer::backspace(); serial_print!("\x08"); // uart_16550 turns this into "\x08 \x08" } } '\n' => { print!("\n"); serial_print!("\n"); line.push('\n'); deliver(line.as_bytes()); line.clear(); } c => { print!("{}", c); serial_print!("{}", c); line.push(c); } } } /// Called by the keyboard task for keys that aren't characters (arrows...). pub fn input_key(key: KeyCode) { if is_raw() { // The ANSI/VT100 sequences a Linux terminal would send. let seq: &[u8] = match key { KeyCode::ArrowUp => b"\x1b[A", KeyCode::ArrowDown => b"\x1b[B", KeyCode::ArrowRight => b"\x1b[C", KeyCode::ArrowLeft => b"\x1b[D", KeyCode::Home => b"\x1b[H", KeyCode::End => b"\x1b[F", KeyCode::Delete => b"\x1b[3~", _ => return, }; deliver(seq); } else { print!("<{:?}>", key); serial_print!("<{:?}>", key); } } /// The console as a file: reading gets keyboard input, writing prints to the /// screen and serial port. pub struct Console; impl File for Console { /// Blocks until input is available. Cooked: returns up to `buf.len()` /// bytes of a line, stopping after its '\n'. Raw: whatever keys are there. fn read(&self, buf: &mut [u8]) -> Result { if buf.is_empty() { return Ok(0); } // Block until input arrives: the thread is parked on INPUT_WAIT // (using no CPU) and `deliver` wakes it. The closure runs with // interrupts off; the ArrayQueue is lock-free, so that's fine. Ok(INPUT_WAIT.wait_until(|| { let mut n = 0; while n < buf.len() { let Some(b) = input().pop() else { break }; buf[n] = b; n += 1; if b == b'\n' { break; } } if n > 0 { Some(n) } else { None } })) } fn write(&self, buf: &[u8]) -> Result { // Print valid UTF-8 as is, anything else as '?'. Control characters // are handled one by one. for chunk in buf.utf8_chunks() { for piece in chunk.valid().split_inclusive(['\x08', '\x0c']) { let (text, control) = match piece.strip_suffix(['\x08', '\x0c']) { Some(text) => (text, piece.chars().last()), None => (piece, None), }; print!("{}", text); serial_print!("{}", text); match control { Some('\x08') => { vga_buffer::backspace(); serial_print!("\x08"); } Some(_) => vga_buffer::clear_screen(), // serial: nothing to clear None => {} } } if !chunk.invalid().is_empty() { print!("?"); serial_print!("?"); } } Ok(buf.len()) } fn stat(&self) -> Result { Ok(Stat { kind: KIND_CONSOLE, size: 0 }) } }