diff --git a/docs/window-likes/audio-player.md b/docs/window-likes/audio-player.md index 8e1ea32..0bde4e6 100644 --- a/docs/window-likes/audio-player.md +++ b/docs/window-likes/audio-player.md @@ -1,4 +1,4 @@ -Audio player with playlist and folder support. +Audio player with playlist and folder support. Requires the dev version of `alsa` lib. ## Commands diff --git a/docs/window-likes/onscreen-keyboard.md b/docs/window-likes/onscreen-keyboard.md new file mode 100644 index 0000000..8113684 --- /dev/null +++ b/docs/window-likes/onscreen-keyboard.md @@ -0,0 +1 @@ +This is primarily intended for mobile users. diff --git a/src/bin/main.rs b/src/bin/main.rs index 6fa77c6..67942ea 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -12,6 +12,7 @@ fn main() { height: fb.var_screen_info.yres_virtual as usize, bytes_per_pixel, stride: fb.fix_screen_info.line_length as usize / bytes_per_pixel, + old_stride: None, }; init(fb, fb_info); diff --git a/src/components/mod.rs b/src/components/mod.rs index 35b56e9..ed77f9b 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -7,6 +7,7 @@ use crate::window_manager::DrawInstructions; pub mod toggle_button; pub mod highlight_button; pub mod paragraph; +pub mod press_button; pub trait Component { fn handle_message(&mut self, message: WindowMessage) -> Option; @@ -17,6 +18,7 @@ pub trait Component { //focusing for components is purely to give a visual representation fn focusable(&self) -> bool; fn clickable(&self) -> bool; + //fn pressable(&self) -> bool; //touch fn name(&self) -> &String; //should be unique } diff --git a/src/components/press_button.rs b/src/components/press_button.rs new file mode 100644 index 0000000..c7d1e37 --- /dev/null +++ b/src/components/press_button.rs @@ -0,0 +1,69 @@ +use std::vec; +use std::vec::Vec; + +use crate::components::Component; +use crate::framebuffer::{ Dimensions, Point }; +use crate::themes::ThemeInfo; +use crate::messages::WindowMessage; +use crate::window_manager::{ DrawInstructions }; + +const MONO_WIDTH: u8 = 10; + +pub struct PressButton { + pub top_left: Point, + pub size: Dimensions, + text: String, + press_return: T, +} + +impl Component for PressButton { + // + fn handle_message(&mut self, message: WindowMessage) -> Option { + match message { + WindowMessage::Touch(_, _) => { + //assume that the parent window-like passed it to us intentionally and checked already + //we can check again, but why? + Some(self.press_return.clone()) + }, + _ => None, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let half = self.size[0] / 2 - self.text.len() * MONO_WIDTH as usize / 2; + vec![ + DrawInstructions::Rect(self.top_left, [self.size[0], 1], theme_info.border_left_top), + DrawInstructions::Rect(self.top_left, [1, self.size[1]], theme_info.border_left_top), + DrawInstructions::Rect([self.top_left[0], self.top_left[1] + self.size[1]], [self.size[0], 1], theme_info.border_right_bottom), + DrawInstructions::Rect([self.top_left[0] + self.size[0], self.top_left[1]], [1, self.size[1]], theme_info.border_right_bottom), + //assume normal background colour + DrawInstructions::Text([self.top_left[0] + half, self.top_left[1] + 8], vec!["times-new-romono".to_string()], self.text.clone(), theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)), + ] + } + + //properties + fn focusable(&self) -> bool { + false + } + + fn clickable(&self) -> bool { + false + } + + fn name(&self) -> &String { + //sorry + &self.text + } +} + +impl PressButton { + pub fn new(top_left: Point, size: Dimensions, text: String, press_return: T) -> Self { + Self { + top_left, + size, + text, + press_return, + } + } +} + diff --git a/src/essential/mod.rs b/src/essential/mod.rs index 0800750..e594dcb 100644 --- a/src/essential/mod.rs +++ b/src/essential/mod.rs @@ -3,6 +3,7 @@ pub mod taskbar; pub mod lock_screen; pub mod workspace_indicator; pub mod start_menu; +pub mod onscreen_keyboard; pub mod about; pub mod help; diff --git a/src/essential/onscreen_keyboard.rs b/src/essential/onscreen_keyboard.rs new file mode 100644 index 0000000..db15b4d --- /dev/null +++ b/src/essential/onscreen_keyboard.rs @@ -0,0 +1,200 @@ +use std::vec; +use std::vec::Vec; +use std::collections::HashMap; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, KeyChar }; +use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::press_button::PressButton; +use crate::utils::point_inside; + +//seems like framebuffer only updates if (real) key is pressed... +//on mobile, volume down button seems to work but is annoying + +const PADDING_Y: usize = 15; +const PADDING_X: usize = 15; +//padding in between keys in the x direction +const KEY_PADDING_X: usize = 5; +const KEY_PADDING_Y: usize = 5; + +#[derive(Clone, Default, Eq, PartialEq, Hash)] +enum Board { + #[default] + Regular, + Shift, + Symbols, + SymbolsShift, +} + +impl Board { + fn inc(&mut self) -> Self { + match self { + Board::Regular => Board::Shift, + Board::Shift => Board::Symbols, + Board::Symbols => Board::SymbolsShift, + Board::SymbolsShift => Board::Regular, + } + } +} + +#[derive(Clone)] +enum KeyResponse { + Key(char), + Alt, + Ctrl, + SwitchBoard, +} + +//if alt is true and ctrl is true, only alt will be sent. +//because I don't care about ctrl+alt stuff, and won't use it. +//(and probably not supported by this with a real keyboard anyways) +#[derive(Default)] +pub struct OnscreenKeyboard { + dimensions: Dimensions, + components: Vec>>, + alt: bool, + ctrl: bool, + board: Board, +} + +impl WindowLike for OnscreenKeyboard { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.set_key_components(); + WindowMessageResponse::JustRedraw + }, + WindowMessage::Touch(x, y) => { + for c in &mut self.components { + if point_inside([x, y], c.top_left, c.size) { + let returned = c.handle_message(WindowMessage::Touch(x, y)); + if let Some(returned) = returned { + return match returned { + KeyResponse::Key(ch) => { + let kc = if self.alt { + self.alt = false; + KeyChar::Alt(ch) + } else if self.ctrl { + self.ctrl = false; + KeyChar::Ctrl(ch) + } else { + KeyChar::Press(ch) + }; + WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(kc)) + }, + KeyResponse::Alt => { + self.alt = !self.alt; + WindowMessageResponse::DoNothing + }, + KeyResponse::Ctrl => { + self.ctrl = !self.ctrl; + WindowMessageResponse::DoNothing + }, + KeyResponse::SwitchBoard => { + self.board = self.board.inc(); + WindowMessageResponse::DoNothing + }, + }; + } + } + } + WindowMessageResponse::DoNothing + }, + _ => WindowMessageResponse::DoNothing, + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![DrawInstructions::Rect([0, 0], self.dimensions, theme_info.background)]; + for component in &self.components { + instructions.extend(component.draw(theme_info)); + } + instructions + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::OnscreenKeyboard + } + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions { + [dimensions[0] - 175, 250] + } +} + +impl OnscreenKeyboard { + pub fn new() -> Self { + Default::default() + } + + fn set_key_components(&mut self) { + self.components = Vec::new(); + let rows: [HashMap>; 4] = [ + HashMap::from([ + (Board::Regular, vec!['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p']), + (Board::Shift, vec!['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P']), + (Board::Symbols, vec!['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']), + (Board::SymbolsShift, vec![]), //empty + ]), + HashMap::from([ + (Board::Regular, vec!['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l']), + (Board::Shift, vec!['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L']), + (Board::Symbols, vec!['|', '=', '$', '%', '&', '-', '+', '(', ')']), + (Board::SymbolsShift, vec!['`', '@', '#', '^']), + ]), + HashMap::from([ + (Board::Regular, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace + (Board::Shift, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace + (Board::Symbols, vec!['~', '*', '"', '\'', ':', ';', '!', '?', '𐘁']), //backspace + (Board::SymbolsShift, vec!['[', ']', '{', '}', '𐘁']), //backspace + ]), + HashMap::from([ + (Board::Regular, vec!['𐘧', '𐘎', ' ', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter + (Board::Shift, vec!['𐘧', '𐘎', ' ', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter + (Board::Symbols, vec!['𐘧', '𐘎', ',', '_', ' ', '/', '.', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter + (Board::SymbolsShift, vec!['𐘧', '𐘎', '\\', '<', ' ', '>', '𐘾', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), ctrl (special case, not a real key), enter + //ctrl = shimazu + ]), + ]; + //hardcoded for now + let mut y = PADDING_Y; + let key_height = (self.dimensions[1] - PADDING_Y * 2 - KEY_PADDING_Y * (rows.len() - 1)) / rows.len(); + let reg_key_width = (self.dimensions[0] - PADDING_X * 2 - KEY_PADDING_X * (10 - 1)) / 10; + for row in rows { + let row_keys = &row[&self.board]; + //centre + let mut x = PADDING_X + (10 - row_keys.len()) * (reg_key_width + KEY_PADDING_X) / 2; + for key in row_keys { + let press_return = if key == &'𐘧' { + KeyResponse::SwitchBoard + } else if key == &'𐘎' { + KeyResponse::Alt + } else if key == &'𐘾' { + KeyResponse::Ctrl + } else { + KeyResponse::Key(*key) + }; + let mut text = key.to_string(); + if text == "𐘧" { + text = "Switch".to_string(); + } else if text == "𐘎" { + text = "Alt".to_string(); + } else if text == "𐘂" { + text = "Enter".to_string(); + } else if text == "𐘁" { + text = "Back".to_string(); + } else if text == "𐘃" { + text = "Esc".to_string(); + } else if text == "𐘾" { + text = "Ctrl".to_string(); + } + self.components.push(Box::new(PressButton::new([x, y], [reg_key_width, key_height], text, press_return))); + x += reg_key_width + KEY_PADDING_X; + } + y += key_height + KEY_PADDING_Y; + } + } +} + diff --git a/src/framebuffer.rs b/src/framebuffer.rs index 15d5ea2..73187af 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -38,6 +38,7 @@ pub struct FramebufferInfo { pub height: usize, pub bytes_per_pixel: usize, pub stride: usize, + pub old_stride: Option, //used/set only when rotate is true } //currently doesn't check if writing onto next line accidentally @@ -45,6 +46,7 @@ pub struct FramebufferWriter { info: FramebufferInfo, buffer: Vec, saved_buffer: Option>, + rotate_buffer: Option>, } impl FramebufferWriter { @@ -57,10 +59,25 @@ impl FramebufferWriter { self.info.clone() } - pub fn get_buffer(&self) -> &[u8] { + pub fn get_buffer(&mut self) -> &[u8] { &self.buffer } + pub fn get_transposed_buffer(&mut self) -> &[u8] { + let mut output_array = vec![255; self.info.byte_len]; + let row_bytes_len = self.info.stride * self.info.bytes_per_pixel; + let row_bytes_len_transposed = self.info.old_stride.unwrap_or(self.info.height) * self.info.bytes_per_pixel; + for y in 0..self.info.height { + for x in 0..self.info.width { + for i in 0..self.info.bytes_per_pixel { + output_array[(self.info.width - x - 1) * row_bytes_len_transposed + y * self.info.bytes_per_pixel + i] = self.buffer[y * row_bytes_len + x * self.info.bytes_per_pixel + i]; + } + } + } + self.rotate_buffer = Some(output_array); + &self.rotate_buffer.as_ref().unwrap() + } + pub fn save_buffer(&mut self) { self.saved_buffer = Some(self.buffer.clone()); } @@ -237,6 +254,7 @@ impl Default for FramebufferWriter { info: Default::default(), buffer: Vec::new(), saved_buffer: None, + rotate_buffer: None, } } } diff --git a/src/keyboard.rs b/src/keyboard.rs deleted file mode 100644 index 6834d76..0000000 --- a/src/keyboard.rs +++ /dev/null @@ -1,22 +0,0 @@ -use termion::event::Key; - -#[derive(Clone, Debug, PartialEq)] -pub enum KeyChar { - Press(char), - Alt(char), - Ctrl(char), -} - -//use Linear A for escape, backspace, enter -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::Ctrl(c) => Some(KeyChar::Ctrl(c)), - Key::Backspace => Some(KeyChar::Press('𐘁')), - Key::Esc => Some(KeyChar::Press('𐘃')), - _ => None, - } -} - diff --git a/src/lib.rs b/src/lib.rs index 387d654..cb61bd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,5 @@ pub mod utils; pub mod logging; pub mod ipc; mod proxy_window_like; -mod keyboard; mod essential; diff --git a/src/messages.rs b/src/messages.rs index 132ff80..18ad423 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -4,12 +4,12 @@ use std::vec::Vec; use serde::{ Deserialize, Serialize }; -use crate::keyboard::KeyChar; use crate::framebuffer::Dimensions; -use crate::window_manager::WindowLike; +use crate::window_manager::{ WindowLike, KeyChar }; pub enum WindowManagerMessage { KeyChar(KeyChar), + Touch(usize, usize), // } @@ -24,22 +24,17 @@ impl PartialEq for WindowBox { } */ -#[derive(PartialEq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum WindowManagerRequest { OpenWindow(String), ClipboardCopy(String), CloseStartMenu, Unlock, Lock, + DoKeyChar(KeyChar), // } -impl fmt::Debug for WindowManagerRequest{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "WindowManagerRequest lmao") - } -} - #[derive(PartialEq, Debug, Serialize, Deserialize)] pub enum WindowMessageResponse { Request(WindowManagerRequest), @@ -47,6 +42,16 @@ pub enum WindowMessageResponse { DoNothing, } +impl WindowMessageResponse { + pub fn is_key_char_request(&self) -> bool { + if let WindowMessageResponse::Request(WindowManagerRequest::DoKeyChar(_)) = self { + true + } else { + false + } + } +} + #[derive(Serialize, Deserialize)] pub struct KeyPress { pub key: char, @@ -99,5 +104,14 @@ pub enum WindowMessage { Unfocus, FocusClick, ChangeDimensions(Dimensions), + Touch(usize, usize), //for onscreen keyboard only // } + +pub enum ThreadMessage { + KeyChar(KeyChar), + Touch(usize, usize), + Clear, + Exit, +} + diff --git a/src/utils.rs b/src/utils.rs index 8e62417..11504ad 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,28 @@ use std::path::PathBuf; +use termion::event::Key; + +use crate::window_manager::KeyChar; +use crate::framebuffer::{ Dimensions, Point }; + +//use Linear A for escape, backspace, enter +//Linear A used only internally in onscreen keyboard: 𐘎 is alt, 𐘧 is switch board, 𐘾 is ctrl +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::Ctrl(c) => Some(KeyChar::Ctrl(c)), + Key::Backspace => Some(KeyChar::Press('𐘁')), + Key::Esc => Some(KeyChar::Press('𐘃')), + _ => None, + } +} + +pub fn min(one: usize, two: usize) -> usize { + if one > two { two } else { one } +} + pub trait Substring { fn substring(&self, start: usize, end: usize) -> &str; fn remove(&self, index: usize, len: usize) -> String; @@ -20,7 +43,6 @@ impl Substring for String { break; } byte_end += char_length; - } &self[byte_start..byte_end] } @@ -125,3 +147,13 @@ pub fn is_hex(c: char) -> bool { HEX_CHARS.iter().position(|hc| hc == &c).is_some() } +pub fn point_inside(point: Point, top_left: Point, size: Dimensions) -> bool { + let x = point[0]; + let y = point[1]; + let x2 = top_left[0]; + let y2 = top_left[1]; + let x3 = x2 + size[0]; + let y3 = y2 + size[1]; + x >= x2 && y >= y2 && x <= x3 && y <= y3 +} + diff --git a/src/window_manager.rs b/src/window_manager.rs index f4a9d97..a651e65 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -3,19 +3,24 @@ use std::vec; use std::collections::{ HashMap, VecDeque }; use std::fmt; use std::boxed::Box; -use std::io::{ stdin, stdout, Write }; +use std::io::{ stdin, stdout, BufReader, BufRead, Write }; use std::process::exit; use std::cell::RefCell; +use std::sync::mpsc; +use std::thread; +use std::time::Duration; +use std::env; +use std::process::{ Command, Stdio }; use linux_framebuffer::Framebuffer; use termion::input::TermRead; use termion::raw::IntoRawMode; -use termion::cursor; +use termion::{ clear, cursor }; use serde::{ Deserialize, Serialize }; use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor }; use crate::themes::{ ThemeInfo, Themes, get_theme_info }; -use crate::keyboard::{ KeyChar, key_to_char }; +use crate::utils::{ min, key_to_char, point_inside }; use crate::messages::*; use crate::proxy_window_like::ProxyWindowLike; use crate::essential::desktop_background::DesktopBackground; @@ -25,13 +30,40 @@ use crate::essential::workspace_indicator::WorkspaceIndicator; use crate::essential::start_menu::StartMenu; use crate::essential::about::About; use crate::essential::help::Help; +use crate::essential::onscreen_keyboard::OnscreenKeyboard; //use crate::logging::log; +//todo: a lot of the usize should be changed to u16 + pub const TASKBAR_HEIGHT: usize = 38; pub const INDICATOR_HEIGHT: usize = 20; const WINDOW_TOP_HEIGHT: usize = 26; +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum KeyChar { + Press(char), + Alt(char), + Ctrl(char), +} + pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { + let args: Vec<_> = env::args().collect(); + + let rotate = args.contains(&"rotate".to_string()); + + let framebuffer_info = if rotate { + FramebufferInfo { + byte_len: framebuffer_info.byte_len, + width: framebuffer_info.height, + height: framebuffer_info.width, + bytes_per_pixel: framebuffer_info.bytes_per_pixel, + stride: framebuffer_info.height, + old_stride: Some(framebuffer_info.stride), + } + } else { + framebuffer_info + }; + let dimensions = [framebuffer_info.width, framebuffer_info.height]; println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT); @@ -40,36 +72,101 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { writer.init(framebuffer_info.clone()); - let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions); + let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions, rotate); + + let mut stdout = stdout().into_raw_mode().unwrap(); + + write!(stdout, "{}", clear::All).unwrap(); + + write!(stdout, "{}", cursor::Hide).unwrap(); + + stdout.flush().unwrap(); wm.draw(None, false); - let stdin = stdin().lock(); - let mut stdout = stdout().into_raw_mode().unwrap(); + let (tx, rx) = mpsc::channel(); - write!(stdout, "{}", cursor::Hide).unwrap(); - stdout.flush().unwrap(); + let tx1 = tx.clone(); + //read key presses + thread::spawn(move || { + let stdin = stdin().lock(); + for c in stdin.keys() { + if let Some(kc) = key_to_char(c.unwrap()) { + //do not allow exit when locked unless debugging + //if kc == KeyChar::Alt('E') { + if kc == KeyChar::Alt('E') { + tx.send(ThreadMessage::Exit).unwrap(); + } else { + tx.send(ThreadMessage::KeyChar(kc)).unwrap(); + } + } + thread::sleep(Duration::from_millis(1)); + } + }); - for c in stdin.keys() { - if let Some(kc) = key_to_char(c.unwrap()) { - //do not allow exit when locked unless debugging - //if kc == KeyChar::Alt('E') { - if kc == KeyChar::Alt('E') && !wm.locked { - write!(stdout, "{}", cursor::Show).unwrap(); - stdout.suspend_raw_mode().unwrap(); - exit(0); - } else { - wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())); + let touch = args.contains(&"touch".to_string()); + + //read touchscreen presses (hopefully) + thread::spawn(move || { + //spawn evtest, parse it for touch coords + if touch { + let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap(); + let reader = BufReader::new(evtest.stdout.as_mut().unwrap()); + let mut x: Option = None; + let mut y: Option = None; + for line in reader.lines() { + let line = line.unwrap(); + if line.contains(&"ABS_X), value ") || line.contains(&"ABS_Y), value ") { + let value: Vec<_> = line.split("), value ").collect(); + let value = value[value.len() - 1].parse::().unwrap(); + if line.contains(&"ABS_X") { + x = Some(value); + } else { + y = Some(value); + } + if x.is_some() && y.is_some() { + let (x2, y2) = if rotate { + (y.unwrap(), dimensions[1] - x.unwrap()) + } else { + (x.unwrap(), y.unwrap()) + }; + //top right, clear + //useful sometimes, I think. + if x2 > dimensions[0] - 100 && y2 < 100 { + tx1.send(ThreadMessage::Clear).unwrap(); + } + tx1.send(ThreadMessage::Touch(x2, y2)).unwrap(); + x = None; + y = None; + } + } + thread::sleep(Duration::from_millis(1)); } } + }); + if touch { + //opens osk + wm.handle_message(WindowManagerMessage::Touch(1, 1)); + } + + for message in rx { + match message { + ThreadMessage::KeyChar(kc) => wm.handle_message(WindowManagerMessage::KeyChar(kc.clone())), + ThreadMessage::Touch(x, y) => wm.handle_message(WindowManagerMessage::Touch(x, y)), + ThreadMessage::Clear => { + write!(stdout, "{}", clear::All).unwrap(); + stdout.flush().unwrap(); + }, + ThreadMessage::Exit => { + if !wm.locked { + write!(stdout, "{}", cursor::Show).unwrap(); + stdout.suspend_raw_mode().unwrap(); + exit(0); + } + }, + }; } - - // -} - -pub fn min(one: usize, two: usize) -> usize { - if one > two { two } else { one } } #[derive(Debug, Serialize, Deserialize)] @@ -89,6 +186,7 @@ pub enum WindowLikeType { Taskbar, StartMenu, WorkspaceIndicator, + OnscreenKeyboard, } pub trait WindowLike { @@ -133,8 +231,10 @@ impl fmt::Debug for WindowLikeInfo { pub struct WindowManager { writer: RefCell, + rotate: bool, id_count: usize, window_infos: Vec, + osk: Option, dimensions: Dimensions, theme: Themes, focused_id: usize, @@ -147,11 +247,13 @@ pub struct WindowManager { //1 is up, 2 is down impl WindowManager { - pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions) -> Self { + pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool) -> Self { let mut wm = WindowManager { writer: RefCell::new(writer), + rotate, id_count: 0, window_infos: Vec::new(), + osk: None, dimensions, theme: Themes::Standard, focused_id: 0, @@ -169,10 +271,9 @@ impl WindowManager { let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions)); self.id_count = self.id_count + 1; let id = self.id_count; - self.focused_id = id; window_like.handle_message(WindowMessage::Init(dimensions)); let dimensions = if window_like.subtype() == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions }; - self.window_infos.push(WindowLikeInfo { + let window_info = WindowLikeInfo { id, window_like, top_left, @@ -183,7 +284,13 @@ impl WindowManager { Workspace::All }, fullscreen: false, - }); + }; + if subtype == WindowLikeType::OnscreenKeyboard { + self.osk = Some(window_info); + } else { + self.focused_id = id; + self.window_infos.push(window_info); + } } fn get_focused_index(&self) -> Option { @@ -215,6 +322,7 @@ impl WindowManager { } //if off_only is true, also handle request + //written confusingly but it works I promise fn toggle_start_menu(&mut self, off_only: bool) -> WindowMessageResponse { let start_menu_exists = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::StartMenu).is_some(); if (start_menu_exists && off_only) || !off_only { @@ -518,22 +626,53 @@ impl WindowManager { }, } }, - // + WindowManagerMessage::Touch(x, y) => { + if x < 100 && y < 100 { + //toggle onscreen keyboard if top left keyboard clicked + if self.osk.is_some() { + self.osk = None; + } else { + let osk = Box::new(OnscreenKeyboard::new()); + let ideal_dimensions = osk.ideal_dimensions(self.dimensions); + self.add_window_like(osk, [175, self.dimensions[1] - TASKBAR_HEIGHT - 250], Some(ideal_dimensions)); + } + WindowMessageResponse::JustRedraw + } else { + //see if in onscreen keyboard, if so send to it after offsetting coords + if self.osk.is_some() { + let osk = self.osk.as_mut().unwrap(); + if point_inside([x, y], osk.top_left, osk.dimensions) { + osk.window_like.handle_message(WindowMessage::Touch(x - osk.top_left[0], y - osk.top_left[1])) + } else { + WindowMessageResponse::DoNothing + } + } else { + WindowMessageResponse::DoNothing + } + } + } }; if response != WindowMessageResponse::DoNothing { + let is_key_char_request = response.is_key_char_request(); match response { WindowMessageResponse::Request(request) => self.handle_request(request), _ => {}, }; - self.draw(redraw_ids, use_saved_buffer); + if !is_key_char_request { + self.draw(redraw_ids, use_saved_buffer); + } } } pub fn handle_request(&mut self, request: WindowManagerRequest) { - let focused_index = self.get_focused_index().unwrap(); - let subtype = self.window_infos[focused_index].window_like.subtype(); + let subtype = if let Some(focused_index) = self.get_focused_index() { + Some(self.window_infos[focused_index].window_like.subtype()) + } else { + None + }; match request { WindowManagerRequest::OpenWindow(w) => { + let subtype = subtype.unwrap(); if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { return; } @@ -565,6 +704,7 @@ impl WindowManager { self.taskbar_update_windows(); }, WindowManagerRequest::CloseStartMenu => { + let subtype = subtype.unwrap(); if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu { return; } @@ -574,13 +714,13 @@ impl WindowManager { } }, WindowManagerRequest::Unlock => { - if subtype != WindowLikeType::LockScreen { + if subtype.unwrap() != WindowLikeType::LockScreen { return; } self.unlock(); }, WindowManagerRequest::Lock => { - if subtype != WindowLikeType::StartMenu { + if subtype.unwrap() != WindowLikeType::StartMenu { return; } self.lock(); @@ -588,6 +728,9 @@ impl WindowManager { WindowManagerRequest::ClipboardCopy(content) => { self.clipboard = Some(content); }, + WindowManagerRequest::DoKeyChar(kc) => { + self.handle_message(WindowManagerMessage::KeyChar(kc)); + }, }; } @@ -605,12 +748,15 @@ impl WindowManager { } //get windows to redraw let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new()); - let all_in_workspace = self.get_windows_in_workspace(true); + let mut all_in_workspace = self.get_windows_in_workspace(true); + if let Some(osk) = &self.osk { + all_in_workspace.push(osk); + } let maybe_length = all_in_workspace.len(); let redraw_windows = all_in_workspace.iter().filter(|w| { //basically, maybe_redraw_ids was None if redraw_ids.len() > 0 { - redraw_ids.contains(&w.id) + redraw_ids.contains(&w.id) || w.window_like.subtype() == WindowLikeType::OnscreenKeyboard } else { true } @@ -697,6 +843,9 @@ impl WindowManager { self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer()); w_index += 1; } - self.framebuffer.write_frame(self.writer.borrow().get_buffer()); + //could probably figure out a way to do borrow() when self.rotate is false but does it matter? + let mut writer_borrow = self.writer.borrow_mut(); + let frame = if self.rotate { writer_borrow.get_transposed_buffer() } else { writer_borrow.get_buffer() }; + self.framebuffer.write_frame(frame); } }