use core::error::Error; use core::fmt; use core::fmt::{Display, Formatter, Write}; use crate::drivers::tty::vga::VGATTYDriver; use crate::ttys; pub fn _print(args: fmt::Arguments) { ttys.lock().write_fmt(args).unwrap(); ttys.lock().update().unwrap(); } pub struct TTY<const C: usize> { pub ttys: [VirtualTTY; C], pub selected: usize, driver: VGATTYDriver } impl<const C: usize> TTY<C> { pub fn new() -> TTY<C> { Self { ttys: [ VirtualTTY::new(); C ], selected: 0, driver: VGATTYDriver {} } } pub fn select(&mut self, new: usize) { self.selected = new; } pub fn update(&self) -> Result<(), TTYDriverError> { let selected_tty = &self.ttys[self.selected]; for (row_num, row) in selected_tty.screen_buffer.iter().enumerate() { for (col_num, col) in row.iter().enumerate() { self.driver.set_char(row_num, col_num, *col)?; } } Ok(()) } pub fn handle(&self) -> &VirtualTTY { &self.ttys[self.selected] } pub fn handle_mut(&mut self) -> &mut VirtualTTY { &mut self.ttys[self.selected] } pub fn select_and_update(&mut self, new: usize) -> Result<(), TTYDriverError> { self.select(new); self.update() } } #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum Color { Black = 0, Blue = 1, Green = 2, Cyan = 3, Red = 4, Magenta = 5, Brown = 6, LightGray = 7, DarkGray = 8, LightBlue = 9, LightGreen = 10, LightCyan = 11, LightRed = 12, Pink = 13, Yellow = 14, White = 15, } #[derive(Copy, Clone)] pub struct TTYCharacter { pub format: u8, pub char: char } impl TTYCharacter { pub fn blank() -> Self { Self { format: 0u8, char: ' ' } } pub fn from(char: char) -> Self { Self { format: 0x0fu8, char }} pub fn new(char: char, fg: Color, bg: Color) -> Self { Self { format: (bg as u8) << 4 | fg as u8, char } } } pub const ROWS_ON_SCREEN: usize = 25; pub const COLS: usize = 80; pub const ROWS_IN_TTY: usize = 80; #[derive(Copy, Clone)] pub struct VirtualTTY { full_buffer: VirtualTTYBuffer<ROWS_IN_TTY, COLS>, full_buffer_copy: VirtualTTYBuffer<ROWS_IN_TTY, COLS>, screen_buffer: VirtualTTYBuffer<ROWS_ON_SCREEN, COLS>, line_offset: usize, cursor_col: usize, cursor_row: usize, current_color_fg: Color, current_color_bg: Color } impl Default for VirtualTTY { fn default() -> Self { Self { full_buffer: [[TTYCharacter::blank(); COLS]; ROWS_IN_TTY], full_buffer_copy: [[TTYCharacter::blank(); COLS]; ROWS_IN_TTY], screen_buffer: [[TTYCharacter::blank(); COLS]; ROWS_ON_SCREEN], line_offset: ROWS_IN_TTY - ROWS_ON_SCREEN, // assume we are scrolled all the way down cursor_row: ROWS_IN_TTY - ROWS_ON_SCREEN, current_color_fg: Color::White, cursor_col: 0, current_color_bg: Color::Black, } } } impl VirtualTTY { pub fn new() -> Self { Self::default() } pub fn set_color(&mut self, fg: Color, bg: Color) { self.current_color_bg = bg; self.current_color_fg = fg; } pub fn setcur(&mut self, cursor_x: usize, cursor_y: usize) { self.cursor_row = cursor_y; self.cursor_col = cursor_x; } pub fn putchar(&mut self, character: char) { match character { '\n' => { self.print_newline(); self.cursor_col = 0; self.cursor_row += 1; }, _ => { if self.cursor_col > COLS { self.print_newline(); self.cursor_col = 0; self.cursor_row += 1; } self._putchar(character); self.cursor_col += 1; } } } pub fn putstr(&mut self, str: &str) { for char in str.chars() { self.putchar(char); } } fn _putchar(&mut self, character: char) { self.full_buffer[self.cursor_row][self.cursor_col] = TTYCharacter::new(character, self.current_color_fg, self.current_color_bg); self.update_screenbuf() } // shifts everything up one fn print_newline(&mut self) { for (line_no, line) in self.full_buffer.iter().skip(1).enumerate() { self.full_buffer_copy[line_no] = *line; } self.full_buffer = self.full_buffer_copy; } pub fn set_screen_offset(&mut self, new_offset: usize) -> Result<(), ()> { if new_offset > (ROWS_IN_TTY - ROWS_ON_SCREEN) { return Err(()) } self.line_offset = new_offset; self.update_screenbuf(); Ok(()) } fn update_screenbuf(&mut self) { for (line_no, line) in self.full_buffer.iter().skip(self.line_offset).enumerate() { self.screen_buffer[line_no] = *line; } } } type VirtualTTYBuffer<const R: usize, const C: usize> = [[TTYCharacter; C]; R]; #[derive(Debug)] pub enum TTYDriverError { RowOutOfBounds { got: usize }, ColOutOfBounds { got: usize }, CannotScrollTooFar } impl Display for TTYDriverError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { Self::RowOutOfBounds { got } => write!(f, "Character write out of bounds: row {} too high", got), Self::ColOutOfBounds { got } => write!(f, "Character write out of bounds: col {} too high", got), Self::CannotScrollTooFar => write!(f, "Cannot scroll outside line buffer") } } } impl Error for TTYDriverError {} pub trait TTYDriver { fn set_char(&self, row: usize, col: usize, char: TTYCharacter) -> Result<(), TTYDriverError>; } impl<const C: usize> Write for TTY<C> { fn write_str(&mut self, s: &str) -> fmt::Result { self.handle_mut().putstr(s); Ok(()) } }