From cb766ce8a5deacbd9d1ff84e9a651ff08f00b234 Mon Sep 17 00:00:00 2001 From: stjet <49297268+stjet@users.noreply.github.com> Date: Wed, 12 Feb 2025 07:44:36 +0000 Subject: [PATCH] inhouse serialize/deserialize, remove ron and serde deps malvim gets 'A' --- Cargo.toml | 2 - README.md | 18 + bmps/times-new-roman/𐚆.alpha | 13 + bmps/times-new-roman/𐚆0.bmp | Bin 0 -> 294 bytes bmps/times-new-romono/𐚆.alpha | 13 + bmps/times-new-romono/𐚆0.bmp | Bin 0 -> 294 bytes docs/images/mobile.png | Bin 0 -> 52791 bytes src/bin/malvim.rs | 6 +- src/fs.rs | 2 +- src/ipc.rs | 16 +- src/lib.rs | 1 + src/messages.rs | 18 +- src/proxy_window_like.rs | 22 +- src/serialize.rs | 753 ++++++++++++++++++++++++++++++++++ src/themes.rs | 5 +- src/window_manager.rs | 7 +- 16 files changed, 835 insertions(+), 41 deletions(-) create mode 100644 bmps/times-new-roman/𐚆.alpha create mode 100644 bmps/times-new-roman/𐚆0.bmp create mode 100644 bmps/times-new-romono/𐚆.alpha create mode 100644 bmps/times-new-romono/𐚆0.bmp create mode 100644 docs/images/mobile.png create mode 100644 src/serialize.rs diff --git a/Cargo.toml b/Cargo.toml index 9c64742..0ed80e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,6 @@ linux_framebuffer = { package = "framebuffer", version = "0.3.1" } termion = "4.0.3" rodio = "0.19.0" rand = "0.8.5" -ron = "0.8" -serde = { version = "1", features = ["derive"] } audiotags = "0.5.0" bmp-rust = "0.4.1" dirs = "5.0.1" diff --git a/README.md b/README.md index 6b4ee8f..3327de8 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,21 @@ cargo build --release Though just `cargo run --release` can be done. +### Running on Mobile Linux + +Running with an onscreen keyboard. The framebuffer may not be redrawn to the screen without a (real) key press. The volume down button seems to work. + +``` +cargo build --release +./target/release/main touch +``` + +Optionally, in landscape mode (todo: osk may be broken in landscape mode): + +``` +cargo build --release +./target/release/main touch rotate +``` + +![mobile example](/docs/images/mobile.png) + diff --git a/bmps/times-new-roman/𐚆.alpha b/bmps/times-new-roman/𐚆.alpha new file mode 100644 index 0000000..c6f6629 --- /dev/null +++ b/bmps/times-new-roman/𐚆.alpha @@ -0,0 +1,13 @@ +0 +179,1,0,0,0 +119,61,0,0,0 +27,150,0,0,0 +0,174,7,0,0 +0,100,80,0,0 +0,14,162,0,0 +0,0,164,16,0 +0,0,81,99,0 +0,0,6,171,0 +0,0,0,151,29 +0,0,0,62,118 +0,0,0,1,176 \ No newline at end of file diff --git a/bmps/times-new-roman/𐚆0.bmp b/bmps/times-new-roman/𐚆0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4ec2a51de108377e55c8b4a045f692c4a6921457 GIT binary patch literal 294 zcmZ?rRbylT12Z700mQ68%mc)X3<^M!f#Cy~L?!QvBSiGY-u2# U2IE8Zl>^0W(Zn_b#Tb#r03#)KF8}}l literal 0 HcmV?d00001 diff --git a/bmps/times-new-romono/𐚆.alpha b/bmps/times-new-romono/𐚆.alpha new file mode 100644 index 0000000..c6f6629 --- /dev/null +++ b/bmps/times-new-romono/𐚆.alpha @@ -0,0 +1,13 @@ +0 +179,1,0,0,0 +119,61,0,0,0 +27,150,0,0,0 +0,174,7,0,0 +0,100,80,0,0 +0,14,162,0,0 +0,0,164,16,0 +0,0,81,99,0 +0,0,6,171,0 +0,0,0,151,29 +0,0,0,62,118 +0,0,0,1,176 \ No newline at end of file diff --git a/bmps/times-new-romono/𐚆0.bmp b/bmps/times-new-romono/𐚆0.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4ec2a51de108377e55c8b4a045f692c4a6921457 GIT binary patch literal 294 zcmZ?rRbylT12Z700mQ68%mc)X3<^M!f#Cy~L?!QvBSiGY-u2# U2IE8Zl>^0W(Zn_b#Tb#r03#)KF8}}l literal 0 HcmV?d00001 diff --git a/docs/images/mobile.png b/docs/images/mobile.png new file mode 100644 index 0000000000000000000000000000000000000000..f0501259fc9f40ac6400bdcad711f2267b8988e5 GIT binary patch literal 52791 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 literal 0 HcmV?d00001 diff --git a/src/bin/malvim.rs b/src/bin/malvim.rs index 083a8fb..4d98b45 100644 --- a/src/bin/malvim.rs +++ b/src/bin/malvim.rs @@ -100,7 +100,11 @@ impl WindowLike for Malvim { self.mode = Mode::Command; self.command = Some(String::new()); changed = false; - } else if key_press.key == 'i' && self.mode == Mode::Normal && self.state == State::None && self.files.len() > 0 { + } else if (key_press.key == 'i' || key_press.key == 'A') && self.mode == Mode::Normal && self.state == State::None && self.files.len() > 0 { + if key_press.key == 'A' { + let current_file = &mut self.files[self.current_file_index]; + current_file.cursor_pos = current_file.content[current_file.line_pos].len(); + } self.mode = Mode::Insert; changed = false; } else if self.mode == Mode::Insert { diff --git a/src/fs.rs b/src/fs.rs index e8525ad..265698d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::io::Read; fn get_font_char(dir: &str, c: char) -> Option<(char, Vec>, u8)> { - let c = if c == '/' { '𐘋' } else if c == '.' { '𐘅' } else { c }; + let c = if c == '/' { '𐘋' } else if c == '\\' { '𐚆' } else if c == '.' { '𐘅' } else { c }; if let Ok(mut file) = File::open(dir.to_string() + "/" + &c.to_string() + ".alpha") { let mut ch: Vec> = Vec::new(); let mut contents = String::new(); diff --git a/src/ipc.rs b/src/ipc.rs index d61c6e0..4a189a5 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,10 +1,11 @@ use std::io::{ stdin, BufRead }; use std::panic; -//use serde::{ Deserialize, Serialize }; -use ron; - use crate::window_manager::WindowLike; +use crate::serialize::Serializable; +use crate::themes::ThemeInfo; +use crate::framebuffer::Dimensions; +use crate::messages::WindowMessage; use crate::logging::log; /* @@ -56,10 +57,11 @@ pub fn listen(mut window_like: impl WindowLike) { let arg = &parts.collect::>().join(" "); let output = match method { "handle_message" => { - format!("{}", ron::to_string(&window_like.handle_message(ron::from_str(arg).unwrap())).unwrap()) + log(arg); + format!("{}", &window_like.handle_message(WindowMessage::deserialize(arg).unwrap()).serialize()) }, "draw" => { - format!("{}", ron::to_string(&window_like.draw(&ron::from_str(arg).unwrap())).unwrap()) + format!("{}", &window_like.draw(&ThemeInfo::deserialize(arg).unwrap()).serialize()) }, "title" => { format!("{}", window_like.title()) @@ -68,10 +70,10 @@ pub fn listen(mut window_like: impl WindowLike) { format!("{}", window_like.resizable()) }, "subtype" => { - format!("{}", ron::to_string(&window_like.subtype()).unwrap()) + format!("{}", &window_like.subtype().serialize()) }, "ideal_dimensions" => { - format!("{}", ron::to_string(&window_like.ideal_dimensions(ron::from_str(arg).unwrap())).unwrap()) + format!("{}", &window_like.ideal_dimensions(Dimensions::deserialize(arg).unwrap()).serialize()) }, _ => String::new(), }; diff --git a/src/lib.rs b/src/lib.rs index cb61bd7..e36798f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod fs; pub mod utils; pub mod logging; pub mod ipc; +pub mod serialize; mod proxy_window_like; mod essential; diff --git a/src/messages.rs b/src/messages.rs index 18ad423..76c52df 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,9 +1,6 @@ use std::boxed::Box; -use std::fmt; use std::vec::Vec; -use serde::{ Deserialize, Serialize }; - use crate::framebuffer::Dimensions; use crate::window_manager::{ WindowLike, KeyChar }; @@ -24,9 +21,10 @@ impl PartialEq for WindowBox { } */ -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq)] pub enum WindowManagerRequest { OpenWindow(String), + //may not work in \x1E, \x1F or \x1D are in the paste string ClipboardCopy(String), CloseStartMenu, Unlock, @@ -35,7 +33,7 @@ pub enum WindowManagerRequest { // } -#[derive(PartialEq, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Debug)] pub enum WindowMessageResponse { Request(WindowManagerRequest), JustRedraw, @@ -52,12 +50,11 @@ impl WindowMessageResponse { } } -#[derive(Serialize, Deserialize)] pub struct KeyPress { pub key: char, } -#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, PartialEq)] pub enum Direction { Left, Down, @@ -66,7 +63,7 @@ pub enum Direction { } //todo, rename to CommandType -#[derive(PartialEq, Serialize, Deserialize)] +#[derive(PartialEq)] pub enum ShortcutType { StartMenu, SwitchWorkspace(u8), @@ -80,20 +77,19 @@ pub enum ShortcutType { FullscreenWindow, HalfWidthWindow, //half width, full height ClipboardCopy, + //may not work in \x1E, \x1F or \x1D are in the paste string ClipboardPaste(String), // } pub type WindowsVec = Vec<(usize, String)>; -#[derive(Serialize, Deserialize)] pub enum InfoType { //let taskbar know what the current windows in the workspace are - WindowsInWorkspace(WindowsVec, usize), //Vec, focused id + WindowsInWorkspace(WindowsVec, usize), //Vec<(id, name)>, focused id // } -#[derive(Serialize, Deserialize)] pub enum WindowMessage { Init(Dimensions), KeyPress(KeyPress), diff --git a/src/proxy_window_like.rs b/src/proxy_window_like.rs index ae766ac..a36d23a 100644 --- a/src/proxy_window_like.rs +++ b/src/proxy_window_like.rs @@ -5,34 +5,33 @@ use std::cell::RefCell; use std::path::Path; use std::io::Read; -use ron; - use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; use crate::messages::{ WindowMessage, WindowMessageResponse }; use crate::framebuffer::Dimensions; use crate::themes::ThemeInfo; +use crate::serialize::{ Serializable, DrawInstructionsVec }; + pub struct ProxyWindowLike { process: RefCell, } //try to handle panics of child processes so the entire wm doesn't crash -//we can "guarantee" that the ron::to_string(...).unwrap() calls will never panic impl WindowLike for ProxyWindowLike { fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { - let _ = stdin.write_all(("handle_message ".to_string() + &ron::to_string(&message).unwrap() + "\n").as_bytes()); + let _ = stdin.write_all(("handle_message ".to_string() + &message.serialize() + "\n").as_bytes()); } let output = self.read_line(); - ron::from_str(&output).unwrap_or(WindowMessageResponse::JustRedraw) + WindowMessageResponse::deserialize(&output).unwrap_or(WindowMessageResponse::JustRedraw) } fn draw(&self, theme_info: &ThemeInfo) -> Vec { if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { - let _ = stdin.write_all(("draw ".to_string() + &ron::to_string(&theme_info).unwrap() + "\n").as_bytes()); + let _ = stdin.write_all(("draw ".to_string() + &theme_info.serialize() + "\n").as_bytes()); } let output = self.read_line(); - ron::from_str(&output).unwrap_or(Vec::new()) + DrawInstructionsVec::deserialize(&output).unwrap_or(Vec::new()) } //properties @@ -44,11 +43,12 @@ impl WindowLike for ProxyWindowLike { } fn resizable(&self) -> bool { + //serialize for bool is just true -> "true", false -> "false" if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { let _ = stdin.write_all("resizable\n".to_string().as_bytes()); } let output = self.read_line(); - ron::from_str(&output).unwrap_or(false) + output == "true\n" } fn subtype(&self) -> WindowLikeType { @@ -56,15 +56,15 @@ impl WindowLike for ProxyWindowLike { let _ = stdin.write_all("subtype\n".to_string().as_bytes()); } let output = self.read_line(); - ron::from_str(&output).unwrap_or(WindowLikeType::Window) + WindowLikeType::deserialize(&output).unwrap_or(WindowLikeType::Window) } fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() { - let _ = stdin.write_all(("ideal_dimensions ".to_string() + &ron::to_string(&dimensions).unwrap() + "\n").as_bytes()); + let _ = stdin.write_all(("ideal_dimensions ".to_string() + &dimensions.serialize() + "\n").as_bytes()); } let output = self.read_line(); - ron::from_str(&output).unwrap_or([420, 420]) + Dimensions::deserialize(&output).unwrap_or([420, 420]) } } diff --git a/src/serialize.rs b/src/serialize.rs new file mode 100644 index 0000000..0fde6b7 --- /dev/null +++ b/src/serialize.rs @@ -0,0 +1,753 @@ +use std::fmt::Display; + +use crate::themes::ThemeInfo; +use crate::messages::{ WindowMessageResponse, WindowManagerRequest, KeyPress, WindowMessage, Direction, ShortcutType, InfoType }; +use crate::window_manager::{ KeyChar, DrawInstructions, WindowLikeType }; +use crate::framebuffer::Dimensions; + +//serde + ron but worse! yay +//not same as ron - simplified +//very messy + +//todo: bug with extra byte when copy/pasting because of this... maybe it's the newline or something? + +//I can't do `impl fmt::Display for RGBColor` which is annoying + +fn array_to_string(array: &[T]) -> String { + let mut output = String::new(); + for item in array { + output += &format!("{}{}", if output == String::new() { + "" + } else { + "\x1F" + }, item); + } + output +} + +fn option_to_string(option: &Option) -> String { + if let Some(value) = option { + format!("S{}", value) + } else { + "N".to_string() + } +} + +fn get_rest_of_split(split: &mut dyn Iterator, sep: Option<&str>) -> String { + let mut rest = String::new(); + let mut n = split.next(); + loop { + rest += &n.unwrap(); + n = split.next(); + if n.is_some() { + rest += sep.unwrap_or(""); + } else { + break; + } + } + rest +} + +fn get_color(serialized: &str) -> Result<[u8; 3], ()> { + let rgb = serialized.split("\x1F"); + let mut color = [0; 3]; + let mut c_i = 0; + //won't return error if rgb is 0, 1, or 2 elements. + //I guess that's okay, since it doesn't panic either + for c in rgb { + if c_i == 3 { + return Err(()); + } + if let Ok(c) = c.parse() { + color[c_i] = c; + } else { + return Err(()); + } + c_i += 1; + } + Ok(color) +} + +fn get_two_array(serialized: &str) -> Result<[usize; 2], ()> { + let mut arg = serialized.split("\x1F"); + let mut a = [0; 2]; + for i in 0..2 { + if let Some(n) = arg.next() { + if let Ok(n) = n.parse() { + a[i] = n; + continue + } + } + return Err(()); + } + return Ok(a); +} + +pub trait Serializable { + fn serialize(&self) -> String; + fn deserialize(serialized: &str) -> Result where Self: Sized; +} + +//ripe for macros when I figure them out + +impl Serializable for ThemeInfo { + fn serialize(&self) -> String { + format!("{}:{}:{}:{}:{}:{}:{}:{}:{}", array_to_string(&self.top), array_to_string(&self.background), array_to_string(&self.border_left_top), array_to_string(&self.border_right_bottom), array_to_string(&self.text), array_to_string(&self.top_text), array_to_string(&self.alt_background), array_to_string(&self.alt_text), array_to_string(&self.alt_secondary)) + } + fn deserialize(serialized: &str) -> Result { + //strip newline at the end + let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized }; + let mut theme_info: ThemeInfo = Default::default(); + let arrays = serialized.split(":"); + let mut a_i = 0; + //won't error or panic if less than 9... rest will just be black by default I guess + for a in arrays { + if a_i == 9 { + return Err(()); + } + let color = get_color(a)?; + match a_i { + 0 => { + theme_info.top = color; + }, + 1 => { + theme_info.background = color; + }, + 2 => { + theme_info.border_left_top = color; + }, + 3 => { + theme_info.border_right_bottom = color; + }, + 4 => { + theme_info.text = color; + }, + 5 => { + theme_info.top_text = color; + }, + 6 => { + theme_info.alt_background = color; + }, + 7 => { + theme_info.alt_text = color; + }, + 8 => { + theme_info.alt_secondary = color; + }, + _ => {}, + }; + if a_i == 8 { + return Ok(theme_info); + } + a_i += 1; + } + Err(()) + } +} + +#[test] +fn theme_info_serialize_deserialize() { + use crate::themes::{ Themes, get_theme_info }; + let theme_info = get_theme_info(&Default::default()).unwrap(); + let serialized = theme_info.serialize(); + assert!(serialized == ThemeInfo::deserialize(&serialized).unwrap().serialize()); +} + +impl Serializable for WindowMessageResponse { + fn serialize(&self) -> String { + match self { + WindowMessageResponse::JustRedraw => "JustRedraw".to_string(), + WindowMessageResponse::DoNothing => "DoNothing".to_string(), + WindowMessageResponse::Request(req) => { + let req = match req { + WindowManagerRequest::OpenWindow(name) => format!("OpenWindow/{}", name), + WindowManagerRequest::ClipboardCopy(name) => format!("ClipboardCopy/{}", name), + WindowManagerRequest::CloseStartMenu => "CloseStartMenu".to_string(), + WindowManagerRequest::Unlock => "Unlock".to_string(), + WindowManagerRequest::Lock => "Lock".to_string(), + WindowManagerRequest::DoKeyChar(kc) => format!("DoKeyChar/{}", match kc { + KeyChar::Press(c) => format!("Press/{}", c), + KeyChar::Alt(c) => format!("Alt/{}", c), + KeyChar::Ctrl(c) => format!("Ctrl/{}", c), + }), + }; + format!("Request/{}", req) + }, + } + } + fn deserialize(serialized: &str) -> Result { + //strip newline at the end + let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized }; + let mut parts = serialized.split("/"); + match parts.next().unwrap_or("Invalid") { + "JustRedraw" => Ok(WindowMessageResponse::JustRedraw), + "DoNothing" => Ok(WindowMessageResponse::DoNothing), + "Request" => { + let req = match parts.next().unwrap_or("Invalid") { + //do get_rest_of_split instead of .next() because it is possible for window name or copy to have "/" + "OpenWindow" => Some(WindowManagerRequest::OpenWindow(get_rest_of_split(&mut parts, Some("/")))), + "ClipboardCopy" => Some(WindowManagerRequest::ClipboardCopy(get_rest_of_split(&mut parts, Some("/")))), + "CloseStartMenu" => Some(WindowManagerRequest::CloseStartMenu), + "Unlock" => Some(WindowManagerRequest::Unlock), + "Lock" => Some(WindowManagerRequest::Lock), + "DoKeyChar" => Some(WindowManagerRequest::DoKeyChar( + match parts.next().unwrap_or("Invalid") { + "Press" => KeyChar::Press(parts.next().unwrap_or("?").chars().next().unwrap()), + "Alt" => KeyChar::Alt(parts.next().unwrap_or("?").chars().next().unwrap()), + "Ctrl" => KeyChar::Ctrl(parts.next().unwrap_or("?").chars().next().unwrap()), + _ => KeyChar::Press('?'), //yeah. + } + )), + _ => None, //yeah... + }; + if let Some(req) = req { + Ok(WindowMessageResponse::Request(req)) + } else { + Err(()) + } + }, + _ => Err(()), + } + } +} + +#[test] +fn window_message_response_serialize_deserialize() { + let resp = WindowMessageResponse::JustRedraw; + let serialized = resp.serialize(); + assert!(resp == WindowMessageResponse::deserialize(&serialized).unwrap()); + let resp = WindowMessageResponse::Request(WindowManagerRequest::OpenWindow("a".to_string())); + let serialized = resp.serialize(); + assert!(resp == WindowMessageResponse::deserialize(&serialized).unwrap()); + let resp = WindowMessageResponse::Request(WindowManagerRequest::Unlock); + let serialized = resp.serialize(); + assert!(resp == WindowMessageResponse::deserialize(&serialized).unwrap()); + let resp = WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(KeyChar::Alt('e'))); + let serialized = resp.serialize(); + assert!(resp == WindowMessageResponse::deserialize(&serialized).unwrap()); +} + +impl Serializable for DrawInstructions { + fn serialize(&self) -> String { + match self { + //use \x1E (record separator) because it won't be in strings. it better fucking not be at least + DrawInstructions::Rect(p, d, c) => format!("Rect/{}\x1E{}\x1E{}", array_to_string(p), array_to_string(d), array_to_string(c)), + DrawInstructions::Text(p, vs, s, c1, c2, ou1, ou2) => format!("Text/{}\x1E{}\x1E{}\x1E{}\x1E{}\x1E{}\x1E{}", array_to_string(p), array_to_string(&vs), s, array_to_string(c1), array_to_string(c2), option_to_string(ou1), option_to_string(ou2)), + DrawInstructions::Gradient(p, d, c1, c2, u) => format!("Gradient/{}\x1E{}\x1E{}\x1E{}\x1E{}", array_to_string(p), array_to_string(d), array_to_string(c1), array_to_string(c2), u), + DrawInstructions::Bmp(p, s, b) => format!("Bmp/{}\x1E{}\x1E{}", array_to_string(p), s, b), + DrawInstructions::Circle(p, u, c) => format!("Circle/{}\x1E{}\x1E{}", array_to_string(p), u, array_to_string(c)), + } + } + fn deserialize(serialized: &str) -> Result { + //no need to strip newlines cause the impl for Vec does that for us + let mut parts = serialized.split("/"); + match parts.next().unwrap_or("Invalid") { + "Rect" => { + let rest = get_rest_of_split(&mut parts, Some("/")); + let mut args = rest.split("\x1E"); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let p = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let d = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let c = get_color(arg.unwrap())?; + Ok(DrawInstructions::Rect(p, d, c)) + }, + "Text" => { + let rest = get_rest_of_split(&mut parts, Some("/")); + //(p, vs, s, c1, c2, ou1, ou2) + let mut args = rest.split("\x1E"); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let p = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let mut vs = Vec::new(); + for s in arg.unwrap().split("\x1F") { + vs.push(s.to_string()); + } + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let s = arg.unwrap(); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let c1 = get_color(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let c2 = get_color(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let arg = arg.unwrap(); + let o1 = match arg { + "N" => None, + _ => { + if arg.len() > 1 { + if let Ok(n) = arg[1..].parse() { + Some(n) + } else { + None + } + } else { + None + } + }, + }; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let arg = arg.unwrap(); + let o2 = match arg { + "N" => None, + _ => { + if arg.len() > 1 { + if let Ok(n) = arg[1..].parse() { + Some(n) + } else { + None + } + } else { + None + } + }, + }; + Ok(DrawInstructions::Text(p, vs, s.to_string(), c1, c2, o1, o2)) + }, + "Gradient" => { + let rest = get_rest_of_split(&mut parts, Some("/")); + //(p, d, c1, c2, u) + let mut args = rest.split("\x1E"); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let p = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let d = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let c1 = get_color(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let c2 = get_color(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let u = arg.unwrap().parse(); + if u.is_err() { + return Err(()); + } + Ok(DrawInstructions::Gradient(p, d, c1, c2, u.unwrap())) + }, + "Bmp" => { + let rest = get_rest_of_split(&mut parts, Some("/")); + //(p, s, b) + let mut args = rest.split("\x1E"); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let p = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let s = arg.unwrap(); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let arg = arg.unwrap(); + if arg != "true" && arg != "false" { + return Err(()); + } + let b = arg == "true"; + Ok(DrawInstructions::Bmp(p, s.to_string(), b)) + }, + "Circle" => { + let rest = get_rest_of_split(&mut parts, Some("/")); + //(p, u, c) + let mut args = rest.split("\x1E"); + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let p = get_two_array(arg.unwrap())?; + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let u = arg.unwrap().parse(); + if u.is_err() { + return Err(()); + } + let arg = args.next(); + if arg.is_none() { + return Err(()); + } + let c = get_color(arg.unwrap())?; + Ok(DrawInstructions::Circle(p, u.unwrap(), c)) + }, + _ => Err(()), + } + } +} + +pub type DrawInstructionsVec = Vec; + +impl Serializable for DrawInstructionsVec { + fn serialize(&self) -> String { + let collected: Vec<_> = self.into_iter().map(|ins| ins.serialize()).collect(); + collected.join("\x1D") + } + fn deserialize(serialized: &str) -> Result { + //strip newline at the end + let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized }; + let mut instructions = Vec::new(); + for ser_ins in serialized.split("\x1D") { + if let Ok(ser_ins) = DrawInstructions::deserialize(ser_ins) { + instructions.push(ser_ins); + } else { + return Err(()); + } + } + Ok(instructions) + } +} + +#[test] +fn draw_instructions_serialize_deserialize() { + use std::vec; + let instructions = vec![ + DrawInstructions::Rect([15, 24], [100, 320], [255, 0, 128]), + DrawInstructions::Text([0, 158], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], "Test test 1234 testing\nmictest / mictest is this thing\non?".to_string(), [12, 36, 108], [128, 128, 128], Some(1), None), + DrawInstructions::Gradient([0, 500], [750, 125], [255, 255, 255], [0, 0, 0], 12), + DrawInstructions::Text([123, 999], vec!["times-new-romono".to_string()], "print!(\"{}\", variable_name);".to_string(), [12, 36, 108], [128, 128, 128], Some(44), Some(200)), + DrawInstructions::Bmp([55, 98], "mingde".to_string(), true), + DrawInstructions::Bmp([55, 98], "wooooo".to_string(), false), + DrawInstructions::Circle([0, 1], 19, [128, 128, 128]), + ]; + let serialized = instructions.serialize(); + assert!(serialized == DrawInstructionsVec::deserialize(&serialized).unwrap().serialize()); + let instructions = vec![ + DrawInstructions::Rect([0, 0], [410, 410], [0, 0, 0]), + DrawInstructions::Text([4, 4], vec!["times-new-romono".to_string()], "Mingde Terminal".to_string(), [255, 255, 255], [0, 0, 0], Some(0), Some(10)), + DrawInstructions::Text([4, 34], vec!["times-new-romono".to_string()], "$ a".to_string(), [255, 255, 255], [0, 0, 0], Some(0), Some(10)), + ]; + let serialized = instructions.serialize() + "\n"; + assert!(serialized[..serialized.len() - 1] == DrawInstructionsVec::deserialize(&serialized).unwrap().serialize()); +} + +impl Serializable for WindowLikeType { + fn serialize(&self) -> String { + match self { + WindowLikeType::LockScreen => "LockScreen".to_string(), + WindowLikeType::Window => "Window".to_string(), + WindowLikeType::DesktopBackground => "DesktopBackground".to_string(), + WindowLikeType::Taskbar => "Taskbar".to_string(), + WindowLikeType::StartMenu => "StartMenu".to_string(), + WindowLikeType::WorkspaceIndicator => "WorkspaceIndicator".to_string(), + WindowLikeType::OnscreenKeyboard => "OnscreenKeyboard".to_string(), + } + } + fn deserialize(serialized: &str) -> Result { + let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized }; + match serialized { + "LockScreen" => Ok(WindowLikeType::LockScreen), + "Window" => Ok(WindowLikeType::Window), + "DesktopBackground" => Ok(WindowLikeType::DesktopBackground), + "Taskbar" => Ok(WindowLikeType::Taskbar), + "StartMenu" => Ok(WindowLikeType::StartMenu), + "WorkspaceIndicator" => Ok(WindowLikeType::WorkspaceIndicator), + "OnscreenKeyboard" => Ok(WindowLikeType::OnscreenKeyboard), + _ => Err(()), + } + } +} + +#[test] +fn window_like_type_serialize_deserialize() { + let wl_type = WindowLikeType::Window; + let serialized = wl_type.serialize(); + assert!(serialized == WindowLikeType::deserialize(&serialized).unwrap().serialize()); +} + +impl Serializable for Dimensions { + fn serialize(&self) -> String { + array_to_string(self) + } + fn deserialize(serialized: &str) -> Result { + //strip newline at the end + let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized }; + let d = get_two_array(serialized)?; + Ok(d) + } +} + +impl Serializable for WindowMessage { + fn serialize(&self) -> String { + match self { + WindowMessage::Init(d) => format!("Init/{}", array_to_string(d)), + WindowMessage::KeyPress(kp) => format!("KeyPress/{}", kp.key), + WindowMessage::CtrlKeyPress(kp) => format!("CtrlKeyPress/{}", kp.key), + WindowMessage::Shortcut(st) => format!("Shortcut/{}", match st { + ShortcutType::StartMenu => "StartMenu".to_string(), + ShortcutType::SwitchWorkspace(u) => format!("SwitchWorkspace/{}", u), + ShortcutType::MoveWindowToWorkspace(u) => format!("MoveWindowToWorkspace/{}", u), + ShortcutType::FocusPrevWindow => "FocusPrevWindow".to_string(), + ShortcutType::FocusNextWindow => "FocusNextWindow".to_string(), + ShortcutType::QuitWindow => "QuitWindow".to_string(), + ShortcutType::MoveWindow(d) => format!("MoveWindow/{}", match d { + Direction::Left => "Left", + Direction::Down => "Down", + Direction::Up => "Up", + Direction::Right => "Right", + }), + ShortcutType::MoveWindowToEdge(d) => format!("MoveWindowToEdge/{}", match d { + Direction::Left => "Left", + Direction::Down => "Down", + Direction::Up => "Up", + Direction::Right => "Right", + }), + ShortcutType::CenterWindow => "CenterWindow".to_string(), + ShortcutType::FullscreenWindow => "FullscreenWindow".to_string(), + ShortcutType::HalfWidthWindow => "HalfWidthWindow".to_string(), + ShortcutType::ClipboardCopy => "ClipboardCopy".to_string(), + ShortcutType::ClipboardPaste(s) => format!("ClipboardPaste/{}", s), + }), + WindowMessage::Info(i) => format!("Info/{}", match i { + InfoType::WindowsInWorkspace(wv, u) => { + let mut wv_string = String::new(); + for w in wv { + wv_string += &format!("{}\x1F{}\x1F", w.0, w.1); + } + wv_string = wv_string[..wv_string.len() - 1].to_string(); + format!("WindowsInWorkspace/{}\x1E{}", wv_string, u) + }, + }), + WindowMessage::Focus => "Focus".to_string(), + WindowMessage::Unfocus => "Unfocus".to_string(), + WindowMessage::FocusClick => "FocusClick".to_string(), + WindowMessage::ChangeDimensions(d) => format!("ChangeDimensions/{}", array_to_string(d)), + WindowMessage::Touch(u1, u2) => format!("Touch/{}\x1E{}", u1, u2), + } + } + fn deserialize(serialized: &str) -> Result { + let serialized = if serialized.ends_with("\n") { &serialized[..serialized.len() - 1] } else { serialized }; + let mut parts = serialized.split("/"); + match parts.next().unwrap_or("Invalid") { + "Init" => { + let arg = parts.next(); + if arg.is_none() { + return Err(()); + } + let d = get_two_array(arg.unwrap())?; + Ok(WindowMessage::Init(d)) + }, + "KeyPress" => { + let charg = get_rest_of_split(&mut parts, Some("/")).chars().next(); + if let Some(charg) = charg { + Ok(WindowMessage::KeyPress(KeyPress { key: charg })) + } else { + Err(()) + } + }, + "CtrlKeyPress" => { + let charg = get_rest_of_split(&mut parts, Some("/")).chars().next(); + if let Some(charg) = charg { + Ok(WindowMessage::CtrlKeyPress(KeyPress { key: charg })) + } else { + Err(()) + } + }, + "Shortcut" => { + let arg = parts.next(); + if arg.is_none() { + return Err(()); + } + let arg = arg.unwrap(); + let shortcut = match arg { + "StartMenu" => Some(ShortcutType::StartMenu), + "SwitchWorkspace" | "MoveWindowToWorkspace" => { + let narg = parts.next(); + if narg.is_none() { + None + } else { + let narg = narg.unwrap(); + if let Ok(n) = narg.parse() { + if arg == "SwitchWorkspace" { + Some(ShortcutType::SwitchWorkspace(n)) + } else { + Some(ShortcutType::MoveWindowToWorkspace(n)) + } + } else { + None + } + } + }, + "FocusPrevWindow" => Some(ShortcutType::FocusPrevWindow), + "FocusNextWindow" => Some(ShortcutType::FocusNextWindow), + "QuitWindow" => Some(ShortcutType::QuitWindow), + "MoveWindow" | "MoveWindowToEdge" => { + let darg = parts.next(); + if let Some(darg) = darg { + let direction = match darg { + "Left" => Some(Direction::Left), + "Up" => Some(Direction::Up), + "Down" => Some(Direction::Down), + "Right" => Some(Direction::Right), + _ => None, + }; + if let Some(direction) = direction { + if arg == "MoveWindow" { + Some(ShortcutType::MoveWindow(direction)) + } else { + Some(ShortcutType::MoveWindowToEdge(direction)) + } + } else { + None + } + } else { + None + } + }, + "CenterWindow" => Some(ShortcutType::CenterWindow), + "FullscreenWindow" => Some(ShortcutType::FullscreenWindow), + "HalfWidthWindow" => Some(ShortcutType::HalfWidthWindow), + "ClipboardCopy" => Some(ShortcutType::ClipboardCopy), + "ClipboardPaste" => Some(ShortcutType::ClipboardPaste(get_rest_of_split(&mut parts, Some("/")))), + _ => None, + }; + if let Some(shortcut) = shortcut { + Ok(WindowMessage::Shortcut(shortcut)) + } else { + Err(()) + } + }, + "Info" => { + //skip WindowsInWorkspace cause that's the only possible InfoType atm + if parts.next().is_none() { + return Err(()); + } + let arg = parts.next(); + if arg.is_none() { + return Err(()); + } + let mut parts2 = arg.unwrap().split("\x1E"); + let arg2 = parts2.next(); + if arg2.is_none() { + return Err(()); + } + let mut w_tuple: (usize, String) = Default::default(); + let mut w_vec = Vec::new(); + let mut i = 0; + for a in arg2.unwrap().split("\x1F") { + if i % 2 == 0 { + if let Ok(n) = a.parse() { + w_tuple.0 = n; + } + } else { + w_tuple.1 = a.to_string(); + w_vec.push(w_tuple.clone()); + } + i += 1; + } + let arg2 = parts2.next(); + if arg2.is_none() { + return Err(()); + } + if let Ok(n) = arg2.unwrap().parse() { + return Ok(WindowMessage::Info(InfoType::WindowsInWorkspace(w_vec, n))); + } else { + return Err(()); + } + }, + "Focus" => Ok(WindowMessage::Focus), + "Unfocus" => Ok(WindowMessage::Unfocus), + "FocusClick" => Ok(WindowMessage::FocusClick), + "ChangeDimensions" => { + let arg = parts.next(); + if arg.is_none() { + return Err(()); + } + let d = get_two_array(arg.unwrap())?; + Ok(WindowMessage::ChangeDimensions(d)) + }, + "Touch" => { + let arg = parts.next(); + if arg.is_none() { + return Err(()); + } + let mut parts2 = arg.unwrap().split("\x1E"); + let arg2 = parts2.next(); + if arg2.is_none() { + return Err(()); + } + let u1 = arg2.unwrap().parse(); + let arg2 = parts2.next(); + if u1.is_err() || arg2.is_none() { + return Err(()); + } + let u2 = arg2.unwrap().parse(); + if u2.is_err() { + return Err(()); + } + Ok(WindowMessage::Touch(u1.unwrap(), u2.unwrap())) + }, + _ => Err(()), + } + } +} + +#[test] +fn window_message_serialize_deserialize() { + for wm in [ + WindowMessage::Init([1000, 1001]), + WindowMessage::KeyPress(KeyPress { key: 'a' }), + WindowMessage::KeyPress(KeyPress { key: '/' }), + WindowMessage::KeyPress(KeyPress { key: '𐘂' }), + WindowMessage::CtrlKeyPress(KeyPress { key: ';' }), + WindowMessage::Shortcut(ShortcutType::StartMenu), + WindowMessage::Shortcut(ShortcutType::MoveWindowToWorkspace(7)), + WindowMessage::Shortcut(ShortcutType::ClipboardPaste("105/20 Azumanga".to_string())), + WindowMessage::Info(InfoType::WindowsInWorkspace(vec![(1, "Terminal".to_string()), (2, "Minesweeper".to_string()), (12, "Test Test".to_string())], 5)), + WindowMessage::Focus, + WindowMessage::Unfocus, + WindowMessage::FocusClick, + WindowMessage::ChangeDimensions([999, 250]), + WindowMessage::Touch(12, 247), + ] { + let serialized = wm.serialize(); + assert!(serialized == WindowMessage::deserialize(&serialized).unwrap().serialize()); + } +} + diff --git a/src/themes.rs b/src/themes.rs index 2b36399..41719b0 100644 --- a/src/themes.rs +++ b/src/themes.rs @@ -1,5 +1,3 @@ -use serde::{ Deserialize, Serialize }; - use crate::framebuffer::RGBColor; #[derive(PartialEq, Default)] @@ -9,7 +7,7 @@ pub enum Themes { // } -#[derive(Serialize, Deserialize)] +#[derive(Default)] pub struct ThemeInfo { pub top: RGBColor, pub background: RGBColor, @@ -20,7 +18,6 @@ pub struct ThemeInfo { pub alt_background: RGBColor, pub alt_text: RGBColor, pub alt_secondary: RGBColor, - // } const THEME_INFOS: [(Themes, ThemeInfo); 1] = [ diff --git a/src/window_manager.rs b/src/window_manager.rs index a651e65..5cb4618 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -16,7 +16,6 @@ use linux_framebuffer::Framebuffer; use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::{ clear, cursor }; -use serde::{ Deserialize, Serialize }; use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor }; use crate::themes::{ ThemeInfo, Themes, get_theme_info }; @@ -39,7 +38,7 @@ pub const TASKBAR_HEIGHT: usize = 38; pub const INDICATOR_HEIGHT: usize = 20; const WINDOW_TOP_HEIGHT: usize = 26; -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq)] pub enum KeyChar { Press(char), Alt(char), @@ -169,7 +168,7 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] pub enum DrawInstructions { Rect(Point, Dimensions, RGBColor), Text(Point, Vec, String, RGBColor, RGBColor, Option, Option), //font and text @@ -178,7 +177,7 @@ pub enum DrawInstructions { Circle(Point, usize, RGBColor), } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq)] pub enum WindowLikeType { LockScreen, Window,