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 0000000..4ec2a51 Binary files /dev/null and b/bmps/times-new-roman/𐚆0.bmp differ 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 0000000..4ec2a51 Binary files /dev/null and b/bmps/times-new-romono/𐚆0.bmp differ diff --git a/docs/images/mobile.png b/docs/images/mobile.png new file mode 100644 index 0000000..f050125 Binary files /dev/null and b/docs/images/mobile.png differ 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,