keyboard working, actual terminal

also bug fixes
This commit is contained in:
stjet
2024-10-13 04:24:17 +00:00
parent f595d4f43c
commit edf293185f
10 changed files with 142 additions and 173 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -42,9 +42,9 @@ pub struct FramebufferWriter {
}
impl FramebufferWriter {
pub fn init(&mut self, info: FramebufferInfo, buffer: Vec<u8>) {
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])
};

View File

@@ -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<Vec<u8>>, u8)> {
let c = if c == '/' { '𐘋' } else { c };
let mut font: Vec<(char, Vec<Vec<u8>>, u8)> = Vec::new();
for entry in read_dir(dir).unwrap() {
let path = entry.unwrap().path();

View File

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

View File

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

View File

@@ -46,7 +46,6 @@ pub enum WindowMessageResponse {
pub struct KeyPress {
pub key: char,
pub held_special_keys: Vec<&'static str>,
//
}

View File

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

View File

@@ -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<Output>),
Custom,
}
#[derive(Default)]
pub struct Terminal {
dimensions: Dimensions,
@@ -17,6 +26,7 @@ pub struct Terminal {
actual_lines: Vec<String>, //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;

View File

@@ -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<Mutex<FramebufferWriter>> = 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 {