Improve console (#74)

* Move keyboard key decoding to keyboard interrupt handler

* Read keys from serial

* Disable cursor in shell for serial

* Trim cpu brand string

* Use array for writer position

* Add Serial struct

* Add console::clear_row()

* Update autocomplete commands

* Parse ANSI color code

* Add colors to banner

* Remove newline before diskless mode

* Use lighter colors in banner

* Fix ansi color code parsing

* Use ansi colors in logger

* Rewrite colors command

* Add color to halt command

* Rewrite help command

* Use yellow color for titles

* User kernel::console::color() in shell

* Update screenshot

* Fix execute state in vte parser

* Fix typo

* Add colors to serial logger

* Fix banner colors

* Add some randomnly darker colors to banner

* Autocomplete devices path

* Create /ini/version.txt during install
This commit is contained in:
Vincent Ollivier 2020-07-10 08:18:34 +02:00 committed by GitHub
parent 5118a3db2c
commit 5828c7f4d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 427 additions and 191 deletions

58
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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}) |
| |
+--------------------------------------------------+

1
dsk/ini/version.txt Normal file
View File

@ -0,0 +1 @@
MOROS v{x.x.x}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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<String> = Mutex::new(String::new());
pub static ref ECHO: Mutex<bool> = Mutex::new(true);
pub static ref RAW: Mutex<bool> = 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);
}
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}

View File

@ -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<SerialPort> = {
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
serial_port.init();
Mutex::new(serial_port)
};
pub static ref SERIAL: Mutex<Serial> = 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);
}

View File

@ -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<Writer> = 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<ScreenChar>; 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 &param 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(|| {

View File

@ -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

View File

@ -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"]);
}

View File

@ -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
}

View File

@ -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

View File

@ -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 <file> <file>", "Copy file from source to destination\n"),
("d", "elete <file>", "Delete file or empty directory\n"),
("e", "dit <file>", "Edit existing or new file\n"),
("g", "oto <dir>", "Go to directory\n"),
("h", "elp", "Display this text\n"),
("l", "ist <dir>", "List entries in directory\n"),
("m", "ove <file> <file>", "Move file from source to destination\n"),
@ -15,11 +19,16 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode {
("r", "ead <file>", "Read file to screen\n"),
("w", "rite <file>", "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 <v@vinc.cc>\n");
user::shell::ExitCode::CommandSuccessful
}

View File

@ -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);
}

View File

@ -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:

View File

@ -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 {