diff --git a/Cargo.lock b/Cargo.lock index 2096c77..a9fa2ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + [[package]] name = "base64" version = "0.12.3" @@ -178,6 +184,7 @@ dependencies = [ "spin", "uart_16550", "volatile", + "vte", "x86_64", ] @@ -211,6 +218,24 @@ dependencies = [ "cpuio", ] +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + [[package]] name = "raw-cpuid" version = "8.1.0" @@ -312,12 +337,45 @@ dependencies = [ "x86_64", ] +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "volatile" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af0edf5b4faacc31fc51159244d78d65ec580f021afcef7bd53c04aeabc7f29" +[[package]] +name = "vte" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d" +dependencies = [ + "arrayvec", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "x86_64" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index a00f660..3a369c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,4 @@ spin = "0.5.2" uart_16550 = "0.2.7" volatile = "0.2.6" x86_64 = "0.11.1" +vte = "0.8.0" diff --git a/dsk/ini/banner.txt b/dsk/ini/banner.txt index fba66c0..ba61858 100644 --- a/dsk/ini/banner.txt +++ b/dsk/ini/banner.txt @@ -3,16 +3,16 @@ (o o) +-------------------ooO--(_)--Ooo------------------+ | | - | .100 110. .1100. 111110. .1001. .01000. | - | 00'1001`11 .11 01. 00 `00 .10 00. 10' 11 | - | 01 00 10 10 00 001101' 01 00 `100. | - | 10 01 10 01 11 01`00 01 11 `100. | - | 00 01 01 `00 11' 10 `11. `00 11' 01 00 | - | 11 10 10 `1001' 00 01 `0110' `01101' | + | .100 110. .1100. 111110. .1001. .01000. | + | 00'1001`11 .11 01. 00 `00 .10 00. 10' 11 | + | 01 00 10 10 00 001101' 01 00 `100. | + | 10 01 10 01 11 01`00 01 11 `100. | + | 00 01 01 `00 11' 10 `11. `00 11' 01 00 | + | 11 10 10 `1001' 00 01 `0110' `01101' | | | - | MOROS: Obscure Rust Operating System | + | MOROS: Obscure Rust Operating System | | | - | (v0.3.1) | + | (v{x.x.x}) | | | +--------------------------------------------------+ diff --git a/dsk/ini/version.txt b/dsk/ini/version.txt new file mode 100644 index 0000000..99e4533 --- /dev/null +++ b/dsk/ini/version.txt @@ -0,0 +1 @@ +MOROS v{x.x.x} diff --git a/screenshot.png b/screenshot.png index 6d2f5b2..19bb795 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/src/kernel/console.rs b/src/kernel/console.rs index 59efdcb..d95bf03 100644 --- a/src/kernel/console.rs +++ b/src/kernel/console.rs @@ -1,16 +1,58 @@ use crate::{print, kernel}; use alloc::string::String; use lazy_static::lazy_static; -use pc_keyboard::{KeyCode, DecodedKey}; use spin::Mutex; use x86_64::instructions::interrupts; +pub fn color(name: &str) -> &str { + match name { + "Black" => "\x1b[30m", + "Red" => "\x1b[31m", + "Green" => "\x1b[32m", + "Brown" => "\x1b[33m", + "Blue" => "\x1b[34m", + "Magenta" => "\x1b[35m", + "Cyan" => "\x1b[36m", + "LightGray" => "\x1b[37m", + "DarkGray" => "\x1b[90m", + "LightRed" => "\x1b[91m", + "LightGreen" => "\x1b[92m", + "Yellow" => "\x1b[93m", + "LightBlue" => "\x1b[94m", + "Pink" => "\x1b[95m", + "LightCyan" => "\x1b[96m", + "White" => "\x1b[97m", + "Reset" => "\x1b[0m", + _ => "", + } +} + lazy_static! { pub static ref STDIN: Mutex = Mutex::new(String::new()); pub static ref ECHO: Mutex = Mutex::new(true); pub static ref RAW: Mutex = Mutex::new(false); } +#[cfg(feature="vga")] +pub fn has_cursor() -> bool { + true +} + +#[cfg(feature="serial")] +pub fn has_cursor() -> bool { + false +} + +#[cfg(feature="vga")] +pub fn clear_row() { + kernel::vga::clear_row(); +} + +#[cfg(feature="serial")] +pub fn clear_row() { + print!("\x1b[2K\r"); +} + #[cfg(feature="vga")] #[macro_export] macro_rules! print { @@ -32,10 +74,9 @@ macro_rules! print { macro_rules! log { ($($arg:tt)*) => ({ let uptime = $crate::kernel::clock::uptime(); - let (fg, bg) = $crate::kernel::vga::color(); - $crate::kernel::vga::set_color($crate::kernel::vga::Color::Green, bg); - $crate::kernel::vga::print_fmt(format_args!("[{:.6}] ", uptime)); - $crate::kernel::vga::set_color(fg, bg); + let csi_color = $crate::kernel::console::color("LightGreen"); + let csi_reset = $crate::kernel::console::color("Reset"); + $crate::kernel::vga::print_fmt(format_args!("{}[{:.6}]{} ", csi_color, uptime, csi_reset)); $crate::kernel::vga::print_fmt(format_args!($($arg)*)); }); } @@ -45,7 +86,9 @@ macro_rules! log { macro_rules! log { ($($arg:tt)*) => ({ let uptime = $crate::kernel::clock::uptime(); - $crate::kernel::serial::print_fmt(format_args!("[{:.6}] ", uptime)); + let csi_color = $crate::kernel::console::color("LightGreen"); + let csi_reset = $crate::kernel::console::color("Reset"); + $crate::kernel::serial::print_fmt(format_args!("{}[{:.6}]{} ", csi_color, uptime, csi_reset)); $crate::kernel::serial::print_fmt(format_args!($($arg)*)); }); } @@ -78,18 +121,10 @@ pub fn is_raw_enabled() -> bool { *RAW.lock() } -pub fn key_handle(key: DecodedKey) { - let c = match key { - DecodedKey::Unicode(c) => c, - DecodedKey::RawKey(KeyCode::ArrowLeft) => '←', // U+2190 - DecodedKey::RawKey(KeyCode::ArrowUp) => '↑', // U+2191 - DecodedKey::RawKey(KeyCode::ArrowRight) => '→', // U+2192 - DecodedKey::RawKey(KeyCode::ArrowDown) => '↓', // U+2193 - DecodedKey::RawKey(_) => '\0' - }; +pub fn key_handle(key: char) { let mut stdin = STDIN.lock(); - if c == '\x08' && !is_raw_enabled() { + if key == '\x08' && !is_raw_enabled() { // Avoid printing more backspaces than chars inserted into STDIN. // Also, the VGA driver support only ASCII so unicode chars will // be displayed with one square for each codepoint. @@ -104,9 +139,9 @@ pub fn key_handle(key: DecodedKey) { } else { // TODO: Replace non-ascii chars by ascii square symbol to keep length // at 1 instead of being variable? - stdin.push(c); + stdin.push(key); if is_echo_enabled() { - print!("{}", c); + print!("{}", key); } } } diff --git a/src/kernel/cpu.rs b/src/kernel/cpu.rs index 500d1f7..ab09787 100644 --- a/src/kernel/cpu.rs +++ b/src/kernel/cpu.rs @@ -10,7 +10,7 @@ pub fn init() { if let Some(extended_function_info) = cpuid.get_extended_function_info() { if let Some(processor_brand_string) = extended_function_info.processor_brand_string() { - log!("CPU {}\n", processor_brand_string); + log!("CPU {}\n", processor_brand_string.trim()); } } diff --git a/src/kernel/keyboard.rs b/src/kernel/keyboard.rs index 4dfde97..aaa6d76 100644 --- a/src/kernel/keyboard.rs +++ b/src/kernel/keyboard.rs @@ -1,6 +1,6 @@ use crate::kernel; use lazy_static::lazy_static; -use pc_keyboard::{Keyboard, ScancodeSet1, HandleControl, layouts}; +use pc_keyboard::{Keyboard, ScancodeSet1, HandleControl, layouts, KeyCode, DecodedKey}; use spin::Mutex; use x86_64::instructions::port::Port; @@ -84,7 +84,15 @@ fn interrupt_handler() { let scancode = read_scancode(); if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { if let Some(key) = keyboard.process_keyevent(key_event) { - kernel::console::key_handle(key); + let c = match key { + DecodedKey::Unicode(c) => c, + DecodedKey::RawKey(KeyCode::ArrowLeft) => '←', // U+2190 + DecodedKey::RawKey(KeyCode::ArrowUp) => '↑', // U+2191 + DecodedKey::RawKey(KeyCode::ArrowRight) => '→', // U+2192 + DecodedKey::RawKey(KeyCode::ArrowDown) => '↓', // U+2193 + DecodedKey::RawKey(_) => '\0' + }; + kernel::console::key_handle(c); } } } diff --git a/src/kernel/serial.rs b/src/kernel/serial.rs index 47b6c1b..1269916 100644 --- a/src/kernel/serial.rs +++ b/src/kernel/serial.rs @@ -1,17 +1,58 @@ +use crate::kernel; use lazy_static::lazy_static; use spin::Mutex; use uart_16550::SerialPort; +use core::fmt; +use core::fmt::Write; lazy_static! { - pub static ref SERIAL1: Mutex = { - let mut serial_port = unsafe { SerialPort::new(0x3F8) }; - serial_port.init(); - Mutex::new(serial_port) - }; + pub static ref SERIAL: Mutex = Mutex::new(Serial::new(0x3F8)); +} + +pub struct Serial { + pub port: SerialPort +} + +impl Serial { + fn new(addr: u16) -> Self { + let mut port = unsafe { SerialPort::new(addr) }; + port.init(); + Self { port } + } + + fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + self.write_byte(byte) + } + } + + pub fn write_byte(&mut self, byte: u8) { + self.port.send(byte); + } +} + +impl fmt::Write for Serial { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } } #[doc(hidden)] -pub fn print_fmt(args: ::core::fmt::Arguments) { - use core::fmt::Write; - SERIAL1.lock().write_fmt(args).expect("Could not print to serial"); +pub fn print_fmt(args: fmt::Arguments) { + SERIAL.lock().write_fmt(args).expect("Could not print to serial"); +} + +pub fn init() { + kernel::idt::set_irq_handler(4, interrupt_handler); +} + +fn interrupt_handler() { + let b = SERIAL.lock().port.receive(); + let c = match b as char { + '\r' => '\n', + '\x7F' => '\x08', // Delete => Backspace + c => c, + }; + kernel::console::key_handle(c); } diff --git a/src/kernel/vga.rs b/src/kernel/vga.rs index 38f85f5..2ef3ec0 100644 --- a/src/kernel/vga.rs +++ b/src/kernel/vga.rs @@ -1,26 +1,28 @@ +use crate::kernel; use bit_field::BitField; use core::fmt; use core::fmt::Write; use lazy_static::lazy_static; use spin::Mutex; use volatile::Volatile; -use x86_64::instructions::port::Port; +use vte; use x86_64::instructions::interrupts; +use x86_64::instructions::port::Port; + +const FG: Color = Color::LightGray; +const BG: Color = Color::Black; +const UNPRINTABLE: u8 = 0xFE; // Unprintable characters will be replaced by a square lazy_static! { - /// A global `Writer` instance that can be used for printing to the VGA text buffer. - /// - /// Used by the `print!` and `println!` macros. pub static ref WRITER: Mutex = Mutex::new(Writer { cursor: [0; 2], - col_pos: 0, - row_pos: 0, - color_code: ColorCode::new(Color::LightGray, Color::Black), - buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + writer: [0; 2], + color_code: ColorCode::new(FG, BG), + buffer: unsafe { &mut *(0xB8000 as *mut Buffer) }, }); } -/// The standard color palette in VGA text mode. +/// The standard color palette in VGA text mode #[allow(dead_code)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] @@ -62,57 +64,67 @@ const COLORS: [Color; 16] = [ Color::White, ]; -/// A combination of a foreground and a background color. +fn color_from_ansi(code: u8) -> Color { + match code { + 30 => Color::Black, + 31 => Color::Red, + 32 => Color::Green, + 33 => Color::Brown, + 34 => Color::Blue, + 35 => Color::Magenta, + 36 => Color::Cyan, + 37 => Color::LightGray, + 90 => Color::DarkGray, + 91 => Color::LightRed, + 92 => Color::LightGreen, + 93 => Color::Yellow, + 94 => Color::LightBlue, + 95 => Color::Pink, + 96 => Color::LightCyan, + 97 => Color::White, + _ => FG, // Error + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(transparent)] struct ColorCode(u8); impl ColorCode { - /// Create a new `ColorCode` with the given foreground and background colors. fn new(foreground: Color, background: Color) -> ColorCode { ColorCode((background as u8) << 4 | (foreground as u8)) } } -/// A screen character in the VGA text buffer, consisting of an ASCII character and a `ColorCode`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] struct ScreenChar { - ascii_character: u8, + ascii_code: u8, color_code: ColorCode, } -/// The height of the text buffer (normally 25 lines). const BUFFER_HEIGHT: usize = 25; -/// The width of the text buffer (normally 80 columns). const BUFFER_WIDTH: usize = 80; -/// A structure representing the VGA text buffer. #[repr(transparent)] struct Buffer { chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], } -/// A writer type that allows writing ASCII bytes and strings to an underlying `Buffer`. -/// -/// Wraps lines at `BUFFER_WIDTH`. Supports newline characters and implements the -/// `core::fmt::Write` trait. pub struct Writer { - cursor: [usize; 2], - col_pos: usize, - row_pos: usize, + cursor: [usize; 2], // x, y + writer: [usize; 2], // x, y color_code: ColorCode, buffer: &'static mut Buffer, } impl Writer { pub fn writer_position(&self) -> (usize, usize) { - (self.col_pos, self.row_pos) + (self.writer[0], self.writer[1]) } pub fn set_writer_position(&mut self, x: usize, y: usize) { - self.col_pos = x; - self.row_pos = y; + self.writer = [x, y]; } pub fn cursor_position(&self) -> (usize, usize) { @@ -151,7 +163,7 @@ impl Writer { } } - /// Writes an ASCII byte to the buffer. + /// Writes an ASCII byte to the screen buffer pub fn write_byte(&mut self, byte: u8) { match byte { 0x0A => { // Newline @@ -160,63 +172,59 @@ impl Writer { 0x0D => { // Carriage Return }, 0x08 => { // Backspace - if self.col_pos > 0 { - self.col_pos -= 1; + if self.writer[0] > 0 { + self.writer[0] -= 1; let blank = ScreenChar { - ascii_character: b' ', + ascii_code: b' ', color_code: self.color_code, }; - let x = self.col_pos; - let y = self.row_pos; + let x = self.writer[0]; + let y = self.writer[1]; self.buffer.chars[y][x].write(blank); } }, byte => { - if self.col_pos >= BUFFER_WIDTH { + if self.writer[0] >= BUFFER_WIDTH { self.new_line(); } - let col = self.col_pos; - let row = self.row_pos; + let x = self.writer[0]; + let y = self.writer[1]; + let ascii_code = if is_printable(byte) { byte } else { UNPRINTABLE }; let color_code = self.color_code; - self.buffer.chars[row][col].write(ScreenChar { - ascii_character: byte, - color_code, - }); - self.col_pos += 1; + self.buffer.chars[y][x].write(ScreenChar { ascii_code, color_code }); + self.writer[0] += 1; } } } fn write_string(&mut self, s: &str) { + let mut state_machine = vte::Parser::new(); for byte in s.bytes() { - if is_printable(byte) { - self.write_byte(byte) // Printable chars, backspace, newline - } else { - self.write_byte(0xFE) // Square - } + state_machine.advance(self, byte); } } fn new_line(&mut self) { - if self.row_pos < BUFFER_HEIGHT - 1 { - self.row_pos += 1; + if self.writer[1] < BUFFER_HEIGHT - 1 { + self.writer[1] += 1; } else { - for row in 1..BUFFER_HEIGHT { - for col in 0..BUFFER_WIDTH { - let character = self.buffer.chars[row][col].read(); - self.buffer.chars[row - 1][col].write(character); + for y in 1..BUFFER_HEIGHT { + for x in 0..BUFFER_WIDTH { + let character = self.buffer.chars[y][x].read(); + self.buffer.chars[y - 1][x].write(character); } } self.clear_row(BUFFER_HEIGHT - 1); } - self.col_pos = 0; + self.writer[0] = 0; + } - /// Clears a row by overwriting it with blank characters. + /// Clears a row by overwriting it with blank characters fn clear_row(&mut self, y: usize) { let blank = ScreenChar { - ascii_character: b' ', + ascii_code: b' ', color_code: self.color_code, }; for x in 0..BUFFER_WIDTH { @@ -228,9 +236,8 @@ impl Writer { for y in 0..BUFFER_HEIGHT { self.clear_row(y); } - self.row_pos = 0; - self.col_pos = 0; - self.set_cursor_position(self.col_pos, self.row_pos); + self.set_writer_position(0, 0); + self.set_cursor_position(0, 0); } pub fn set_color(&mut self, foreground: Color, background: Color) { @@ -245,16 +252,72 @@ impl Writer { } } +/// See https://vt100.net/emu/dec_ansi_parser +impl vte::Perform for Writer { + fn print(&mut self, c: char) { + self.write_byte(c as u8); + } + + fn execute(&mut self, byte: u8) { + self.write_byte(byte); + kernel::serial::print_fmt(format_args!("[execute] {:02x}\n", byte)); + } + + fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + kernel::serial::print_fmt(format_args!("[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}\n", params, intermediates, ignore, c)); + } + + fn put(&mut self, byte: u8) { + kernel::serial::print_fmt(format_args!("[put] {:02x}\n", byte)); + } + + fn unhook(&mut self) { + kernel::serial::print_fmt(format_args!("[unhook]\n")); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + kernel::serial::print_fmt(format_args!("[osc_dispatch] params={:?} bell_terminated={}\n", params, bell_terminated)); + } + + fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) { + if c == 'm' { + let mut fg = FG; + let mut bg = BG; + for ¶m in params { + match param { + 0 => { + fg = FG; + bg = BG; + }, + 30..=37 | 90..=97 => { + fg = color_from_ansi(param as u8); + }, + 40..=47 | 100..=107 => { + bg = color_from_ansi((param as u8) - 10); + }, + _ => {} + } + } + self.set_color(fg, bg); + } + + kernel::serial::print_fmt(format_args!("[csi_dispatch] params={:?}, intermediates={:?}, ignore={:?}, char={:?}\n", params, intermediates, ignore, c)); + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + kernel::serial::print_fmt(format_args!("[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}\n", intermediates, ignore, byte)); + } +} + impl fmt::Write for Writer { fn write_str(&mut self, s: &str) -> fmt::Result { self.write_string(s); - self.set_cursor_position(self.col_pos, self.row_pos); + let (x, y) = self.writer_position(); + self.set_cursor_position(x, y); Ok(()) } } -/// Prints the given formatted string to the VGA text buffer -/// through the global `WRITER` instance. #[doc(hidden)] pub fn print_fmt(args: fmt::Arguments) { interrupts::without_interrupts(|| { diff --git a/src/lib.rs b/src/lib.rs index 008f71b..b6c4cd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ pub fn init(boot_info: &'static BootInfo) { kernel::time::init(); kernel::keyboard::init(); + kernel::serial::init(); kernel::mem::init(boot_info); kernel::cpu::init(); kernel::pci::init(); // Require MEM diff --git a/src/main.rs b/src/main.rs index aecc313..f475ed3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,6 @@ fn main(boot_info: &'static BootInfo) -> ! { print!("MFS is not mounted to '/'\n"); } print!("Running console in diskless mode\n"); - print!("\n"); user::shell::main(&["shell"]); } diff --git a/src/user/colors.rs b/src/user/colors.rs index 81bf307..dd2022d 100644 --- a/src/user/colors.rs +++ b/src/user/colors.rs @@ -1,28 +1,29 @@ -use crate::{print, kernel, user}; +use alloc::format; +use crate::{print, user}; pub fn main(_args: &[&str]) -> user::shell::ExitCode { - let colors = kernel::vga::colors(); - let (fg, bg) = kernel::vga::color(); + let csi_reset = "\x1b[0m"; - for i in 0..colors.len() { - let c = colors[i]; - kernel::vga::set_color(c, bg); - print!(" {:02} ", i); - if i == 7 || i == 15 { - kernel::vga::set_color(fg, bg); - print!("\n"); - } + for i in 30..38 { + let csi_color = format!("\x1b[{};40m", i); + print!(" {}{:3}{}", csi_color, i, csi_reset); } - - for i in 0..colors.len() { - let c = colors[i]; - kernel::vga::set_color(bg, c); - print!(" {:02} ", i); - if i == 7 || i == 15 { - kernel::vga::set_color(fg, bg); - print!("\n"); - } + print!("\n"); + for i in 90..98 { + let csi_color = format!("\x1b[{};40m", i); + print!(" {}{:3}{}", csi_color, i, csi_reset); } + print!("\n"); + for i in 40..48 { + let csi_color = format!("\x1b[30;{}m", i); + print!(" {}{:3}{}", csi_color, i, csi_reset); + } + print!("\n"); + for i in 100..108 { + let csi_color = format!("\x1b[30;{}m", i); + print!(" {}{:3}{}", csi_color, i, csi_reset); + } + print!("\n"); user::shell::ExitCode::CommandSuccessful } diff --git a/src/user/halt.rs b/src/user/halt.rs index 2c61917..5467b99 100644 --- a/src/user/halt.rs +++ b/src/user/halt.rs @@ -1,7 +1,9 @@ use crate::{print, kernel, user}; pub fn main(_args: &[&str]) -> user::shell::ExitCode { - print!("MOROS has reached its fate, the system is now halted.\n"); + let csi_color = kernel::console::color("Yellow"); + let csi_reset = kernel::console::color("Reset"); + print!("{}MOROS has reached its fate, the system is now halted.{}\n", csi_color, csi_reset); kernel::time::sleep(3.0); kernel::acpi::poweroff(); user::shell::ExitCode::CommandSuccessful diff --git a/src/user/help.rs b/src/user/help.rs index 6792812..d793edb 100644 --- a/src/user/help.rs +++ b/src/user/help.rs @@ -1,12 +1,16 @@ use crate::{print, user, kernel}; -use crate::kernel::vga::Color; pub fn main(_args: &[&str]) -> user::shell::ExitCode { - let (fg, bg) = kernel::vga::color(); + let csi_color = kernel::console::color("Yellow"); + let csi_reset = kernel::console::color("Reset"); + print!("{}Commands:{}\n", csi_color, csi_reset); + print!("\n"); + let cmds = [ ("c", "opy ", "Copy file from source to destination\n"), ("d", "elete ", "Delete file or empty directory\n"), ("e", "dit ", "Edit existing or new file\n"), + ("g", "oto ", "Go to directory\n"), ("h", "elp", "Display this text\n"), ("l", "ist ", "List entries in directory\n"), ("m", "ove ", "Move file from source to destination\n"), @@ -15,11 +19,16 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode { ("r", "ead ", "Read file to screen\n"), ("w", "rite ", "Write file or directory\n"), ]; - for (cmd, args, usage) in &cmds { - kernel::vga::set_color(Color::White, bg); - print!("{}", cmd); - kernel::vga::set_color(fg, bg); - print!("{:20}{}", args, usage); + for (alias, command, usage) in &cmds { + let csi_col1 = kernel::console::color("LightGreen"); + let csi_col2 = kernel::console::color("LightCyan"); + print!(" {}{}{}{:20}{}{}", csi_col1, alias, csi_col2, command, csi_reset, usage); } -user::shell::ExitCode::CommandSuccessful + print!("\n"); + + print!("{}Credits:{}\n", csi_color, csi_reset); + print!("\n"); + + print!("Made with <3 in 2019-2020 by Vincent Ollivier \n"); + user::shell::ExitCode::CommandSuccessful } diff --git a/src/user/install.rs b/src/user/install.rs index 8b1b68f..a0ab295 100644 --- a/src/user/install.rs +++ b/src/user/install.rs @@ -1,11 +1,9 @@ use crate::{print, kernel, user}; -use crate::kernel::vga::Color; pub fn main(_args: &[&str]) -> user::shell::ExitCode { - let (fg, bg) = kernel::vga::color(); - kernel::vga::set_color(Color::LightCyan, bg); - print!("Welcome to MOROS v{} installation program!\n", env!("CARGO_PKG_VERSION")); - kernel::vga::set_color(fg, bg); + let csi_color = kernel::console::color("Yellow"); + let csi_reset = kernel::console::color("Reset"); + print!("{}Welcome to MOROS v{} installation program!{}\n", csi_color, env!("CARGO_PKG_VERSION"), csi_reset); print!("\n"); print!("Proceed? [y/N] "); @@ -13,15 +11,11 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode { print!("\n"); if !kernel::fs::is_mounted() { - kernel::vga::set_color(Color::LightCyan, bg); - print!("Listing disks ...\n"); - kernel::vga::set_color(fg, bg); + print!("{}Listing disks ...{}\n", csi_color, csi_reset); user::disk::main(&["disk", "list"]); print!("\n"); - kernel::vga::set_color(Color::LightCyan, bg); - print!("Formatting disk ...\n"); - kernel::vga::set_color(fg, bg); + print!("{}Formatting disk ...{}\n", csi_color, csi_reset); print!("Enter path of disk to format: "); let pathname = kernel::console::get_line(); let res = user::disk::main(&["disk", "format", pathname.trim_end()]); @@ -31,9 +25,7 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode { print!("\n"); } - kernel::vga::set_color(Color::LightCyan, bg); - print!("Populating filesystem ...\n"); - kernel::vga::set_color(fg, bg); + print!("{}Populating filesystem...{}\n", csi_color, csi_reset); create_dir("/bin"); // Binaries create_dir("/dev"); // Devices create_dir("/ini"); // Initializers @@ -46,13 +38,12 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode { copy_file("/ini/boot.sh", include_str!("../../dsk/ini/boot.sh")); copy_file("/ini/banner.txt", include_str!("../../dsk/ini/banner.txt")); + copy_file("/ini/version.txt", include_str!("../../dsk/ini/version.txt")); copy_file("/tmp/alice.txt", include_str!("../../dsk/tmp/alice.txt")); if kernel::process::user().is_none() { print!("\n"); - kernel::vga::set_color(Color::LightCyan, bg); - print!("Creating user ...\n"); - kernel::vga::set_color(fg, bg); + print!("{}Creating user...{}\n", csi_color, csi_reset); let res = user::user::main(&["user", "create"]); if res == user::shell::ExitCode::CommandError { return res; @@ -60,9 +51,7 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode { } print!("\n"); - kernel::vga::set_color(Color::LightCyan, bg); - print!("Installation successful!\n"); - kernel::vga::set_color(fg, bg); + print!("{}Installation successful!{}\n", csi_color, csi_reset); print!("\n"); print!("Exit console or reboot to apply changes\n"); } @@ -81,6 +70,7 @@ fn copy_file(pathname: &str, contents: &str) { return; } if let Some(mut file) = kernel::fs::File::create(pathname) { + let contents = contents.replace("{x.x.x}", env!("CARGO_PKG_VERSION")); file.write(&contents.as_bytes()).unwrap(); print!("Copied '{}'\n", pathname); } diff --git a/src/user/read.rs b/src/user/read.rs index 3a5d17b..eb1884f 100644 --- a/src/user/read.rs +++ b/src/user/read.rs @@ -19,10 +19,6 @@ pub fn main(args: &[&str]) -> user::shell::ExitCode { "/dev/clk/uptime" => { user::uptime::main(&["uptime", "--raw"]) }, - "/sys/version" => { - print!("MOROS v{}\n", env!("CARGO_PKG_VERSION")); - user::shell::ExitCode::CommandSuccessful - }, _ => { if pathname.starts_with("/net/") { // Examples: diff --git a/src/user/shell.rs b/src/user/shell.rs index a450a16..6aa0662 100644 --- a/src/user/shell.rs +++ b/src/user/shell.rs @@ -1,10 +1,26 @@ use crate::{print, user, kernel}; -use crate::kernel::vga::Color; use alloc::format; use alloc::vec; use alloc::vec::Vec; use alloc::string::String; +// TODO: Scan /bin +const AUTOCOMPLETE_COMMANDS: [&str; 29] = [ + "base64", "clear", "colors", "copy", "delete", "dhcp", "disk", "edit", + "geotime", "goto", "halt", "help", "hex", "host", "http", "install", "ip", + "list", "move", "net", "print", "quit", "read", "route", "shell", "sleep", + "tcp", "user", "write", +]; + +// TODO: Scan /dev +const AUTOCOMPLETE_DEVICES: [&str; 5] = [ + "/dev/ata", + "/dev/clk", + "/dev/clk/uptime", + "/dev/clk/realtime", + "/dev/rtc", +]; + #[repr(u8)] #[derive(PartialEq)] pub enum ExitCode { @@ -143,37 +159,49 @@ impl Shell { '\x08' => { // Backspace self.update_history(); self.update_autocomplete(); - let cmd = self.cmd.clone(); - if cmd.len() > 0 && x > self.prompt.len() { - let (before_cursor, mut after_cursor) = cmd.split_at(x - 1 - self.prompt.len()); - if after_cursor.len() > 0 { - after_cursor = &after_cursor[1..]; + if self.cmd.len() > 0 { + if kernel::console::has_cursor() { + if x > self.prompt.len() { + let cmd = self.cmd.clone(); + let (before_cursor, mut after_cursor) = cmd.split_at(x - 1 - self.prompt.len()); + if after_cursor.len() > 0 { + after_cursor = &after_cursor[1..]; + } + self.cmd.clear(); + self.cmd.push_str(before_cursor); + self.cmd.push_str(after_cursor); + kernel::vga::clear_row(); + self.print_prompt(); + print!("{}", self.cmd); + kernel::vga::set_cursor_position(x - 1, y); + kernel::vga::set_writer_position(x - 1, y); + } + } else { + self.cmd.pop(); + print!("{}", c); } - self.cmd.clear(); - self.cmd.push_str(before_cursor); - self.cmd.push_str(after_cursor); - kernel::vga::clear_row(); - self.print_prompt(); - print!("{}", self.cmd); - kernel::vga::set_cursor_position(x - 1, y); - kernel::vga::set_writer_position(x - 1, y); } }, c => { self.update_history(); self.update_autocomplete(); if c.is_ascii() && kernel::vga::is_printable(c as u8) { - let cmd = self.cmd.clone(); - let (before_cursor, after_cursor) = cmd.split_at(x - self.prompt.len()); - self.cmd.clear(); - self.cmd.push_str(before_cursor); - self.cmd.push(c); - self.cmd.push_str(after_cursor); - kernel::vga::clear_row(); - self.print_prompt(); - print!("{}", self.cmd); - kernel::vga::set_cursor_position(x + 1, y); - kernel::vga::set_writer_position(x + 1, y); + if kernel::console::has_cursor() { + let cmd = self.cmd.clone(); + let (before_cursor, after_cursor) = cmd.split_at(x - self.prompt.len()); + self.cmd.clear(); + self.cmd.push_str(before_cursor); + self.cmd.push(c); + self.cmd.push_str(after_cursor); + kernel::vga::clear_row(); + self.print_prompt(); + print!("{}", self.cmd); + kernel::vga::set_cursor_position(x + 1, y); + kernel::vga::set_writer_position(x + 1, y); + } else { + self.cmd.push(c); + print!("{}", c); + } } }, } @@ -230,25 +258,28 @@ impl Shell { let mut args = self.parse(&self.cmd); let i = args.len() - 1; if self.autocomplete_index == 0 { - if args.len() == 1 { - // Autocomplete command - let autocomplete_commands = vec![ // TODO: scan /bin - "copy", "delete", "edit", "help", "move", "print", "quit", "read", "write", "sleep", "clear" - ]; + if args.len() == 1 { // Autocomplete cmd self.autocomplete = vec![args[i].into()]; - for cmd in autocomplete_commands { + for &cmd in &AUTOCOMPLETE_COMMANDS { if cmd.starts_with(args[i]) { self.autocomplete.push(cmd.into()); } } - } else { - // Autocomplete path + } else { // Autocomplete path let pathname = kernel::fs::realpath(args[i]); let dirname = kernel::fs::dirname(&pathname); let filename = kernel::fs::filename(&pathname); self.autocomplete = vec![args[i].into()]; - if let Some(dir) = kernel::fs::Dir::open(dirname) { - let sep = if dirname.ends_with("/") { "" } else { "/" }; + let sep = if dirname.ends_with("/") { "" } else { "/" }; + if pathname.starts_with("/dev") { + for dev in &AUTOCOMPLETE_DEVICES { + let d = kernel::fs::dirname(dev); + let f = kernel::fs::filename(dev); + if d == dirname && f.starts_with(filename) { + self.autocomplete.push(format!("{}{}{}", d, sep, f)); + } + } + } else if let Some(dir) = kernel::fs::Dir::open(dirname) { for entry in dir.read() { if entry.name().starts_with(filename) { self.autocomplete.push(format!("{}{}{}", dirname, sep, entry.name())); @@ -262,7 +293,7 @@ impl Shell { args[i] = &self.autocomplete[self.autocomplete_index]; let cmd = args.join(" "); - kernel::vga::clear_row(); + kernel::console::clear_row(); self.print_prompt(); print!("{}", cmd); } @@ -375,10 +406,10 @@ impl Shell { } fn print_prompt(&self) { - let (fg, bg) = kernel::vga::color(); - kernel::vga::set_color(if self.errored { Color::Red } else { Color::Magenta }, bg); - print!("{}", self.prompt); - kernel::vga::set_color(fg, bg); + let color = if self.errored { "Red" } else { "Magenta" }; + let csi_color = kernel::console::color(color); + let csi_reset = kernel::console::color("Reset"); + print!("{}{}{}", csi_color, self.prompt, csi_reset); } fn change_dir(&self, args: &[&str]) -> ExitCode {