diff --git a/Cargo.toml b/Cargo.toml index ad6e375..ecfcec6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" blake2 = { version = "0.10.6", default-features = false } linux_framebuffer = { package = "framebuffer", version = "0.3.1" } bmp-rust = "0.4.1" +termion = "4.0.3" diff --git a/bmps/times-new-roman/𐘋0.bmp b/bmps/times-new-roman/𐘋0.bmp new file mode 100644 index 0000000..4a0de14 Binary files /dev/null and b/bmps/times-new-roman/𐘋0.bmp differ diff --git a/src/framebuffer.rs b/src/framebuffer.rs index 31872a4..871ec81 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -42,9 +42,9 @@ pub struct FramebufferWriter { } impl FramebufferWriter { - pub fn init(&mut self, info: FramebufferInfo, buffer: Vec) { + pub fn init(&mut self, info: FramebufferInfo, buffer_length: usize) { self.info = info; - self.buffer = buffer; + self.buffer = vec![0; buffer_length]; } pub fn get_info(&self) -> FramebufferInfo { @@ -65,6 +65,7 @@ impl FramebufferWriter { } fn _draw_pixel(&mut self, start_pos: usize, color: RGBColor) { + let color = [color[2], color[1], color[0]]; self.buffer[start_pos..(start_pos + 3)] .copy_from_slice(&color[..]); } @@ -109,29 +110,16 @@ impl FramebufferWriter { self._draw_pixel(start_pos, color); } - //(lines are rectangles of height 1) - pub fn draw_line(&mut self, left: Point, width: usize, color: RGBColor) { - self.draw_rect(left, [width, 1], color); - } - //shapes pub fn draw_rect(&mut self, top_left: Point, dimensions: Dimensions, color: RGBColor) { let line_bytes = if self.info.bytes_per_pixel > 3 { - [color[0], color[1], color[2], 255].repeat(dimensions[0]) + [color[2], color[1], color[0], 255].repeat(dimensions[0]) } else { color.repeat(dimensions[0]) }; let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel; for _row in 0..dimensions[1] { - /* - * for _col in 0..dimensions[0] { - self._draw_pixel(start_pos, color); - start_pos += self.info.bytes_per_pixel; - } - //assumes stride is same as bytes_per_pixel * width - //start_pos = start_pos + top_left[0] * self.info.bytes_per_pixel; - */ //use _draw_line instead for MUCH more efficiency self._draw_line(start_pos, &line_bytes[..]); start_pos += self.info.stride * self.info.bytes_per_pixel; @@ -157,7 +145,7 @@ impl FramebufferWriter { color = [(start_color[0] as f32 + (delta_r * s as f32)) as u8, (start_color[1] as f32 + (delta_g * s as f32)) as u8, (start_color[2] as f32 + (delta_b * s as f32)) as u8]; }; let line_bytes = if self.info.bytes_per_pixel > 3 { - [color[0], color[1], color[2], 255].repeat(dimensions[0]) + [color[2], color[1], color[0], 255].repeat(dimensions[0]) } else { color.repeat(dimensions[0]) }; diff --git a/src/fs.rs b/src/fs.rs index 6f3ae4a..9a577b5 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,9 +1,9 @@ use std::fs::read_dir; -use std::path::Path; use bmp_rust::bmp::BMP; pub fn get_font_char(dir: &str, c: char) -> Option<(char, Vec>, u8)> { + let c = if c == '/' { '𐘋' } else { c }; let mut font: Vec<(char, Vec>, u8)> = Vec::new(); for entry in read_dir(dir).unwrap() { let path = entry.unwrap().path(); diff --git a/src/keyboard.rs b/src/keyboard.rs index 0152a01..a6cb19d 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,109 +1,19 @@ +use termion::event::Key; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum KeyChar { Press(char), - SpecialPress(&'static str), - SpecialRelease(&'static str), + Alt(char), } //use Linear A for escape, backspace, enter -//https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1 -pub fn scancode_to_char(scancode: u8) -> Option { - match scancode { - 0x01 => Some(KeyChar::Press('𐘀')), //escape - 0x02 => Some(KeyChar::Press('1')), - 0x03 => Some(KeyChar::Press('2')), - 0x04 => Some(KeyChar::Press('3')), - 0x05 => Some(KeyChar::Press('4')), - 0x06 => Some(KeyChar::Press('5')), - 0x07 => Some(KeyChar::Press('6')), - 0x08 => Some(KeyChar::Press('7')), - 0x09 => Some(KeyChar::Press('8')), - 0x0A => Some(KeyChar::Press('9')), - 0x0B => Some(KeyChar::Press('0')), - 0x0C => Some(KeyChar::Press('-')), - 0x0D => Some(KeyChar::Press('=')), - 0x0E => Some(KeyChar::Press('𐘁')), //backspace - // - 0x10 => Some(KeyChar::Press('q')), - 0x11 => Some(KeyChar::Press('w')), - 0x12 => Some(KeyChar::Press('e')), - 0x13 => Some(KeyChar::Press('r')), - 0x14 => Some(KeyChar::Press('t')), - 0x15 => Some(KeyChar::Press('y')), - 0x16 => Some(KeyChar::Press('u')), - 0x17 => Some(KeyChar::Press('i')), - 0x18 => Some(KeyChar::Press('o')), - 0x19 => Some(KeyChar::Press('p')), - 0x1A => Some(KeyChar::Press('[')), - 0x1B => Some(KeyChar::Press(']')), - 0x1C => Some(KeyChar::Press('𐘂')), //enter - // - 0x1E => Some(KeyChar::Press('a')), - 0x1F => Some(KeyChar::Press('s')), - 0x20 => Some(KeyChar::Press('d')), - 0x21 => Some(KeyChar::Press('f')), - 0x22 => Some(KeyChar::Press('g')), - 0x23 => Some(KeyChar::Press('h')), - 0x24 => Some(KeyChar::Press('j')), - 0x25 => Some(KeyChar::Press('k')), - 0x26 => Some(KeyChar::Press('l')), - 0x27 => Some(KeyChar::Press(';')), - 0x28 => Some(KeyChar::Press('\'')), - 0x29 => Some(KeyChar::Press('`')), - 0x2A => Some(KeyChar::SpecialPress("shift")), - 0x2B => Some(KeyChar::Press('\\')), - 0x2C => Some(KeyChar::Press('z')), - 0x2D => Some(KeyChar::Press('x')), - 0x2E => Some(KeyChar::Press('c')), - 0x2F => Some(KeyChar::Press('v')), - 0x30 => Some(KeyChar::Press('b')), - 0x31 => Some(KeyChar::Press('n')), - 0x32 => Some(KeyChar::Press('m')), - 0x33 => Some(KeyChar::Press(',')), - 0x34 => Some(KeyChar::Press('.')), - 0x35 => Some(KeyChar::Press('/')), - // - 0x38 => Some(KeyChar::SpecialPress("alt")), - 0x39 => Some(KeyChar::Press(' ')), - // - 0xAA => Some(KeyChar::SpecialRelease("shift")), - // - 0xB8 => Some(KeyChar::SpecialRelease("alt")), +pub fn key_to_char(key: Key) -> Option { + match key { + Key::Char('\n') => Some(KeyChar::Press('𐘂')), + Key::Char(c) => Some(KeyChar::Press(c)), + Key::Alt(c) => Some(KeyChar::Alt(c)), + Key::Backspace => Some(KeyChar::Press('𐘁')), _ => None, } } -//handle shift + key -pub fn uppercase_or_special(c: char) -> char { - let upper = c.to_uppercase().next().unwrap(); - if upper == c { - //special, the other keys on top - match c { - '1' => '!', - '2' => '@', - '3' => '#', - '4' => '$', - '5' => '%', - '6' => '^', - '7' => '&', - '8' => '*', - '9' => '(', - '0' => ')', - '-' => '_', - '=' => '+', - '[' => '{', - ']' => '}', - '\\' => '|', - ';' => ':', - '\'' => '"', - ',' => '<', - '.' => '>', - '/' => '?', - _ => c, - } - } else { - upper - } -} - diff --git a/src/main.rs b/src/main.rs index 852e938..5ed51ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,10 +28,7 @@ fn main() { bytes_per_pixel, stride: fb.fix_screen_info.line_length as usize / bytes_per_pixel, }; - println!("{:?}", fb_info); init(fb, fb_info); - - // } diff --git a/src/messages.rs b/src/messages.rs index 2ff7227..420b7cc 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -46,7 +46,6 @@ pub enum WindowMessageResponse { pub struct KeyPress { pub key: char, - pub held_special_keys: Vec<&'static str>, // } diff --git a/src/window_likes/taskbar.rs b/src/window_likes/taskbar.rs index 84da083..bdaadc2 100644 --- a/src/window_likes/taskbar.rs +++ b/src/window_likes/taskbar.rs @@ -8,7 +8,6 @@ use crate::framebuffer::Dimensions; use crate::themes::ThemeInfo; use crate::components::Component; use crate::components::toggle_button::{ ToggleButton, ToggleButtonAlignment }; -use crate::window_likes::start_menu::StartMenu; const PADDING: usize = 4; const META_WIDTH: usize = 175; //of the window button diff --git a/src/window_likes/terminal.rs b/src/window_likes/terminal.rs index ef3a689..4ac3169 100644 --- a/src/window_likes/terminal.rs +++ b/src/window_likes/terminal.rs @@ -1,5 +1,9 @@ use std::vec::Vec; use std::vec; +use std::process::{ Command, Output }; +use std::str::from_utf8; +use std::path::PathBuf; +use std::io; use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, WINDOW_TOP_HEIGHT }; use crate::messages::{ WindowMessage, WindowMessageResponse }; @@ -10,6 +14,11 @@ const MONO_WIDTH: u8 = 8; const LINE_HEIGHT: usize = 15; const PADDING: usize = 4; +enum CommandResponse { + ActualCommand(io::Result), + Custom, +} + #[derive(Default)] pub struct Terminal { dimensions: Dimensions, @@ -17,6 +26,7 @@ pub struct Terminal { actual_lines: Vec, //wrapping actual_line_num: usize, //what line # is at the top, for scrolling current_input: String, + current_path: String, } //for some reason key presses, then moving the window leaves the old window still there, behind it. weird @@ -26,13 +36,40 @@ impl WindowLike for Terminal { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; + self.current_path = "/".to_string(); self.lines = vec!["Mingde Terminal".to_string(), "".to_string()]; self.calc_actual_lines(); WindowMessageResponse::JustRerender }, WindowMessage::KeyPress(key_press) => { - self.current_input += &key_press.key.to_string(); + if key_press.key == '𐘁' { //backspace + if self.current_input.len() > 0 { + self.current_input = self.current_input[..self.current_input.len() - 1].to_string(); + } else { + return WindowMessageResponse::DoNothing; + } + } else if key_press.key == '𐘂' { //the enter key + self.lines.push("$ ".to_string() + &self.current_input); + if let CommandResponse::ActualCommand(maybe_output) = self.process_command() { + if let Ok(output) = maybe_output { + let write_output = if output.status.success() { + output.stdout + } else { + output.stderr + }; + for line in from_utf8(&write_output).unwrap_or("Failed to parse process output as utf-8").split("\n") { + self.lines.push(line.to_string()); + } + } else { + self.lines.push("Failed to execute process".to_string()); + } + } + self.current_input = String::new(); + } else { + self.current_input += &key_press.key.to_string(); + } self.calc_actual_lines(); + self.actual_line_num = self.actual_lines.len().checked_sub(self.get_max_lines()).unwrap_or(0); WindowMessageResponse::JustRerender }, WindowMessage::ChangeDimensions(dimensions) => { @@ -50,7 +87,7 @@ impl WindowLike for Terminal { // ]; //add the visible lines of text - let end_line = self.actual_line_num + (self.dimensions[1] - WINDOW_TOP_HEIGHT- PADDING * 2) / LINE_HEIGHT; + let end_line = self.actual_line_num + self.get_max_lines(); let mut text_y = WINDOW_TOP_HEIGHT + PADDING; for line_num in self.actual_line_num..end_line { if line_num == self.actual_lines.len() { @@ -85,6 +122,49 @@ impl Terminal { Default::default() } + fn get_max_lines(&self) -> usize { + (self.dimensions[1] - WINDOW_TOP_HEIGHT- PADDING * 2) / LINE_HEIGHT + } + + fn process_command(&mut self) -> CommandResponse { + if self.current_input.starts_with("clear ") || self.current_input == "clear" { + self.lines = Vec::new(); + CommandResponse::Custom + } else if self.current_input.starts_with("cd ") { + let mut cd_split = self.current_input.split(" "); + cd_split.next().unwrap(); + let mut failed = false; + let arg = cd_split.next().unwrap(); + let mut new_path = PathBuf::from(&self.current_path); + if arg.starts_with("/") { + //absolute path + new_path = PathBuf::from(arg); + } else { + //relative path + for part in arg.split("/") { + if part == ".." { + if let Some(parent) = new_path.parent() { + new_path = parent.to_path_buf(); + } else { + failed = true; + } + } else { + new_path.push(part); + } + } + } + if !failed { + //see if path exists + if new_path.exists() { + self.current_path = new_path.to_str().unwrap().to_string(); + } + } + CommandResponse::Custom + } else { + CommandResponse::ActualCommand(Command::new("sh").arg("-c").arg(&self.current_input).current_dir(&self.current_path).output()) + } + } + fn calc_actual_lines(&mut self) { self.actual_lines = Vec::new(); let max_chars_per_line = (self.dimensions[0] - PADDING * 2) / MONO_WIDTH as usize; diff --git a/src/window_manager.rs b/src/window_manager.rs index 2532348..5a6afee 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -3,9 +3,14 @@ use std::vec; use std::collections::HashMap; use std::fmt; use std::boxed::Box; +use std::sync::{ LazyLock, Mutex }; +use std::io::{ stdin, stdout, Write }; +use std::process::exit; use linux_framebuffer::Framebuffer; -use std::sync::{ LazyLock, Mutex }; +use termion::input::TermRead; +use termion::raw::IntoRawMode; +use termion::cursor; use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor }; use crate::window_likes::desktop_background::DesktopBackground; @@ -13,7 +18,7 @@ use crate::window_likes::taskbar::Taskbar; use crate::window_likes::lock_screen::LockScreen; use crate::window_likes::workspace_indicator::WorkspaceIndicator; use crate::themes::{ ThemeInfo, Themes, get_theme_info }; -use crate::keyboard::{ KeyChar, uppercase_or_special }; +use crate::keyboard::{ KeyChar, key_to_char }; use crate::messages::*; use crate::window_likes::start_menu::StartMenu; @@ -26,18 +31,33 @@ pub const WINDOW_TOP_HEIGHT: usize = 26; static WRITER: LazyLock> = LazyLock::new(|| Mutex::new(Default::default())); -//todo: close start menu if window focus next shortcut done - pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { let dimensions = [framebuffer_info.width, framebuffer_info.height]; - let mut temp_vec = vec![0 as u8; framebuffer_info.height * framebuffer_info.stride * framebuffer_info.bytes_per_pixel]; - WRITER.lock().unwrap().init(framebuffer_info.clone(), temp_vec); + WRITER.lock().unwrap().init(framebuffer_info.clone(), framebuffer_info.height * framebuffer_info.stride * framebuffer_info.bytes_per_pixel); let mut wm: WindowManager = WindowManager::new(framebuffer, dimensions); wm.render(None, false); + let stdin = stdin().lock(); + let mut stdout = stdout().into_raw_mode().unwrap(); + + write!(stdout, "{}", cursor::Hide).unwrap(); + stdout.flush().unwrap(); + + for c in stdin.keys() { + if let Some(kc) = key_to_char(c.unwrap()) { + if kc == KeyChar::Alt('e') { + write!(stdout, "{}", cursor::Show).unwrap(); + stdout.suspend_raw_mode().unwrap(); + exit(0); + } else { + wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())); + } + } + } + // } @@ -45,19 +65,6 @@ pub fn min(one: usize, two: usize) -> usize { if one > two { two } else { one } } -/* -pub fn keyboard_emit(key_char: KeyChar) { - let mut kc = key_char; - if let KeyChar::Press(c) = kc { - if WM.lock().held_special_keys.contains(&"shift") { - kc = KeyChar::Press(uppercase_or_special(c)); - } - } - //unsafe { SERIAL1.lock().write_text(&format!("{:?}", &kc)); } - WM.lock().handle_message(WindowManagerMessage::KeyChar(kc)); -} -*/ - #[derive(Debug)] pub enum DrawInstructions { Rect(Point, Dimensions, RGBColor), @@ -119,7 +126,6 @@ pub struct WindowManager { dimensions: Dimensions, theme: Themes, focused_id: usize, - held_special_keys: Vec<&'static str>, locked: bool, current_workspace: u8, framebuffer: Framebuffer, @@ -135,7 +141,6 @@ impl WindowManager { dimensions, theme: Themes::Standard, focused_id: 0, - held_special_keys: Vec::new(), locked: false, current_workspace: 0, framebuffer, @@ -232,9 +237,9 @@ impl WindowManager { //check if is special key (key releases are guaranteed to be special keys) //eg: ctrl, alt, command/windows, shift, or caps lock match key_char { - KeyChar::Press(c) => { + KeyChar::Alt(c) => { let mut press_response = WindowMessageResponse::DoNothing; - if self.held_special_keys.contains(&"alt") && !self.locked { + if !self.locked { //keyboard shortcut let shortcuts = HashMap::from([ ('s', ShortcutType::StartMenu), @@ -424,33 +429,24 @@ impl WindowManager { } }; } - } else { - //send to focused window - if let Some(focused_index) = self.get_focused_index() { - press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::KeyPress(KeyPress { - key: c, - held_special_keys: self.held_special_keys.clone(), - })); - //at most, only the focused window needs to be rerendered - redraw_ids = Some(vec![self.window_infos[focused_index].id]); - //requests can result in window openings and closings, etc - if press_response != WindowMessageResponse::JustRerender { - redraw_ids = None; - } - } } press_response }, - KeyChar::SpecialPress(special_key) => { - //add to pressed keys - self.held_special_keys.push(special_key); - WindowMessageResponse::DoNothing - }, - KeyChar::SpecialRelease(special_key) => { - //remove it from pressed keys - let index = self.held_special_keys.iter().position(|sk| sk == &special_key).unwrap(); - self.held_special_keys.remove(index); - WindowMessageResponse::DoNothing + KeyChar::Press(c) => { + let mut press_response = WindowMessageResponse::DoNothing; + //send to focused window + if let Some(focused_index) = self.get_focused_index() { + press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::KeyPress(KeyPress { + key: c, + })); + //at most, only the focused window needs to be rerendered + redraw_ids = Some(vec![self.window_infos[focused_index].id]); + //requests can result in window openings and closings, etc + if press_response != WindowMessageResponse::JustRerender { + redraw_ids = None; + } + } + press_response }, } }, @@ -579,8 +575,7 @@ impl WindowManager { framebuffer_info.stride = window_width; //make a writer just for the window let mut window_writer: FramebufferWriter = Default::default(); - let mut temp_vec = vec![0 as u8; window_width * window_height * bytes_per_pixel]; - window_writer.init(framebuffer_info, temp_vec); + window_writer.init(framebuffer_info, window_width * window_height * bytes_per_pixel); for instruction in instructions { //unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", instruction)); } match instruction {