//! Console device interfaces use core::time::Duration; use abi::{error::Error, io::TerminalSize, primitive_enum}; use alloc::{vec, vec::Vec}; use bitflags::bitflags; use libk::{task::runtime, vfs::TerminalOutput}; use libk_util::{sync::IrqSafeSpinlock, StaticVector}; use crate::debug::DebugSink; const CONSOLE_ROW_LEN: usize = 80; const MAX_CSI_ARGS: usize = 8; const DEFAULT_FG_COLOR: ColorAttribute = ColorAttribute::White; const DEFAULT_BG_COLOR: ColorAttribute = ColorAttribute::Blue; primitive_enum! { #[allow(missing_docs)] #[doc = "Color attribute of a console character"] pub enum ColorAttribute: u8 { Black = 0, Red = 1, Green = 2, Yellow = 3, Blue = 4, Magenta = 5, Cyan = 6, White = 7, } } bitflags! { #[doc = "Extra attributes of a console character"] #[derive(Clone, Copy)] pub struct Attributes: u8 { #[allow(missing_docs)] const BOLD = 1 << 0; } } impl ColorAttribute { fn from_vt100(val: u8) -> Self { match val { 0..=7 => Self::try_from(val).unwrap(), _ => ColorAttribute::Red, } } /// Converts the attribute to RGBA representation pub fn as_rgba(&self, bold: bool) -> u32 { let color = match self { Self::Black => 0x000000, Self::Red => 0x7F0000, Self::Green => 0x007F00, Self::Yellow => 0x7F7F00, Self::Blue => 0x00007F, Self::Magenta => 0x7F007F, Self::Cyan => 0x007F7F, Self::White => 0x7F7F7F, }; if bold { color * 2 } else { color } } } /// Represents a single character with its attributes #[derive(Clone, Copy)] #[repr(C)] pub struct ConsoleChar { attributes: u16, char: u8, _pad: u8, } /// Represents a single line in the console buffer #[derive(Clone, Copy)] pub struct ConsoleRow { dirty: u8, chars: [ConsoleChar; CONSOLE_ROW_LEN], } /// Buffer that contains text rows of the console with their attributes + tracks dirty rows which /// need to be flushed to the display pub struct ConsoleBuffer { rows: Vec, height: u32, } /// Console wrapper for adding it into devfs as a char device pub struct ConsoleWrapper<'a>(pub &'a dyn DisplayConsole); enum EscapeState { Normal, Escape, Csi, } /// Common state for console output devices pub struct ConsoleState { /// Current cursor row pub cursor_row: u32, /// Current cursor column pub cursor_col: u32, /// Current foreground color pub fg_color: ColorAttribute, /// Current background color pub bg_color: ColorAttribute, /// Current set of attributes pub attributes: Attributes, esc_args: StaticVector, esc_state: EscapeState, /// Row buffer pub buffer: ConsoleBuffer, } /// Helper type to iterate over dirty rows in the buffer pub struct RowIter<'a> { buffer: &'a mut ConsoleBuffer, index: u32, } /// Interface to implement buffered console semantics on an abstract console output device pub trait DisplayConsole: Sync { /// Returns the state lock fn state(&self) -> &IrqSafeSpinlock; /// Flushes the data from console buffer to the display fn flush(&self, state: &mut ConsoleState); /// Writes characters to the backing buffer + handles special control sequences fn write_char(&self, c: u8) { let mut state = self.state().lock(); if state.putc(c) { self.flush(&mut state); } } /// Returns the dimensions of the console in chars: (rows, columns) fn text_dimensions(&self) -> (usize, usize) { let state = self.state().lock(); (state.buffer.height as _, CONSOLE_ROW_LEN as _) } } impl ConsoleChar { /// Empty character pub const BLANK: Self = Self { attributes: 0, char: 0, _pad: 0, }; /// Constructs a console character from a char and its attributes #[inline(always)] pub fn from_parts( char: u8, fg: ColorAttribute, bg: ColorAttribute, attributes: Attributes, ) -> Self { let attributes = ((attributes.bits() as u16) << 8) | ((u8::from(bg) as u16) << 4) | (u8::from(fg) as u16); Self { attributes, char, _pad: 0, } } /// Returns the attributes of the character #[inline(always)] pub fn attributes(self) -> (ColorAttribute, ColorAttribute, Attributes) { let fg = ColorAttribute::try_from((self.attributes & 0xF) as u8).unwrap_or(DEFAULT_FG_COLOR); let bg = ColorAttribute::try_from(((self.attributes >> 4) & 0xF) as u8) .unwrap_or(DEFAULT_BG_COLOR); let attributes = Attributes::from_bits((self.attributes >> 8) as u8).unwrap_or(Attributes::empty()); (fg, bg, attributes) } /// Returns the character data of this [ConsoleChar] #[inline(always)] pub const fn character(self) -> u8 { self.char } } impl<'a> RowIter<'a> { /// Returns the next dirty row pub fn next_dirty(&mut self) -> Option<(u32, &[ConsoleChar])> { loop { if self.index == self.buffer.height { return None; } if !self.buffer.rows[self.index as usize].clear_dirty() { self.index += 1; continue; } let row_index = self.index; let row = &self.buffer.rows[self.index as usize]; self.index += 1; return Some((row_index, &row.chars)); } } } impl ConsoleRow { /// Constructs a row filled with blank characters pub const fn zeroed() -> Self { Self { dirty: 1, chars: [ConsoleChar { attributes: ((DEFAULT_BG_COLOR as u8) as u16) << 4, char: b' ', _pad: 0, }; CONSOLE_ROW_LEN], } } /// Returns `true` if the row's dirty flag is set #[inline] pub const fn is_dirty(&self) -> bool { self.dirty != 0 } /// Clears "dirty" flag for the row #[inline] pub fn clear_dirty(&mut self) -> bool { let old = self.dirty; self.dirty = 0; old == 1 } /// Clears the console row with blank characters pub fn clear(&mut self, bg: ColorAttribute) { self.dirty = 1; self.chars .fill(ConsoleChar::from_parts(b' ', bg, bg, Attributes::empty())); } } impl ConsoleBuffer { /// Constructs a fixed-size console buffer pub fn new(height: u32) -> Result { // let size = size_of::() * (height as usize); let mut rows = vec![ConsoleRow::zeroed(); height as usize]; for row in rows.iter_mut() { row.clear(DEFAULT_BG_COLOR); } Ok(Self { rows, height }) // let size = size_of::() * (height as usize); // let page_count = (size + 0xFFF) / 0x1000; // let pages = phys::alloc_pages_contiguous(page_count, PageUsage::Used)?; // let rows = unsafe { // core::slice::from_raw_parts_mut(pages.virtualize() as *mut ConsoleRow, height as usize) // }; // for row in rows.iter_mut() { // row.clear(DEFAULT_BG_COLOR); // } // Ok(Self { rows, height }) } #[inline(never)] fn set_char(&mut self, row: u32, col: u32, c: ConsoleChar) { self.rows[row as usize].dirty = 1; self.rows[row as usize].chars[col as usize] = c; } #[inline(never)] fn set_dirty(&mut self, row: u32) { self.rows[row as usize].dirty = 1; } /// Returns an iterator over dirty rows, while clearing dirty flag for them pub fn flush_rows(&mut self) -> RowIter { RowIter { buffer: self, index: 0, } } fn clear(&mut self, bg: ColorAttribute) { for row in self.rows.iter_mut() { row.clear(bg); } } fn clear_row(&mut self, row: u32, bg: ColorAttribute) { self.rows[row as usize].dirty = 1; self.rows[row as usize].clear(bg); } fn erase_in_row(&mut self, row: u32, start: usize, bg: ColorAttribute) { self.rows[row as usize].dirty = 1; self.rows[row as usize].chars[start..].fill(ConsoleChar::from_parts( b' ', DEFAULT_FG_COLOR, bg, Attributes::empty(), )); } fn scroll_once(&mut self, bg: ColorAttribute) { self.rows.copy_within(1.., 0); self.rows[(self.height - 1) as usize].clear(bg); // Mark everything dirty self.rows.iter_mut().for_each(|row| { row.dirty = 1; }); } } impl ConsoleState { /// Constructs a new console state with given buffer pub fn new(buffer: ConsoleBuffer) -> Self { Self { cursor_row: 0, cursor_col: 0, fg_color: DEFAULT_FG_COLOR, bg_color: DEFAULT_BG_COLOR, attributes: Attributes::empty(), esc_args: StaticVector::new(), esc_state: EscapeState::Normal, buffer, } } fn putc_normal(&mut self, c: u8) -> bool { let mut flush = false; match c { c if c >= 127 => { self.buffer.set_char( self.cursor_row, self.cursor_col, ConsoleChar::from_parts( b'?', self.fg_color, ColorAttribute::Red, self.attributes, ), ); self.cursor_col += 1; } b'\x1b' => { self.esc_state = EscapeState::Escape; return false; } b'\r' => { self.cursor_col = 0; } b'\n' => { self.cursor_row += 1; self.cursor_col = 0; flush = true; } _ => { self.buffer.set_char( self.cursor_row, self.cursor_col, ConsoleChar::from_parts(c, self.fg_color, self.bg_color, self.attributes), ); self.cursor_col += 1; } } if self.cursor_col == CONSOLE_ROW_LEN as u32 { self.cursor_col = 0; self.cursor_row += 1; } if self.cursor_row == self.buffer.height { self.buffer.scroll_once(self.bg_color); self.cursor_row = self.buffer.height - 1; flush = true; } flush } fn handle_csi(&mut self, c: u8) -> bool { match c { // Move back one character b'D' => { if self.cursor_col > 0 { self.cursor_col -= 1; } } // Manipulate display attributes b'm' => { if let Some(arg) = self.esc_args.first() { match arg { // Reset 0 => { self.fg_color = DEFAULT_FG_COLOR; self.bg_color = DEFAULT_BG_COLOR; self.attributes = Attributes::empty(); } // Bold 1 => { self.attributes |= Attributes::BOLD; } // Foreground colors 30..=39 => { let vt_color = self.esc_args[0] % 10; if vt_color == 9 { self.fg_color = DEFAULT_FG_COLOR; } else { self.fg_color = ColorAttribute::from_vt100(vt_color as u8); } } // Background colors 40..=49 => { let vt_color = self.esc_args[0] % 10; if vt_color == 9 { self.bg_color = DEFAULT_BG_COLOR; } else { self.bg_color = ColorAttribute::from_vt100(vt_color as u8); } } _ => (), } } } // Move cursor to position b'f' => { let row = self.esc_args[0].clamp(1, self.buffer.height) - 1; let col = self.esc_args[1].clamp(1, CONSOLE_ROW_LEN as u32) - 1; self.buffer.set_dirty(row); self.cursor_row = row; self.cursor_col = col; } // Clear rows/columns/screen b'J' => match self.esc_args[0] { // Erase lines down 0 => (), // Erase lines up 1 => (), // Erase all 2 => { self.buffer.clear(self.bg_color); } _ => (), }, // Erase in Line b'K' => match self.esc_args[0] { // Erase to Right 0 => { self.buffer .erase_in_row(self.cursor_row, self.cursor_col as _, self.bg_color); } // Erase All 2 => { self.buffer.clear_row(self.cursor_row, self.bg_color); } _ => (), }, _ => (), } self.esc_state = EscapeState::Normal; false } fn handle_csi_byte(&mut self, c: u8) -> bool { match c { b'0'..=b'9' => { let arg = self.esc_args.last_mut().unwrap(); *arg *= 10; *arg += (c - b'0') as u32; false } b';' => { self.esc_args.push(0); false } _ => self.handle_csi(c), } } fn putc(&mut self, c: u8) -> bool { match self.esc_state { EscapeState::Normal => self.putc_normal(c), EscapeState::Escape => match c { b'[' => { self.esc_state = EscapeState::Csi; self.esc_args.clear(); self.esc_args.push(0); false } _ => { self.esc_state = EscapeState::Normal; self.esc_args.clear(); false } }, EscapeState::Csi => self.handle_csi_byte(c), } } } impl DebugSink for dyn DisplayConsole { fn putc(&self, c: u8) -> Result<(), Error> { self.write_char(c); Ok(()) } fn supports_control_sequences(&self) -> bool { true } } impl TerminalOutput for ConsoleWrapper<'_> { fn size(&self) -> TerminalSize { let (rows, columns) = self.0.text_dimensions(); TerminalSize { rows, columns } } fn write(&self, byte: u8) -> Result<(), Error> { self.0.write_char(byte); Ok(()) } } static CONSOLES: IrqSafeSpinlock> = IrqSafeSpinlock::new(Vec::new()); /// Adds a console device to a auto-flush list pub fn add_console_autoflush(console: &'static dyn DisplayConsole) { CONSOLES.lock().push(console); } /// Flushes console buffers to their displays pub fn flush_consoles() { for console in CONSOLES.lock().iter() { let mut state = console.state().lock(); console.flush(&mut state); } } /// Periodically flushes data from console buffers onto their displays pub async fn update_consoles_task() { loop { flush_consoles(); runtime::sleep(Duration::from_millis(20)).await; } }