diff --git a/kernel/src/arch/aarch64/driver/console/escape_parser.rs b/kernel/src/arch/aarch64/driver/console/escape_parser.rs new file mode 100644 index 00000000..5c308f2e --- /dev/null +++ b/kernel/src/arch/aarch64/driver/console/escape_parser.rs @@ -0,0 +1,152 @@ +//! ANSI escape sequences parser +//! (ref: https://en.wikipedia.org/wiki/ANSI_escape_code) + +use super::color::{ConsoleColor, ConsoleColor::*, FramebufferColor}; +use alloc::vec::Vec; + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CharacterAttribute { + /// foreground color + pub foreground: C, + /// background color + pub background: C, + /// show underline + pub underline: bool, + /// swap foreground and background colors + pub reverse: bool, + /// text marked cfor deletion + pub strikethrough: bool, +} + +impl Default for CharacterAttribute { + fn default() -> Self { + CharacterAttribute { + foreground: White, + background: Black, + underline: false, + reverse: false, + strikethrough: false, + } + } +} + +#[derive(Debug, PartialEq)] +enum ParseStatus { + /// The last character is `ESC`, start parsing the escape sequence. + BeginEscapeSequence, + + /// The character followed by `ESC` is `[`, start parsing the CSI (Control + /// Sequence Introducer) sequence. The CSI sequence format is like + /// `ESC [ n1 ; n2 ; ... m`. + ParsingCSI, + + /// Display text Normally. + Text, +} + +#[derive(Debug)] +pub struct EscapeParser { + status: ParseStatus, + char_attr: CharacterAttribute, + current_param: Option, + params: Vec, +} + +impl EscapeParser { + pub fn new() -> EscapeParser { + EscapeParser { + status: ParseStatus::Text, + char_attr: CharacterAttribute::default(), + params: Vec::new(), + current_param: None, + } + } + + pub fn is_parsing(&self) -> bool { + self.status != ParseStatus::Text + } + + /// See an `ECS` character, start parsing escape sequence. + pub fn start_parse(&mut self) { + assert!(self.status == ParseStatus::Text); + self.status = ParseStatus::BeginEscapeSequence; + self.current_param = None; + } + + //// Parse SGR (Select Graphic Rendition) parameters. + fn parse_sgr_params(&mut self) { + use core::mem::transmute; + for param in &self.params { + match param { + 0 => self.char_attr = CharacterAttribute::default(), + 4 => self.char_attr.underline = true, + 7 => self.char_attr.reverse = true, + 9 => self.char_attr.strikethrough = true, + 24 => self.char_attr.underline = false, + 27 => self.char_attr.reverse = false, + 29 => self.char_attr.strikethrough = false, + 30...37 | 90...97 => self.char_attr.foreground = unsafe { transmute(param - 30) }, + 40...47 | 100...107 => self.char_attr.background = unsafe { transmute(param - 40) }, + _ => { /* unimplemented!() */ } + } + } + } + + /// See a character during parsing. + pub fn parse(&mut self, byte: u8) -> bool { + assert!(self.status != ParseStatus::Text); + match self.status { + ParseStatus::BeginEscapeSequence => match byte { + b'[' => { + self.status = ParseStatus::ParsingCSI; + self.current_param = Some(0); + self.params.clear(); + return true; + } + _ => { /* unimplemented!() */ } + }, + ParseStatus::ParsingCSI => match byte { + b'0'...b'9' => { + let digit = (byte - b'0') as u32; + if let Some(param) = self.current_param { + let res: u32 = param as u32 * 10 + digit; + self.current_param = if res <= 0xFF { Some(res as u8) } else { None }; + } + return true; + } + b';' => { + if let Some(param) = self.current_param { + self.params.push(param); + } + self.current_param = Some(0); + return true; + } + // @A–Z[\]^_`a–z{|}~ + 0x40...0x7E => { + if let Some(param) = self.current_param { + self.params.push(param); + } + match byte { + b'm' => self.parse_sgr_params(), + _ => { /* unimplemented!() */ } + } + self.status = ParseStatus::Text; + self.current_param = None; + self.params.clear(); + return true; + } + _ => {} + }, + ParseStatus::Text => {} + } + self.status = ParseStatus::Text; + self.current_param = None; + self.params.clear(); + false + } + + pub fn char_attribute(&self) -> CharacterAttribute { + self.char_attr + } +} diff --git a/kernel/src/arch/aarch64/driver/console/fonts/font8x16.rs b/kernel/src/arch/aarch64/driver/console/fonts/font8x16.rs index d62518cd..89ffe67c 100644 --- a/kernel/src/arch/aarch64/driver/console/fonts/font8x16.rs +++ b/kernel/src/arch/aarch64/driver/console/fonts/font8x16.rs @@ -4622,6 +4622,9 @@ impl Font for Font8x16 { const HEIGHT: usize = 16; const WIDTH: usize = 8; + const UNDERLINE: usize = 13; + const STRIKETHROUGH: usize = 8; + #[inline] fn get(byte: u8, x: usize, y: usize) -> bool { Self::DATA[byte as usize * 16 + y] & (1 << (7 - x)) != 0 diff --git a/kernel/src/arch/aarch64/driver/console/fonts/mod.rs b/kernel/src/arch/aarch64/driver/console/fonts/mod.rs index 277f477c..fde1f9af 100644 --- a/kernel/src/arch/aarch64/driver/console/fonts/mod.rs +++ b/kernel/src/arch/aarch64/driver/console/fonts/mod.rs @@ -8,5 +8,9 @@ pub trait Font { const HEIGHT: usize; const WIDTH: usize; + const UNDERLINE: usize; + const STRIKETHROUGH: usize; + + /// Whether the character `byte` is visible at `(x, y)`. fn get(byte: u8, x: usize, y: usize) -> bool; } diff --git a/kernel/src/arch/aarch64/driver/console/mod.rs b/kernel/src/arch/aarch64/driver/console/mod.rs index 89d91354..90fc38ac 100644 --- a/kernel/src/arch/aarch64/driver/console/mod.rs +++ b/kernel/src/arch/aarch64/driver/console/mod.rs @@ -1,9 +1,11 @@ //! Framebuffer console display driver for ARM64 mod color; +mod escape_parser; mod fonts; -use self::color::{ConsoleColor, ConsoleColor::*, FramebufferColor}; +use self::color::FramebufferColor; +use self::escape_parser::{CharacterAttribute, EscapeParser}; use self::fonts::{Font, Font8x16}; use super::fb::{ColorDepth::*, FramebufferInfo, FRAME_BUFFER}; @@ -14,28 +16,18 @@ use lazy_static::lazy_static; use log::*; use spin::Mutex; -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] -struct ColorPair { - foreground: C, - background: C, -} - #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] pub struct ConsoleChar { ascii_char: u8, - color: ColorPair, + attr: CharacterAttribute, } impl Default for ConsoleChar { fn default() -> Self { ConsoleChar { - ascii_char: b' ', - color: ColorPair { - foreground: Black, - background: Black, - }, + ascii_char: 0, + attr: CharacterAttribute::default(), } } } @@ -60,24 +52,40 @@ impl ConsoleBuffer { /// Write one character at `(row, col)`. fn write(&mut self, row: usize, col: usize, ch: ConsoleChar) { + if self.buf[row][col] == ch { + return; + } self.buf[row][col] = ch; let off_x = col * F::WIDTH; let off_y = row * F::HEIGHT; if let Some(fb) = FRAME_BUFFER.lock().as_mut() { - let (foreground, background) = match fb.color_depth { + let (mut foreground, mut background) = match fb.color_depth { ColorDepth16 => ( - ch.color.foreground.pack16() as u32, - ch.color.background.pack16() as u32, + ch.attr.foreground.pack16() as u32, + ch.attr.background.pack16() as u32, ), ColorDepth32 => ( - ch.color.foreground.pack32(), - ch.color.background.pack32(), + ch.attr.foreground.pack32(), + ch.attr.background.pack32(), ), }; + if ch.attr.reverse { + core::mem::swap(&mut foreground, &mut background); + } + let underline_y = if ch.attr.underline { + F::UNDERLINE + } else { + F::HEIGHT + }; + let strikethrough_y = if ch.attr.strikethrough { + F::STRIKETHROUGH + } else { + F::HEIGHT + }; for y in 0..F::HEIGHT { for x in 0..F::WIDTH { - let pixel = if F::get(ch.ascii_char, x, y) { + let pixel = if y == underline_y || y == strikethrough_y || F::get(ch.ascii_char, x, y) { foreground } else { background @@ -98,18 +106,11 @@ impl ConsoleBuffer { fn new_line(&mut self) { for i in 1..self.num_row { for j in 0..self.num_col { - if self.buf[i - 1][j] != self.buf[i][j] { - self.write(i - 1, j, self.buf[i][j]); - } + self.write(i - 1, j, self.buf[i][j]); } } for j in 0..self.num_col { - self.buf[self.num_row - 1][j] = ConsoleChar::default(); - } - - if let Some(fb) = FRAME_BUFFER.lock().as_mut() { - let rowbytes = F::HEIGHT * fb.fb_info.pitch as usize; - fb.fill(rowbytes * (self.num_row - 1), rowbytes, 0); + self.write(self.num_row - 1, j, ConsoleChar::default()); } } @@ -128,12 +129,12 @@ impl ConsoleBuffer { /// Console structure pub struct Console { - /// current color - color: ColorPair, /// cursor row row: usize, /// cursor column col: usize, + /// escape sequence parser + parser: EscapeParser, /// character buffer buf: ConsoleBuffer, } @@ -143,17 +144,21 @@ impl Console { let num_row = fb.yres as usize / F::HEIGHT; let num_col = fb.xres as usize / F::WIDTH; Console { - color: ColorPair { - foreground: BrightWhite, - background: Black, - }, row: 0, col: 0, + parser: EscapeParser::new(), buf: ConsoleBuffer::new(num_row, num_col), } } fn new_line(&mut self) { + let attr_blank = ConsoleChar { + ascii_char: 0, + attr: self.parser.char_attribute(), + }; + for j in self.col..self.buf.num_col { + self.buf.write(self.row, j, attr_blank); + } self.col = 0; if self.row < self.buf.num_row - 1 { self.row += 1; @@ -162,8 +167,12 @@ impl Console { } } - // TODO: pasre color with ANSI escape sequences fn write_byte(&mut self, byte: u8) { + if self.parser.is_parsing() { + if self.parser.parse(byte) { + return; + } + } match byte { b'\x7f' => { if self.col > 0 { @@ -177,6 +186,7 @@ impl Console { } b'\n' => self.new_line(), b'\r' => self.col = 0, + b'\x1b' => self.parser.start_parse(), byte => { if self.col >= self.buf.num_col { self.new_line(); @@ -184,7 +194,7 @@ impl Console { let ch = ConsoleChar { ascii_char: byte, - color: self.color, + attr: self.parser.char_attribute(), }; self.buf.write(self.row, self.col, ch); self.col += 1; @@ -193,12 +203,9 @@ impl Console { } pub fn clear(&mut self) { - self.color = ColorPair { - foreground: BrightWhite, - background: Black, - }; self.row = 0; self.col = 0; + self.parser = EscapeParser::new(); self.buf.clear(); } }