diff --git a/src/term/escape/decode.rs b/src/term/escape/decode.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ce4e1ba725f8170d112bc2ab94a07e46b25deb6 --- /dev/null +++ b/src/term/escape/decode.rs @@ -0,0 +1,108 @@ +use crate::video::framebuffer::Color; + +/// foreground color can be changed like this: "\x1B[38;2;<r>;<g>;<b>m" +/// background color can be changed like this: "\x1B[48;2;<r>;<g>;<b>m" +/// +/// THESE ARE NON STANDARD ESCAPE SEQUENCES +pub struct EscapeDecoder { + buf: [u8; LONGEST_ESCAPE], + len: u8, +} + +pub enum DecodedPart { + Byte(u8), + + /// Null terminated + Bytes([u8; LONGEST_ESCAPE]), + + FgColor(Color), + BgColor(Color), + Reset, + + None, +} + +// + +impl EscapeDecoder { + pub const fn new() -> Self { + Self { + buf: [0; LONGEST_ESCAPE], + len: 0, + } + } + + pub fn next(&mut self, byte: u8) -> DecodedPart { + match (self.len, byte) { + (0, b'\x1B') => { + self.len += 1; + self.buf[0 as usize] = byte; + DecodedPart::None + } + (0, _) => DecodedPart::Byte(byte), + (1, b'[') => { + self.len += 1; + self.buf[1 as usize] = byte; + DecodedPart::None + } + (i, b'm') => { + self.len += 1; + self.buf[i as usize] = byte; + + // crate::qemu::_print(format_args_nl!( + // "seq part: {:?}", + // core::str::from_utf8(&self.buf[..self.len as usize]) + // )); + + let result = match self.buf[..self.len as usize] { + [b'\x1B', b'[', b'3', b'8', b';', b'2', b';', ref rgb @ .., b'm'] => { + Self::parse_rgb_part(rgb).map(DecodedPart::FgColor) + } + [b'\x1B', b'[', b'4', b'8', b';', b'2', b';', ref rgb @ .., b'm'] => { + Self::parse_rgb_part(rgb).map(DecodedPart::BgColor) + } + [b'\x1B', b'[', b'm'] => Some(DecodedPart::Reset), + _ => None, + }; + + if let Some(result) = result { + self.clear(); + result + } else { + self.clear() + } + } + (i @ LONGEST_ESCAPE_PREV_U8.., _) => { + self.len += 1; + self.buf[i as usize] = byte; + self.clear() + } + (i, _) => { + self.len += 1; + self.buf[i as usize] = byte; + DecodedPart::None + } + } + } + + pub fn clear(&mut self) -> DecodedPart { + self.len = 0; + DecodedPart::Bytes(core::mem::take(&mut self.buf)) + } + + fn parse_rgb_part(rgb: &[u8]) -> Option<Color> { + let mut iter = rgb.split(|c| *c == b';'); + let r = core::str::from_utf8(iter.next()?).ok()?.parse().ok()?; + let g = core::str::from_utf8(iter.next()?).ok()?.parse().ok()?; + let b = core::str::from_utf8(iter.next()?).ok()?.parse().ok()?; + Some(Color::new(r, g, b)) + } +} + +// + +// longest supported: "\x1B[48;2;255;255;255m" +const LONGEST_ESCAPE: usize = "\x1B[48;2;255;255;255m".len(); +const LONGEST_ESCAPE_PREV: usize = LONGEST_ESCAPE - 1; +const LONGEST_ESCAPE_U8: u8 = LONGEST_ESCAPE as u8; +const LONGEST_ESCAPE_PREV_U8: u8 = LONGEST_ESCAPE as u8 - 1; diff --git a/src/term/escape/encode.rs b/src/term/escape/encode.rs new file mode 100644 index 0000000000000000000000000000000000000000..23af24261bd95c788950161d491b21cd69f44ded --- /dev/null +++ b/src/term/escape/encode.rs @@ -0,0 +1,62 @@ +use core::fmt; + +// + +pub trait EscapeEncoder { + fn with_escape_code<'a>(&'a self, code: &'a str) -> EncodedPart<'a, Self> { + EncodedPart { code, data: self } + } + + fn red(&self) -> EncodedPart<Self> { + self.with_escape_code("\x1B[38;2;255;0;0m") + } + + fn green(&self) -> EncodedPart<Self> { + self.with_escape_code("\x1B[38;2;0;255;0m") + } + + fn blue(&self) -> EncodedPart<Self> { + self.with_escape_code("\x1B[38;2;0;0;255m") + } + + fn cyan(&self) -> EncodedPart<Self> { + self.with_escape_code("\x1B[38;2;0;255;255m") + } + + fn magenta(&self) -> EncodedPart<Self> { + self.with_escape_code("\x1B[38;2;255;0;255m") + } + + fn yellow(&self) -> EncodedPart<Self> { + self.with_escape_code("\x1B[38;2;255;255;0m") + } +} + +pub struct EncodedPart<'a, T: ?Sized> { + code: &'a str, + data: &'a T, +} + +// + +impl EscapeEncoder for &str {} + +impl<'a, T> fmt::Display for EncodedPart<'a, T> +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}\x1B[m", self.code, self.data) + } +} + +impl<'a, T> fmt::Debug for EncodedPart<'a, T> +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.code)?; + self.data.fmt(f)?; + write!(f, "\x1B[m") + } +} diff --git a/src/term/escape/mod.rs b/src/term/escape/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c80d24e06bb2a3618069e70a80a2295a7f1127a --- /dev/null +++ b/src/term/escape/mod.rs @@ -0,0 +1,2 @@ +pub mod decode; +pub mod encode; diff --git a/src/term/mod.rs b/src/term/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..46e0349586b866e827594e0b15d82c586038882d --- /dev/null +++ b/src/term/mod.rs @@ -0,0 +1 @@ +pub mod escape; diff --git a/src/video/font.bmp b/src/video/font.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d80f083c72b977d9d74b6382872c77f1c010d486 Binary files /dev/null and b/src/video/font.bmp differ diff --git a/src/video/logger.rs b/src/video/logger.rs new file mode 100644 index 0000000000000000000000000000000000000000..e1ef1a8300e80dba6404c463c86c8abff76518c1 --- /dev/null +++ b/src/video/logger.rs @@ -0,0 +1,137 @@ +use crate::term::escape::decode::{DecodedPart, EscapeDecoder}; + +use super::{ + font::FONT, + framebuffer::{get_fbo, Color, Framebuffer, FBO}, +}; +use core::fmt::{self, Arguments, Write}; +use spin::{Lazy, Mutex, MutexGuard, Once}; + +// + +pub fn _print(args: Arguments) { + _ = WRITER.lock().write_fmt(args) +} + +// + +static WRITER: Mutex<Writer> = Mutex::new(Writer::new()); + +// + +struct Writer { + cursor: [u16; 2], + fg_color: Color, + bg_color: Color, + + escapes: EscapeDecoder, +} + +// + +impl Writer { + pub fn write_bytes(&mut self, bytes: &[u8]) { + for byte in bytes { + self.write_byte(*byte) + } + } + + pub fn write_byte(&mut self, byte: u8) { + match self.escapes.next(byte) { + DecodedPart::Byte(b'\n') => { + if let Some(mut fbo) = get_fbo() { + self.new_line(1, &mut fbo) + } + } + DecodedPart::Byte(b'\t') => { + self.cursor[0] = (self.cursor[0] / 4 + 1) * 4; + } + + DecodedPart::Byte(byte) => self.write_byte_raw(byte), + DecodedPart::Bytes(bytes) => bytes + .into_iter() + .take_while(|b| *b != 0) + .for_each(|byte| self.write_byte_raw(byte)), + + DecodedPart::FgColor(color) => self.fg_color = color, + DecodedPart::BgColor(color) => self.bg_color = color, + DecodedPart::Reset => { + self.fg_color = Self::FG_COLOR; + self.bg_color = Self::BG_COLOR; + } + + DecodedPart::None => {} + } + } + + pub fn write_byte_raw(&mut self, byte: u8) { + if let Some(mut fbo) = get_fbo() { + let size = Self::size(&mut fbo); + if size[0] == 0 || size[1] == 0 { + return; + } + + self._write_byte_raw(byte, &mut fbo); + } + } + + const FG_COLOR: Color = Color::from_hex("#bbbbbb"); + const BG_COLOR: Color = Color::from_hex("#000000"); + + const fn new() -> Self { + Self { + cursor: [0; 2], + fg_color: Self::FG_COLOR, + bg_color: Self::BG_COLOR, + + escapes: EscapeDecoder::new(), + } + } + + fn _write_byte_raw(&mut self, byte: u8, fbo: &mut MutexGuard<Framebuffer>) { + let (map, is_double) = FONT[byte as usize]; + + // insert a new line if the next character would be off screen + if self.cursor[0] + if is_double { 1 } else { 0 } >= Self::size(fbo)[0] { + self.new_line(8, fbo); + } + + let (x, y) = (self.cursor[0] as usize * 8, self.cursor[1] as usize * 16); + self.cursor[0] += if is_double { 2 } else { 1 }; + + for (yd, row) in map.into_iter().enumerate() { + for xd in 0..if is_double { 16 } else { 8 } { + fbo.set( + x + xd, + y + yd, + if (row & 1 << xd) != 0 { + self.fg_color + } else { + self.bg_color + }, + ); + } + } + } + + fn new_line(&mut self, count: u16, fbo: &mut MutexGuard<Framebuffer>) { + self.cursor[0] = 0; + self.cursor[1] += 1; + if self.cursor[1] >= Self::size(fbo)[1] { + let scroll_count = count.min(self.cursor[1]); + self.cursor[1] -= scroll_count; + fbo.scroll(16 * scroll_count as usize); + } + } + + fn size(fbo: &mut MutexGuard<Framebuffer>) -> [u16; 2] { + [(fbo.width / 16) as _, (fbo.height / 16) as _] + } +} + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_bytes(s.as_bytes()); + Ok(()) + } +}