341 lines
10 KiB
Rust
341 lines
10 KiB
Rust
use core::fmt;
|
|
use lazy_static::lazy_static;
|
|
use spin::Mutex;
|
|
use volatile::Volatile;
|
|
|
|
use alloc::borrow::ToOwned;
|
|
use alloc::vec;
|
|
use alloc::vec::Vec;
|
|
use crate::std::io::Screen;
|
|
|
|
#[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(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(transparent)]
|
|
pub struct ColorCode(u8);
|
|
|
|
impl ColorCode {
|
|
pub fn new(foreground: Color, background: Color) -> ColorCode {
|
|
ColorCode((background as u8) << 5 | (foreground as u8))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub struct ScreenChar {
|
|
pub character: u8,
|
|
pub colour: ColorCode,
|
|
}
|
|
|
|
impl ScreenChar {
|
|
pub fn null() -> ScreenChar {
|
|
ScreenChar {
|
|
character: 0u8,
|
|
colour: ColorCode::new(Color::White, Color::Black),
|
|
}
|
|
}
|
|
pub fn white(character: u8) -> ScreenChar {
|
|
ScreenChar {
|
|
character,
|
|
colour: ColorCode::new(Color::White, Color::Black),
|
|
}
|
|
}
|
|
pub fn new(character: u8, colour: ColorCode) -> ScreenChar {
|
|
ScreenChar {
|
|
character,
|
|
colour,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub const BUFFER_HEIGHT: usize = 25;
|
|
pub const BUFFER_WIDTH: usize = 80;
|
|
|
|
#[repr(transparent)]
|
|
struct VGAOutput {
|
|
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
|
}
|
|
|
|
pub struct Renderer {
|
|
col_pos: usize,
|
|
screen_ref: &'static mut VGAOutput, // this should not be accessed unless the screen is rendering a new frame
|
|
term_buffer: Vec<[ScreenChar; BUFFER_WIDTH]>, // this is the standard terminal output view
|
|
app_buffer: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], // this is where applications render their frames to
|
|
application_mode: bool, // if false: term mode; if true: app mode
|
|
temp_colour: Option<ColorCode>,
|
|
}
|
|
|
|
lazy_static! {
|
|
pub static ref RENDERER: Mutex<Renderer> = Mutex::new(Renderer {
|
|
col_pos: 0,
|
|
screen_ref: unsafe { &mut *(0xb8000 as *mut VGAOutput) },
|
|
term_buffer: vec![[ScreenChar::null(); BUFFER_WIDTH]; BUFFER_HEIGHT],
|
|
app_buffer: [[ScreenChar::null(); BUFFER_WIDTH]; BUFFER_HEIGHT],
|
|
application_mode: false,
|
|
temp_colour: None,
|
|
});
|
|
}
|
|
|
|
impl Renderer {
|
|
// EXTERNAL API : for use by standard library and other parts of the kernel
|
|
pub fn render_frame(&mut self, frame: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT]) { // renders the given frame to the app buffer
|
|
let mut processed_frame: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT] = [[ScreenChar::null(); BUFFER_WIDTH]; BUFFER_HEIGHT];
|
|
|
|
for (i, row) in frame.iter().enumerate() {
|
|
for (j, col) in row.iter().enumerate() {
|
|
processed_frame[i][j] = match self.special_char(col.character as char) {
|
|
Some(c) => ScreenChar::new(c as u8, col.colour),
|
|
None => *col,
|
|
};
|
|
}
|
|
}
|
|
|
|
self.app_buffer = processed_frame;
|
|
self.internal_render();
|
|
}
|
|
|
|
pub fn terminal_mode_force(&mut self) { // THIS SHOULD ONLY BE USED WHEN THE KERNEL PANICS
|
|
// TODO: find a way to make this function kernel only
|
|
self.application_mode = false;
|
|
self.internal_render();
|
|
}
|
|
|
|
pub fn application_mode(&mut self) -> Result<(), ()> {
|
|
if self.application_mode {
|
|
return Err(());
|
|
} else {
|
|
self.application_mode = true;
|
|
self.internal_render();
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn terminal_mode(&mut self) -> Result<(), ()> {
|
|
if !self.application_mode {
|
|
return Err(());
|
|
} else {
|
|
self.application_mode = false;
|
|
self.internal_render();
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn mode_is_app(&self) -> bool {
|
|
self.application_mode
|
|
}
|
|
|
|
pub fn write_char(&mut self, ch: u8, col: Option<ColorCode>) { // default colour if no colour is selected for character
|
|
if self.application_mode { return; };
|
|
self.write_byte(ch, col);
|
|
self.internal_render();
|
|
}
|
|
|
|
pub fn write_string(&mut self, string: &str, col: Option<ColorCode>) {
|
|
if self.application_mode { return; };
|
|
for ch in string.chars() {
|
|
match self.special_char(ch) {
|
|
Some(c) => self.write_byte(c, col),
|
|
None => match ch as u8 {
|
|
0x20..=0xff | b'\n' => self.write_byte(ch as u8, col),
|
|
_ => self.write_byte(0xfe, col),
|
|
}
|
|
}
|
|
}
|
|
self.internal_render();
|
|
}
|
|
|
|
pub fn backspace(&mut self) -> Result<(), ()> {
|
|
if self.application_mode { return Ok(()); };
|
|
|
|
loop {
|
|
if self.internal_backspace()? {
|
|
break;
|
|
}
|
|
}
|
|
|
|
self.internal_render();
|
|
Ok(())
|
|
}
|
|
|
|
pub fn clear(&mut self) { // clears the screen and all scroll-back
|
|
if self.application_mode { return; };
|
|
|
|
self.term_buffer = vec![[ScreenChar::null(); BUFFER_WIDTH]; BUFFER_HEIGHT];
|
|
self.internal_render();
|
|
}
|
|
|
|
pub fn set_colour(&mut self, cols: ColorCode) {
|
|
self.temp_colour = Some(cols);
|
|
}
|
|
pub fn reset_colour(&mut self) {
|
|
self.temp_colour = None;
|
|
}
|
|
|
|
// INTERNAL API ONLY
|
|
|
|
fn set_cursor_position(&mut self, row: usize, col: usize) {
|
|
use x86_64::instructions::port::Port;
|
|
let cursor_position: u16 = (row as u16) * 80 + (col as u16);
|
|
|
|
unsafe {// Write the high byte of the cursor position to register 14
|
|
let mut control_port = Port::<u8>::new(0x3D4);
|
|
control_port.write(14);
|
|
// Write the high byte of the cursor position to register 15
|
|
let mut data_port = Port::<u8>::new(0x3D5);
|
|
data_port.write((cursor_position >> 8) as u8);
|
|
// Write the low byte of the cursor position to register 14
|
|
control_port.write(15);
|
|
// Write the low byte of the cursor position to register 15
|
|
data_port.write((cursor_position & 0xFF) as u8);
|
|
}
|
|
}
|
|
|
|
fn internal_backspace(&mut self) -> Result<bool, ()> {
|
|
let mut should_break = false;
|
|
|
|
if self.col_pos == 0 {
|
|
self.internal_lastline();
|
|
}
|
|
self.col_pos -= 1;
|
|
let col = self.col_pos;
|
|
|
|
let buff_len = self.term_buffer.len();
|
|
|
|
if self.term_buffer[buff_len - 1][col].character != 0 {
|
|
should_break = true
|
|
}
|
|
|
|
self.term_buffer[buff_len - 1][col] = ScreenChar::null();
|
|
Ok(should_break)
|
|
}
|
|
|
|
fn internal_newline(&mut self) { // moves all content one line up the screen and creates new line
|
|
if self.application_mode { return; }; // only in terminal mode
|
|
self.term_buffer.push([ScreenChar::null(); BUFFER_WIDTH]);
|
|
self.col_pos = 0;
|
|
}
|
|
|
|
fn internal_lastline(&mut self) { // goes back to previous line and shifts all lines down
|
|
if self.application_mode { return; };
|
|
self.term_buffer.pop();
|
|
self.col_pos = BUFFER_WIDTH;
|
|
}
|
|
|
|
fn write_screen_char(&mut self, ch: ScreenChar) { // TODO: optimise so that screen is not fully re-rendered for every string written.
|
|
match ch.character as u8 {
|
|
b'\n' => self.internal_newline(),
|
|
_ => {
|
|
if self.col_pos >= BUFFER_WIDTH {
|
|
self.internal_newline();
|
|
}
|
|
let row = BUFFER_HEIGHT - 1;
|
|
let col = self.col_pos;
|
|
|
|
let buff_len = self.term_buffer.len();
|
|
self.term_buffer[buff_len - 1][col] = ch;
|
|
self.col_pos += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn write_byte(&mut self, byte: u8, col: Option<ColorCode>) { // default colour if no colour is selected for character
|
|
self.write_screen_char(ScreenChar {
|
|
character: byte,
|
|
colour: match col {
|
|
Some(c) => c,
|
|
None => match self.temp_colour {
|
|
Some(c) => c,
|
|
None => ColorCode::new(Color::White, Color::Black),
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
fn internal_render(&mut self) { // private function that can only be used from within this struct.
|
|
if self.application_mode {
|
|
for (i, row) in self.app_buffer.iter().enumerate() {
|
|
for (j, col) in row.iter().enumerate() {
|
|
self.screen_ref.chars[i][j].write(*col);
|
|
}
|
|
}
|
|
} else {
|
|
let buff_len = self.term_buffer.len();
|
|
for (i, row) in self.term_buffer[buff_len - BUFFER_HEIGHT..buff_len].iter().enumerate() {
|
|
for (j, col) in row.iter().enumerate() {
|
|
self.screen_ref.chars[i][j].write(*col);
|
|
}
|
|
}
|
|
}
|
|
self.set_cursor_position(BUFFER_HEIGHT - 1, self.col_pos);
|
|
}
|
|
|
|
fn special_char(&self, ch: char) -> Option<u8> {
|
|
let res: u8 = match ch {
|
|
'│' => 179,
|
|
'─' => 196,
|
|
'┴' => 193,
|
|
'┤' => 180,
|
|
'═' => 205,
|
|
'║' => 186,
|
|
'╗' => 187,
|
|
'╝' => 188,
|
|
'╚' => 200,
|
|
'╔' => 201,
|
|
'»' => 175,
|
|
'┐' => 191,
|
|
'└' => 192,
|
|
'┘' => 217,
|
|
'┌' => 218,
|
|
'┼' => 197,
|
|
'░' => 176,
|
|
'▓' => 178,
|
|
'«' => 174,
|
|
_ => {
|
|
return None;
|
|
}
|
|
};
|
|
Some(res)
|
|
}
|
|
}
|
|
|
|
impl fmt::Write for Renderer {
|
|
fn write_str(&mut self, string: &str) -> fmt::Result {
|
|
self.write_string(string, None);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn write(args: fmt::Arguments, cols: (Color, Color)) {
|
|
use core::fmt::Write;
|
|
use x86_64::instructions::interrupts;
|
|
|
|
let colour_code = ColorCode::new(cols.0, cols.1);
|
|
|
|
interrupts::without_interrupts(|| {
|
|
let mut writer = RENDERER.lock();
|
|
|
|
writer.set_colour(colour_code);
|
|
writer.write_fmt(args).unwrap();
|
|
writer.reset_colour();
|
|
})
|
|
} |