From 643df0972252d5db2b2cd51aaee23e515a325043 Mon Sep 17 00:00:00 2001 From: wyt8 <2253457010@qq.com> Date: Tue, 10 Feb 2026 16:19:03 +0000 Subject: [PATCH] Refactor the processing of ANSI escape sequences --- kernel/comps/framebuffer/src/ansi_escape.rs | 394 ++++++++++++++++---- kernel/comps/framebuffer/src/console.rs | 295 +++++++-------- kernel/comps/framebuffer/src/lib.rs | 6 +- 3 files changed, 446 insertions(+), 249 deletions(-) diff --git a/kernel/comps/framebuffer/src/ansi_escape.rs b/kernel/comps/framebuffer/src/ansi_escape.rs index b537b99cd..3cb98ac7a 100644 --- a/kernel/comps/framebuffer/src/ansi_escape.rs +++ b/kernel/comps/framebuffer/src/ansi_escape.rs @@ -4,13 +4,28 @@ use crate::Pixel; /// A finite-state machine (FSM) to handle ANSI escape sequences. #[derive(Debug)] -pub(super) struct EscapeFsm { +pub struct EscapeFsm { state: WaitFor, - params: [u32; MAX_PARAMS], + params: [Option; MAX_PARAMS], +} + +impl Default for EscapeFsm { + fn default() -> Self { + Self::new() + } +} + +/// The mode for "Erase in Display" (ED) command. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum EraseInDisplay { + CursorToEnd, + CursorToBeginning, + EntireScreen, + EntireScreenAndScrollback, } /// A trait to execute operations from ANSI escape sequences. -pub(super) trait EscapeOp { +pub trait EscapeOp { /// Sets the cursor position. fn set_cursor(&mut self, x: usize, y: usize); @@ -18,15 +33,30 @@ pub(super) trait EscapeOp { fn set_fg_color(&mut self, val: Pixel); /// Sets the background color. fn set_bg_color(&mut self, val: Pixel); + + /// Erases part or all of the display. + fn erase_in_display(&mut self, mode: EraseInDisplay); } const MAX_PARAMS: usize = 8; +// FIXME: Currently we only support a few ANSI escape sequences, and we just swallow the unsupported ones. #[derive(Clone, Copy, Debug)] enum WaitFor { Escape, - Bracket, - Params(u8), + /// Just saw ESC (0x1B), expecting the next selector. + AfterEscape, + /// Currently parsing CSI parameters. + Csi { + idx: u8, + is_private: bool, + saw_digit: bool, + in_intermediate: bool, + }, + /// OSC payload after ESC] . Swallow until BEL (0x07) or ST (ESC \). + Osc, + /// Saw ESC inside OSC and it maybe ST. + OscEscape, } /// Foreground and background colors. @@ -69,92 +99,265 @@ const COLORS: [Pixel; 16] = [ ]; impl EscapeFsm { - pub(super) fn new() -> Self { + pub fn new() -> Self { Self { state: WaitFor::Escape, - params: [0; MAX_PARAMS], + params: [None; MAX_PARAMS], } } - /// Tries to eat a character as part of the ANSI escape sequence. + /// Tries to eat a byte as part of the ANSI escape sequence. /// - /// This method returns a boolean value indicating whether the character is part of an ANSI - /// escape sequence. In other words, if the method returns true, then the character has been - /// eaten and should not be displayed in the console. - pub(super) fn eat(&mut self, byte: u8, op: &mut T) -> bool { - let num_params = match (self.state, byte) { - // Handle '\033'. - (WaitFor::Escape, 0o33) => { - self.state = WaitFor::Bracket; - return true; - } - (WaitFor::Escape, _) => { - // This is not an ANSI escape sequence. - return false; + /// Returns `true` if the byte is consumed by the FSM and must not be rendered as text. + /// Returns `false` if the byte is not part of an escape sequence and should be rendered. + pub fn eat(&mut self, byte: u8, op: &mut T) -> bool { + match self.state { + WaitFor::Escape => { + if byte == 0x1b { + self.state = WaitFor::AfterEscape; + return true; + } + false } - // Handle '['. - (WaitFor::Bracket, b'[') => { - self.state = WaitFor::Params(0); - self.params[0] = 0; - return true; - } - (WaitFor::Bracket, _) => { - // The character is invalid. We cannot handle it, so we are aborting the ANSI - // escape sequence. - self.state = WaitFor::Escape; - return true; + WaitFor::AfterEscape => { + match byte { + b'[' => { + // CSI begins. + self.params.fill(None); + self.state = WaitFor::Csi { + idx: 0, + is_private: false, + saw_digit: false, + in_intermediate: false, + }; + true + } + b']' => { + // OSC begins. + self.state = WaitFor::Osc; + true + } + _ => { + // The character is invalid. We cannot handle it, so we are aborting the ANSI + // escape sequence. + self.state = WaitFor::Escape; + true + } + } } - // Handle numeric parameters. - (WaitFor::Params(i), b'0'..=b'9') => { - let param = &mut self.params[i as usize]; - *param = param.wrapping_mul(10).wrapping_add((byte - b'0') as u32); - return true; - } - (WaitFor::Params(i), b';') if (i as usize + 1) < MAX_PARAMS => { - self.state = WaitFor::Params(i + 1); - self.params[i as usize + 1] = 0; - return true; - } - (WaitFor::Params(_), b';') => { - // There are too many parameters. We cannot handle that many, so we are aborting - // the ANSI escape sequence. - self.state = WaitFor::Escape; - return true; + WaitFor::Osc => { + match byte { + 0x07 => { + // BEL terminator + self.state = WaitFor::Escape; + true + } + 0x1b => { + // Might be ST + self.state = WaitFor::OscEscape; + true + } + _ => { + // Swallow OSC payload. + true + } + } } - // Break and handle the final action. - (WaitFor::Params(i), _) => { - self.state = WaitFor::Escape; - (i + 1) as usize + WaitFor::OscEscape => { + if byte == b'\\' { + self.state = WaitFor::Escape; + true + } else { + // Not ST and we go back to OSC and keep swallowing. + self.state = WaitFor::Osc; + true + } } - }; - match byte { - // CUP - Cursor Position - b'H' if num_params == 2 => { - op.set_cursor( - self.params[1].saturating_sub(1) as usize, - self.params[0].saturating_sub(1) as usize, - ); + WaitFor::Csi { + idx, + is_private, + saw_digit, + in_intermediate, + } => { + match byte { + // Intermediate bytes (0x20..=0x2F). + // Once we see any intermediate, we are in the intermediate section; + // later bytes must be intermediate or final. + 0x20..=0x2f => { + // If we already entered intermediate section, just keep swallowing them. + // If we were still in parameter section, we now transition to intermediate. + self.state = WaitFor::Csi { + idx, + is_private, + saw_digit, + in_intermediate: true, + }; + true + } + + // Parameter bytes (0x30..=0x3F). + 0x30..=0x3f if !in_intermediate => { + match byte { + // digits: contribute to numeric params + b'0'..=b'9' => { + let i = idx as usize; + if i < MAX_PARAMS { + let p = &mut self.params[i]; + *p = Some( + p.unwrap_or(0) + .saturating_mul(10) + .saturating_add((byte - b'0') as u32), + ); + } + self.state = WaitFor::Csi { + idx, + is_private, + saw_digit: true, + in_intermediate: false, + }; + } + + // ';' separates numeric parameters. + b';' => { + let next = idx.saturating_add(1); + if (next as usize) < MAX_PARAMS { + // If there were no digits for this param, it already stays None. + self.state = WaitFor::Csi { + idx: next, + is_private, + saw_digit: false, + in_intermediate: false, + }; + } else { + // There are too many parameters. We cannot handle that many, so we are aborting + // the ANSI escape sequence. + self.state = WaitFor::Escape; + } + } + + b':' => { + // The behavior of ':' is not defined by the standard. + log::warn!("EscapeFsm: unsupported ':' parameter separator in CSI"); + } + + // Sequences containing <=>? are "private". We swallow them and mark `is_private`. + b'<' | b'=' | b'>' | b'?' => { + self.state = WaitFor::Csi { + idx, + is_private: true, + saw_digit, + in_intermediate: false, + }; + } + + _ => unreachable!(), + } + true + } + + // Parameter bytes after intermediate section is illegal by the formal grammar. + // We'll abort and swallow to avoid leaking garbage. + 0x30..=0x3f if in_intermediate => { + self.state = WaitFor::Escape; + true + } + + // Final byte (0x40..=0x7E): ends the CSI. + 0x40..=0x7e => { + self.state = WaitFor::Escape; + + let num_params = (idx as usize).saturating_add(1).min(MAX_PARAMS); + + self.dispatch_csi(byte, num_params, is_private, op); + true + } + + _ => { + // Terminal behavior is undefined if a CSI contains bytes outside 0x20..=0x7E. + log::warn!("EscapeFsm: invalid byte {:#x} in CSI sequence", byte); + self.state = WaitFor::Escape; + true + } + } + } + } + } + + /// Gets the parameter at the given index, or returns the default value if the parameter is not present. + fn param_or(&self, i: usize, default: u32) -> u32 { + self.params.get(i).and_then(|p| *p).unwrap_or(default) + } + + fn dispatch_csi( + &self, + final_byte: u8, + num_params: usize, + is_private: bool, + op: &mut T, + ) { + if is_private { + // For now we don't handle any private sequences, so just swallow them. + return; + } + + match final_byte { + // CUP - Cursor Position: CSI n ; m H + // + // - n=row, m=col + // - default to 1 if omitted + // + // Examples: + // - CSI H -> 1;1 + // - CSI ;5H -> 1;5 + // - CSI 17H -> 17;1 + // - CSI 17;H -> 17;1 + // - CSI 17;1H -> 17;1 + b'H' => { + let row_1b = self.param_or(0, 1); + let col_1b = self.param_or(1, 1); + + op.set_cursor((col_1b - 1) as usize, (row_1b - 1) as usize); + } + + // ED - Erase in Display: CSI n J + // + // n: + // - 0 (or missing): cursor to end of screen + // - 1: cursor to beginning of screen + // - 2: entire screen + // - 3: entire screen + scrollback + b'J' => { + let n = self.param_or(0, 0); + let mode = match n { + 0 => EraseInDisplay::CursorToEnd, + 1 => EraseInDisplay::CursorToBeginning, + 2 => EraseInDisplay::EntireScreen, + 3 => EraseInDisplay::EntireScreenAndScrollback, + _ => { + // Invalid parameter. + return; + } + }; + op.erase_in_display(mode); } // SGR - Select Graphic Rendition - b'm' => self.handle_srg(num_params, op), + b'm' => self.handle_sgr(num_params, op), - // Invalid or unsupported + // Unknown CSI: swallow silently. _ => {} } - - true } /// Handles the "Select Graphic Rendition" sequence. - fn handle_srg(&self, num_params: usize, op: &mut T) { + fn handle_sgr(&self, num_params: usize, op: &mut T) { let mut cursor = 0; while cursor < num_params { - let op_code = self.params[cursor]; + let op_code = self.param_or(cursor, 0) as u8; cursor += 1; match op_code { @@ -167,15 +370,15 @@ impl EscapeFsm { // Set foreground colors // Reference: 30..=37 => op.set_fg_color(COLORS[op_code as usize - 30]), - 38 if num_params - cursor >= 2 && self.params[cursor] == 5 => { - op.set_fg_color(Self::get_256_color(self.params[cursor + 1] as u8)); + 38 if num_params - cursor >= 2 && self.param_or(cursor, 0) == 5 => { + op.set_fg_color(Self::get_256_color(self.param_or(cursor + 1, 0) as u8)); cursor += 2; } - 38 if num_params - cursor >= 4 && self.params[cursor] == 2 => { + 38 if num_params - cursor >= 4 && self.param_or(cursor, 0) == 2 => { op.set_fg_color(Pixel { - red: self.params[cursor + 1] as u8, - green: self.params[cursor + 2] as u8, - blue: self.params[cursor + 3] as u8, + red: self.param_or(cursor + 1, 0) as u8, + green: self.param_or(cursor + 2, 0) as u8, + blue: self.param_or(cursor + 3, 0) as u8, }); cursor += 4; } @@ -186,15 +389,15 @@ impl EscapeFsm { // Set background colors // Reference: 40..=47 => op.set_bg_color(COLORS[op_code as usize - 40]), - 48 if num_params - cursor >= 2 && self.params[cursor] == 5 => { - op.set_bg_color(Self::get_256_color(self.params[cursor + 1] as u8)); + 48 if num_params - cursor >= 2 && self.param_or(cursor, 0) == 5 => { + op.set_bg_color(Self::get_256_color(self.param_or(cursor + 1, 0) as u8)); cursor += 2; } - 48 if num_params - cursor >= 4 && self.params[cursor] == 2 => { + 48 if num_params - cursor >= 4 && self.param_or(cursor, 0) == 2 => { op.set_bg_color(Pixel { - red: self.params[cursor + 1] as u8, - green: self.params[cursor + 2] as u8, - blue: self.params[cursor + 3] as u8, + red: self.param_or(cursor + 1, 0) as u8, + green: self.param_or(cursor + 2, 0) as u8, + blue: self.param_or(cursor + 3, 0) as u8, }); cursor += 4; } @@ -260,6 +463,7 @@ mod test { y: usize, fg: Pixel, bg: Pixel, + last_ed: Option, } impl Default for State { @@ -269,6 +473,7 @@ mod test { y: 0, fg: Pixel::WHITE, bg: Pixel::BLACK, + last_ed: None, } } } @@ -286,6 +491,10 @@ mod test { fn set_bg_color(&mut self, val: Pixel) { self.bg = val; } + + fn erase_in_display(&mut self, mode: EraseInDisplay) { + self.last_ed = Some(mode); + } } fn eat_escape_sequence(esc_fsm: &mut EscapeFsm, state: &mut State, bytes: &[u8]) { @@ -306,15 +515,38 @@ mod test { assert!(!esc_fsm.eat(b'a', &mut state)); - // There is invalid as there is no 0-th row or 0-th column. But in this case, let's move - // the cursor to the first row and the first column. - eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[0;0H"); + // CUP defaults to 1;1 when omitted. + eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[H"); assert_eq!(state.x, 0); assert_eq!(state.y, 0); assert!(!esc_fsm.eat(b'a', &mut state)); } + #[ktest] + fn erase_in_display() { + let mut esc_fsm = EscapeFsm::new(); + let mut state = State::default(); + + // Default (or missing) is 0: cursor to end of screen. + eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[J"); + assert_eq!(state.last_ed, Some(EraseInDisplay::CursorToEnd)); + + eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[1J"); + assert_eq!(state.last_ed, Some(EraseInDisplay::CursorToBeginning)); + + eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[2J"); + assert_eq!(state.last_ed, Some(EraseInDisplay::EntireScreen)); + + eat_escape_sequence(&mut esc_fsm, &mut state, b"\x1B[3J"); + assert_eq!( + state.last_ed, + Some(EraseInDisplay::EntireScreenAndScrollback) + ); + + assert!(!esc_fsm.eat(b'a', &mut state)); + } + #[ktest] fn set_color() { let mut esc_fsm = EscapeFsm::new(); diff --git a/kernel/comps/framebuffer/src/console.rs b/kernel/comps/framebuffer/src/console.rs index a517026c8..b783ce279 100644 --- a/kernel/comps/framebuffer/src/console.rs +++ b/kernel/comps/framebuffer/src/console.rs @@ -2,167 +2,63 @@ use alloc::{sync::Arc, vec::Vec}; -use aster_console::{ - AnyConsoleDevice, ConsoleCallback, ConsoleSetFontError, - font::BitmapFont, - mode::{ConsoleMode, KeyboardMode}, -}; -use ostd::{ - mm::{HasSize, VmReader}, - sync::{LocalIrqDisabled, SpinLock}, -}; -use spin::Once; +use aster_console::{ConsoleSetFontError, font::BitmapFont, mode::ConsoleMode}; +use ostd::mm::HasSize; use crate::{ - FRAMEBUFFER, FrameBuffer, Pixel, - ansi_escape::{EscapeFsm, EscapeOp}, + FrameBuffer, Pixel, + ansi_escape::{EraseInDisplay, EscapeOp}, }; -/// A text console rendered onto the framebuffer. -pub struct FramebufferConsole { - callbacks: SpinLock, - inner: SpinLock<(ConsoleState, EscapeFsm), LocalIrqDisabled>, -} - -pub const CONSOLE_NAME: &str = "Framebuffer-Console"; - -pub static FRAMEBUFFER_CONSOLE: Once> = Once::new(); - -pub(crate) fn init() { - let Some(fb) = FRAMEBUFFER.get() else { - log::warn!("Framebuffer not initialized"); - return; - }; - - FRAMEBUFFER_CONSOLE.call_once(|| Arc::new(FramebufferConsole::new(fb.clone()))); -} - -impl AnyConsoleDevice for FramebufferConsole { - fn send(&self, buf: &[u8]) { - let mut inner = self.inner.lock(); - let (state, esc_fsm) = &mut *inner; - - for byte in buf { - if esc_fsm.eat(*byte, state) { - // The character is part of an ANSI escape sequence. - continue; - } - - if *byte == 0 { - // The character is a NUL character. - continue; - } - - state.send_char(*byte); - } - } - - fn register_callback(&self, callback: &'static ConsoleCallback) { - self.callbacks.lock().callbacks.push(callback); - } - - fn set_font(&self, font: BitmapFont) -> Result<(), ConsoleSetFontError> { - self.inner.lock().0.set_font(font) - } - - fn set_mode(&self, mode: ConsoleMode) -> bool { - self.inner.lock().0.set_mode(mode); - true - } - - fn mode(&self) -> Option { - Some(self.inner.lock().0.mode()) - } - - fn set_keyboard_mode(&self, mode: KeyboardMode) -> bool { - match mode { - KeyboardMode::Xlate => self.callbacks.lock().is_input_enabled = true, - KeyboardMode::Off => self.callbacks.lock().is_input_enabled = false, - _ => return false, - } - true - } - - fn keyboard_mode(&self) -> Option { - if self.callbacks.lock().is_input_enabled { - Some(KeyboardMode::Xlate) - } else { - Some(KeyboardMode::Off) - } - } -} - -impl FramebufferConsole { - /// Creates a new framebuffer console. - pub(self) fn new(framebuffer: Arc) -> Self { - let callbacks = ConsoleCallbacks { - callbacks: Vec::new(), - is_input_enabled: true, - }; - - let state = ConsoleState { - x_pos: 0, - y_pos: 0, - fg_color: Pixel::WHITE, - bg_color: Pixel::BLACK, - font: BitmapFont::new_basic8x8(), - is_output_enabled: true, - - bytes: alloc::vec![0u8; framebuffer.io_mem().size()], - backend: framebuffer, - }; - - let esc_fsm = EscapeFsm::new(); - - Self { - callbacks: SpinLock::new(callbacks), - inner: SpinLock::new((state, esc_fsm)), - } - } - - /// Triggers the registered input callbacks with the given data. - pub(crate) fn trigger_input_callbacks(&self, bytes: &[u8]) { - let callbacks = self.callbacks.lock(); - if !callbacks.is_input_enabled { - return; - } - - let reader = VmReader::from(bytes); - for callback in callbacks.callbacks.iter() { - callback(reader.clone()); - } - } -} - -impl core::fmt::Debug for FramebufferConsole { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("FramebufferConsole").finish_non_exhaustive() - } -} - -struct ConsoleCallbacks { - callbacks: Vec<&'static ConsoleCallback>, - /// Whether the input characters will be handled by the callbacks. - is_input_enabled: bool, -} - #[derive(Debug)] -struct ConsoleState { +pub struct ConsoleState { x_pos: usize, y_pos: usize, fg_color: Pixel, bg_color: Pixel, font: BitmapFont, /// Whether the output characters will be drawn in the framebuffer. - is_output_enabled: bool, - + is_rendering_enabled: bool, + mode: ConsoleMode, bytes: Vec, backend: Arc, } impl ConsoleState { + pub fn new(backend: Arc) -> Self { + let buffer_size = backend.io_mem().size(); + Self { + x_pos: 0, + y_pos: 0, + fg_color: Pixel::WHITE, + bg_color: Pixel::BLACK, + font: BitmapFont::new_basic8x8(), + is_rendering_enabled: false, + mode: ConsoleMode::Text, + bytes: alloc::vec![0; buffer_size], + backend, + } + } + + /// Enables rendering to the framebuffer. + pub fn enable_rendering(&mut self) { + self.is_rendering_enabled = true; + } + + /// Disables rendering to the framebuffer. + pub fn disable_rendering(&mut self) { + self.is_rendering_enabled = false; + } + + /// Flushes the entire console buffer to the framebuffer. + pub fn flush_fullscreen(&self) { + if self.is_rendering_enabled && self.mode == ConsoleMode::Text { + self.backend.write_bytes_at(0, &self.bytes).unwrap(); + } + } + /// Sends a single character to be drawn on the framebuffer. - pub(self) fn send_char(&mut self, ch: u8) { + pub fn send_char(&mut self, ch: u8) { if ch == b'\n' { self.newline(); return; @@ -197,7 +93,7 @@ impl ConsoleState { self.bytes.copy_within(offset.., 0); self.bytes[self.backend.io_mem().size() - offset..].fill(0); - if self.is_output_enabled { + if self.is_rendering_enabled && self.mode == ConsoleMode::Text { self.backend.write_bytes_at(0, &self.bytes).unwrap(); } @@ -243,7 +139,7 @@ impl ConsoleState { } // Write pixels to the framebuffer. - if self.is_output_enabled { + if self.is_rendering_enabled && self.mode == ConsoleMode::Text { self.backend.write_bytes_at(off_st, render_buf).unwrap(); } @@ -252,7 +148,7 @@ impl ConsoleState { } /// Sets the font for the framebuffer console. - pub(self) fn set_font(&mut self, font: BitmapFont) -> Result<(), ConsoleSetFontError> { + pub fn set_font(&mut self, font: BitmapFont) -> Result<(), ConsoleSetFontError> { // Note that the font height cannot exceed the half the height of the framebuffer. // Otherwise, `shift_lines_up` will underflow `x_pos`. if font.width() > self.backend.width() || font.height() > self.backend.height() / 2 { @@ -269,29 +165,66 @@ impl ConsoleState { } /// Sets the console mode (text or graphics). - pub(self) fn set_mode(&mut self, mode: ConsoleMode) { - if mode == ConsoleMode::Graphics { - self.is_output_enabled = false; + pub fn set_mode(&mut self, mode: ConsoleMode) { + if self.mode == mode { return; } - - if self.is_output_enabled { - return; - } - - // We're switching from the graphics mode back to the text mode. The characters need to be - // redrawn in the framebuffer. - self.is_output_enabled = true; - self.backend.write_bytes_at(0, &self.bytes).unwrap(); + self.mode = mode; } /// Gets the current console mode. - pub(self) fn mode(&self) -> ConsoleMode { - if self.is_output_enabled { - ConsoleMode::Text - } else { - ConsoleMode::Graphics + pub fn mode(&self) -> ConsoleMode { + self.mode + } + + /// Fill a rectangular pixel region [x0, x1) × [y0, y1) with the given color. + /// + /// This writes to the shadow buffer, and optionally flushes + /// the affected region to the real framebuffer. + fn fill_rect_pixels(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, color: Pixel) { + if x0 >= x1 || y0 >= y1 { + return; } + + let w = self.backend.width(); + let h = self.backend.height(); + let x0 = x0.min(w); + let x1 = x1.min(w); + let y0 = y0.min(h); + let y1 = y1.min(h); + + if x0 >= x1 || y0 >= y1 { + return; + } + + let rendered_pixel = self.backend.render_pixel(color); + let rendered_pixel_size = rendered_pixel.nbytes(); + let row_bytes = (x1 - x0) * rendered_pixel_size; + + // Fill shadow buffer row by row. + for y in y0..y1 { + let off = self.backend.calc_offset(x0, y).as_usize(); + let buf = &mut self.bytes[off..off + row_bytes]; + + // Write pixels. + for chunk in buf.chunks_exact_mut(rendered_pixel_size) { + chunk.copy_from_slice(rendered_pixel.as_slice()); + } + + // Flush to framebuffer if needed. + if self.is_rendering_enabled && self.mode == ConsoleMode::Text { + self.backend.write_bytes_at(off, buf).unwrap(); + } + } + } + + /// Pixel coordinates for the cursor cell. + fn cursor_cell_rect(&self) -> (usize, usize, usize, usize) { + let cx = self.x_pos.min(self.backend.width()); + let cy = self.y_pos.min(self.backend.height()); + let x1 = (cx + self.font.width()).min(self.backend.width()); + let y1 = (cy + self.font.height()).min(self.backend.height()); + (cx, cy, x1, y1) } } @@ -313,4 +246,38 @@ impl EscapeOp for ConsoleState { fn set_bg_color(&mut self, val: Pixel) { self.bg_color = val; } + + fn erase_in_display(&mut self, mode: EraseInDisplay) { + let bg = self.bg_color; + let w = self.backend.width(); + let h = self.backend.height(); + + let (cx0, cy0, cx1, cy1) = self.cursor_cell_rect(); + + match mode { + EraseInDisplay::CursorToEnd => { + // Clear from cursor x to end-of-line, within the cursor row. + self.fill_rect_pixels(cx0, cy0, w, cy1, bg); + + // Clear all rows below cursor row. + if cy1 < h { + self.fill_rect_pixels(0, cy1, w, h, bg); + } + } + + EraseInDisplay::CursorToBeginning => { + // Clear all rows above cursor row. + if cy0 > 0 { + self.fill_rect_pixels(0, 0, w, cy0, bg); + } + + // Clear from start-of-line to cursor cell end, within cursor row. + self.fill_rect_pixels(0, cy0, cx1, cy1, bg); + } + + EraseInDisplay::EntireScreen | EraseInDisplay::EntireScreenAndScrollback => { + self.fill_rect_pixels(0, 0, w, h, bg); + } + } + } } diff --git a/kernel/comps/framebuffer/src/lib.rs b/kernel/comps/framebuffer/src/lib.rs index e7cb1c387..fca0ad5f5 100644 --- a/kernel/comps/framebuffer/src/lib.rs +++ b/kernel/comps/framebuffer/src/lib.rs @@ -8,13 +8,13 @@ extern crate alloc; mod ansi_escape; mod console; -mod console_input; mod dummy_console; mod framebuffer; mod pixel; +pub use ansi_escape::{EscapeFsm, EscapeOp}; use component::{ComponentInitError, init_component}; -pub use console::{CONSOLE_NAME, FRAMEBUFFER_CONSOLE, FramebufferConsole}; +pub use console::ConsoleState; pub use dummy_console::DummyFramebufferConsole; pub use framebuffer::{ColorMapEntry, FRAMEBUFFER, FrameBuffer, MAX_CMAP_SIZE}; pub use pixel::{Pixel, PixelFormat, RenderedPixel}; @@ -22,7 +22,5 @@ pub use pixel::{Pixel, PixelFormat, RenderedPixel}; #[init_component] fn init() -> Result<(), ComponentInitError> { framebuffer::init(); - console::init(); - console_input::init(); Ok(()) }