diff --git a/.gitignore b/.gitignore index af87b77..d284329 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ Cargo.lock ming-wm +password.txt diff --git a/Cargo.toml b/Cargo.toml index c6c1cf1..9c64742 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ default-run = "main" [build-dependencies] bmp-rust = "0.4.1" +blake2 = { version = "0.10.6", default-features = false } [dependencies] blake2 = { version = "0.10.6", default-features = false } diff --git a/README.md b/README.md index ba7a165..6ea3dcd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ +Ming-wm is a keyboard-based, retro-themed window manager for Linux. It is single-threaded, and is neither for Wayland or the X Window System - it writes directly to the framebuffer. Inspirations include i3, Haiku, SerenityOS, and Windows98, and it is a conceptual successor to the previous [mingde](https://github.com/stjet/mingde) and [ming-os](https://github.com/stjet/ming-os). + +![example 1](/docs/images/ws1.png) +![example 2](/docs/images/ws2.png) ## Running +Create a `password.txt` file in the same directory as `build.rs`, otherwise the default password will be "incorrect mule lightbulb niche". + For best performance: ``` cargo build --release @@ -9,27 +15,3 @@ cargo build --release Though just `cargo run --release` can be done. -## Config - -Config files should be protected so they can only be written to with root privileges. - -### Desktop Backgrounds - -In `$XDG_CONFIG_DIR/ming-wm/desktop-background`, you can configure what the desktop background should be for each workspace. The first line decides the background for the first workspace, and so on. If lines are missing, or empty, or the config file is missing, the default green background is used. - -If a line starts with "#", and is followed by 6 lowercase hex characters, then it will interpreted as a RGB colour. - -If a line starts with "r", then what follows with be interpreted as a path to a BMP image file in BGRA order, and if it starts with any other character, what follows will be interpreted as a path to a BMP image file in RGBA order. - -Example: - -``` -#008080 -#003153 -r./bmps/castle1440x842.bmp -r./bmps/ming1440x842.bmp -r./bmps/blur1440x842.bmp -``` - -// - diff --git a/build.rs b/build.rs index a9756c0..28d027f 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,9 @@ -use std::fs::{ read_dir, File }; +use std::fs::{ read_dir, read_to_string, write, File }; use std::io::Write; +use std::env; +use std::path::Path; +use blake2::{ Blake2b512, Digest }; use bmp_rust::bmp::BMP; fn font_chars_to_alphas(dir: &str) { @@ -37,6 +40,14 @@ fn font_chars_to_alphas(dir: &str) { } fn main() { + //hash + "salt" password and add to build + let password = read_to_string("password.txt").unwrap_or("password".to_string()).replace("\n", "") + "salt?sorrycryptographers"; + let mut hasher = Blake2b512::new(); + hasher.update(password.as_bytes()); + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("password.rs"); + write(&dest_path, format!("pub const PASSWORD_HASH: [u8; 64] = {:?};", hasher.finalize())).unwrap(); + //process bmps for entry in read_dir("./bmps").unwrap() { let path = entry.unwrap().path(); if path.is_dir() { diff --git a/docs/images/ws1.png b/docs/images/ws1.png new file mode 100644 index 0000000..674036f Binary files /dev/null and b/docs/images/ws1.png differ diff --git a/docs/images/ws3.png b/docs/images/ws3.png new file mode 100644 index 0000000..fa41082 Binary files /dev/null and b/docs/images/ws3.png differ diff --git a/docs/system/README.md b/docs/system/README.md new file mode 100644 index 0000000..dbff55d --- /dev/null +++ b/docs/system/README.md @@ -0,0 +1,112 @@ +> This is not extensive technical documentation of the project, but a technical overview. +> If reading this in the "About" app inside ming-wm, use the 'j' and 'k' keys to scroll. +> Recommended reading music: Hegira by such + +Ming-wm, as the name implies, is great at liberating the nation of Mongol hordes, and great at collapsing when invaded by Jurchens. + +Also, it is a window manager. The window manager manages "window-likes": + +```rust +pub enum WindowLikeType { + LockScreen, + Window, + DesktopBackground, + Taskbar, + StartMenu, + WorkspaceIndicator, +} +``` + +All of these are called "window-likes" because to the window manager they are all essentially all the same; they all implement the same trait. + +```rust +pub trait WindowLike { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse; + + fn draw(&self, theme_info: &ThemeInfo) -> Vec; + + //properties + fn title(&self) -> String { + String::new() + } + + fn resizable(&self) -> bool { + false + } + + fn subtype(&self) -> WindowLikeType; + + fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions; //needs &self or its not object safe or some bullcrap +} +``` + +The only thing special about `Window` window-likes is that the window manager draws window decorations for it automatically. Well, I don't want to lie. There are a few other special things but that's the main one. + +## The Event Loop + +The event loop goes like this: + +1. Keyboard event received, sent to the window manager +2. The window manager interprets it. It could be a shortcut to say, open the start meny. Or, if a window-like is currently focused, the window manager will probably forward the keyboard event to that window-like by calling the window-like's `handle_message` method +3. (Only if sent to a window-like) The window-like receives and processes it. It returns a `WindowMessageResponse`: +>>> ```rust +pub enum WindowMessageResponse { + Request(WindowManagerRequest), + JustRerender, + DoNothing, +} +``` +4. If the window manager decides the keyboard event means some or all of the screen needs to be redrawn (eg, it was a valid shortcut, or the window-like it sent the event to returned something that wasn't a `DoNothing`), it will go and get the drawing instructions from all the window-likes that need to be redrawn by looping through them and calling their `draw` method + +Nothing except key presses trigger redraws. That means no mouse and no animations. This is a positive. This is a positive. I truly believe that. This is a positive. Having a window manager and windows that don't require taking hands off the keyboard (or rather, entirely designed to be keyboard operated) makes using it very fast and efficient, with no pain of not having a mice and needing to use a shitty mousepad. Videos are nice, but animations and the like are annoying and have no place in a good window manager. + +## Drawing / Rendering + +For each window-like it (re)draws, it creates a new framebuffer, then draws to the framebuffer, the draw instructions it received from the window-like. Then, it draws that new framebuffer onto the actual linux framebuffer (ie, draws the window-like to the screen). So, there is no issue with window-likes overlapping. + +One may wonder why exactly the window manager receives drawing instructions from the window-likes and does the actual drawing of the window-likes. Why not just receive the raw pixels of the window-like from the window-like, and just handle compositing the window-likes? Receiving draw instructions does have the advantage of having a significantly better best-case scenario when transferring data between the window-like and window manager, as letting the window-like do the drawing and handing the result to the window manager will mean always transferring `height*width*bytes per pixel` bytes. Additionally, draw instructions are significantly more readably and easier to debug. Finally, asking window-likes to do their own drawing results in window-likes being required to contain lots of drawing code (especially text drawing code!!), rather than just telling the window manager to draw rectangles, lines, and text. While all the window-likes and windows in this repo all rely on the same `framebuffer.rs`, if someone were to write a window in say, Lisp Scheme, they would need to write all that logic again. But the real answer is because that is how it was written in ming-os, from which much of the core code comes from, and it works well, though the lack of a instruction length upper-bound is concerning and potentially inefficient. I don't feel like rewriting it and I don't believe it will become a problem. + +## Non-window Window-likes + +Recall that the `WindowLikeType`s were LockScreen, DesktopBackground, Taskbar, StartMenu and WorkspaceIndicator (ignoring Window). What they should do is fairly self-explanatory, but as a brief overview: + +- Lock screen: Initial state of the window manager. Is the only window-like until the correct password is entered +- Desktop background: Displays the desktop background, behind the windows. Can be a solid colour, or a .bmp image +- Taskbar: Shows currently open windows in the current workspace, and manages the start menu +- Start menu: Shows the window (app) categories, and opens the window (apps) requested +- Workspace indicator: Shows which workspace the window manager is currently in. There are 9 workspaces, each of which can contain their own set of windows. The workspaces can be switched to or out of easily, and windows can be moved easily between them + +Each of these receives special, privileged messages from the window manager, in order to be useful and function. For example, the taskbar is notified whenever a window is opened or closed, and the workspace indicator receives a message whenever the workspace is changed. + +In some cases, these non-window window-likes also get special rights. For example, only the start menu and taskbar can open window(-likes). The start menu for obvious reasons (opening the windows it was asked to open), and the taskbar needs it to open the start menu. In the future, the taskbar and window manager may be rewritten so the window manager, instead of the taskbar, opens the start menu. Other examples are only the lock screen being able to unlock (if the password is correct), and only the start menu being able to lock (if the lock option is executed). + +All of these non-window window-likes are compiled into the window manager binary. They are not separate binaries/processes as they aren't really expected to be modified or swapped out, and are "essential" to the function of the window manager. + +## Window Window-likes + +Also known as apps. "Apps" and "windows" will be used mostly interchangeably, with the important nuance that there can only be multiple windows opened of a single app. + +Windows can be moved and resized over the desktop background (they cannot overlap with the workspace indicator or taskbar, or go off the screen). They also come with a window decoration on the top displaying the title of the window. + +As windows are mostly just like other window-likes, they can be compiled in as part of the window manager binary. However, most apps in this repo are separate binaries (see `src/bin`). They use `proxy_window_like.rs` which implements the `WindowLike` trait and proxies talking to the window binary. The window is a child process. Messages are sent to it through piped in stdin, and responses are received through stdout. These apps aren't terribly fancy but the performance impact seems to be unnoticable. + +As apps are just any old binary that support the IPC that `proxy_window_like.rs` does, they can be written in any language, and be completely separate from this project. Of course, if they are not written in Rust, extra code is needed to do that IPC. + +//but what apps can be opened are currently hardcoded in start menu and the window manager + +Some of the apps included are Malvim, the subset of vim (a text editor) I use, Minesweeper, and an Audio Player. + +## More on Window-likes + +Further documentation on specific window-likes can be found in `docs/window-likes`. + +## Themeing + +The window manager passes information about it's theme to all window-likes as a parameter to `draw`, so windows can have appropriate background colours, highlight colours, text colours, etc. + +//can't change themes yet. in fact, no other themes yet + +## Fonts / Text + +// +//Japanese / Chinese characters can only be used for display, not input, as there is no CJK input system. yet. And these text inputs don't yet handle multi-byte input very gracefully diff --git a/docs/system/ipc.md b/docs/system/ipc.md new file mode 100644 index 0000000..8337712 --- /dev/null +++ b/docs/system/ipc.md @@ -0,0 +1 @@ +// diff --git a/docs/system/shortcuts.md b/docs/system/shortcuts.md new file mode 100644 index 0000000..e194fcd --- /dev/null +++ b/docs/system/shortcuts.md @@ -0,0 +1,20 @@ +- Alt+E: Exit ming-wm +- Alt+s: Open start menu +- Alt+[: Focus previous window +- Alt+]: Focus next window +- Alt+q: Quit window +- Alt+c: Centre window +- Alt+f: Fullscreen window +- Alt+w: Half width window +- Alt+C: Clipboard copy +- Alt+P: Clipboard paste +- Alt+h: Move window left +- Alt+j: Move window down +- Alt+k: Move window up +- Alt+l: Move window right +- Alt+H: Move window to left edge +- Alt+J: Move window to bottom edge +- Alt+K: Move window to top edge +- Alt+L: Move window to right edge +- Alt+1, Alt+2, ..., Alt+[n], ..., Alt+9: Switch to workspace [n] +- Alt+shift+1, Alt+shift+2, ..., Alt+shift+[n], ..., Alt+shift+9: Move window to workspace [n] diff --git a/docs/window-likes/desktop-background.md b/docs/window-likes/desktop-background.md new file mode 100644 index 0000000..903dd54 --- /dev/null +++ b/docs/window-likes/desktop-background.md @@ -0,0 +1,21 @@ +Displays desktop backgrounds, which can be a BMP image file or a solid colour. + +## Config + +Config files should have the appropriated protections so they can only be written to root privileges. + +In `$XDG_CONFIG_DIR/ming-wm/desktop-background`, you can configure what the desktop background should be for each workspace. The first line decides the background for the first workspace, and so on. If lines are missing, or empty, or the config file is missing, the default green background is used. + +If a line starts with "#", and is followed by 6 lowercase hex characters, then it will interpreted as a RGB colour. + +If a line starts with "r", then what follows with be interpreted as a path to a BMP image file in BGRA order, and if it starts with any other character, what follows will be interpreted as a path to a BMP image file in RGBA order. + +Example: + +``` +#008080 +#003153 +r./bmps/castle1440x842.bmp +r./bmps/ming1440x842.bmp +r./bmps/blur1440x842.bmp +``` diff --git a/docs/window-likes/lock-screen.md b/docs/window-likes/lock-screen.md new file mode 100644 index 0000000..ce5f845 --- /dev/null +++ b/docs/window-likes/lock-screen.md @@ -0,0 +1,5 @@ +Shown when screen is locked. Unlocks the screen when correct password is entered. + +## Config + +Before building, create a `password.txt` in the same directory as the `build.rs` file with the password. diff --git a/docs/window-likes/minesweeper.md b/docs/window-likes/minesweeper.md new file mode 100644 index 0000000..2fa88ea --- /dev/null +++ b/docs/window-likes/minesweeper.md @@ -0,0 +1,13 @@ +The goal of the game is to reveal all tiles that are not mines. Revealing a tile that is a mine means game over. + +The game consists of revealing tiles. If a revealed tile has no mines directly adjacent to it (above, below, left, right, top left, top right, etc). All tiles adjacent to it are revealed. If any of the newly revealed tiles have no mines directly adjacent to it, then... well you get the point. + +Tiles with mines adjacent to it are labeled with a number stating how many mines are adjacent to it. + +To start the game, first enter in a seed for the game, by typing in four random characters, then hitting enter. Then, choose the first tile to reveal. That first tile is guaranteed to be a non-mine tile with no mines adjacent to it. + +Games with the same seed and first tile revealed will have the same mine layout, making the game suitable for competition. + +Choosing to reveal a tile is simple. Just type in the two character name of that tile. For example, 'c4' or '15'. If you make a mistake with the first character, just use the backspace/delete key to clear. + +Upon winning or losing, press a key to play again. diff --git a/src/bin/audio_player.rs b/src/bin/audio_player.rs index 4d91e9e..8899b00 100644 --- a/src/bin/audio_player.rs +++ b/src/bin/audio_player.rs @@ -35,11 +35,11 @@ impl WindowLike for AudioPlayer { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::ChangeDimensions(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { if key_press.key == '𐘂' { //the enter key @@ -52,7 +52,7 @@ impl WindowLike for AudioPlayer { } else { self.command += &key_press.key.to_string(); } - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, _ => { WindowMessageResponse::DoNothing diff --git a/src/bin/file_explorer.rs b/src/bin/file_explorer.rs index 82d3605..a90c934 100644 --- a/src/bin/file_explorer.rs +++ b/src/bin/file_explorer.rs @@ -16,9 +16,6 @@ struct DirectoryChild { override_name: Option, path: PathBuf, is_file: bool, - //can only be true if dir - //if true, means the contents of this dir should be visible too, even though it isn't the current path. like a tree - tree_open: bool, } #[derive(Default)] @@ -38,11 +35,11 @@ impl WindowLike for FileExplorer { self.current_path = PathBuf::from("/"); self.dimensions = dimensions; self.current_dir_contents = self.get_current_dir_contents(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::ChangeDimensions(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { if key_press.key == '𐘂' { //the enter key @@ -53,7 +50,7 @@ impl WindowLike for FileExplorer { self.current_dir_contents = self.get_current_dir_contents(); self.position = 0; self.top_position = 0; - return WindowMessageResponse::JustRerender; + return WindowMessageResponse::JustRedraw; } } WindowMessageResponse::DoNothing @@ -74,15 +71,16 @@ impl WindowLike for FileExplorer { } } //calculate position + let max_height = self.dimensions[1] - HEIGHT; if self.position > self.top_position { let current_height = (self.position - self.top_position) * HEIGHT; if current_height > self.dimensions[1] { - self.top_position += (current_height - self.dimensions[1]) / HEIGHT + 1; + self.top_position += (current_height - max_height) / HEIGHT + 1; } } else { self.top_position = self.position; }; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { WindowMessageResponse::DoNothing } @@ -93,10 +91,10 @@ impl WindowLike for FileExplorer { fn draw(&self, theme_info: &ThemeInfo) -> Vec { let mut instructions = Vec::new(); - //top bar with path name and editing - // + //top bar with path name + instructions.push(DrawInstructions::Text([5, 0], vec!["times-new-roman".to_string(), "shippori-mincho".to_string()], "Current: ".to_string() + &self.current_path.to_string_lossy().to_string(), theme_info.text, theme_info.background, None, None)); //the actual files and directories - let mut start_y = 0; + let mut start_y = HEIGHT; let mut i = self.top_position; for entry in self.current_dir_contents.iter().skip(self.top_position) { if start_y > self.dimensions[1] { @@ -149,7 +147,6 @@ impl FileExplorer { contents.push(DirectoryChild { override_name: Some("..".to_string()), is_file: false, - tree_open: false, path: self.current_path.parent().unwrap().to_owned(), }); } @@ -158,7 +155,6 @@ impl FileExplorer { DirectoryChild { override_name: None, is_file: path.is_file(), - tree_open: false, path, } })); diff --git a/src/bin/malvim.rs b/src/bin/malvim.rs index b29f999..083a8fb 100644 --- a/src/bin/malvim.rs +++ b/src/bin/malvim.rs @@ -87,7 +87,7 @@ impl WindowLike for Malvim { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { let mut changed = true; @@ -304,11 +304,11 @@ impl WindowLike for Malvim { } } self.calc_top_line_pos(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::ChangeDimensions(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::Shortcut(shortcut) => { match shortcut { @@ -329,7 +329,7 @@ impl WindowLike for Malvim { self.calc_top_line_pos(); self.calc_current(); //too over zealous but whatever self.files[self.current_file_index].changed = true; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { WindowMessageResponse::DoNothing } diff --git a/src/bin/minesweeper.rs b/src/bin/minesweeper.rs index bb08c71..9c79a9a 100644 --- a/src/bin/minesweeper.rs +++ b/src/bin/minesweeper.rs @@ -44,7 +44,7 @@ impl WindowLike for Minesweeper { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { if self.state == State::Seed { @@ -58,7 +58,7 @@ impl WindowLike for Minesweeper { self.random_chars.push(key_press.key); } } - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else if self.state == State::BeforePlaying || self.state == State::Playing { if key_press.key == '𐘁' { //backspace self.first_char = '\0'; @@ -125,7 +125,7 @@ impl WindowLike for Minesweeper { self.state = State::Won; } } - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { WindowMessageResponse::DoNothing } diff --git a/src/bin/reversi.rs b/src/bin/reversi.rs index 7156eac..a6dd067 100644 --- a/src/bin/reversi.rs +++ b/src/bin/reversi.rs @@ -59,7 +59,7 @@ impl WindowLike for Reversi { self.dimensions = dimensions; self.new_tiles(); self.valid_moves = self.get_valid_moves(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { if let Ok(n) = key_press.key.to_string().parse::() { @@ -82,7 +82,7 @@ impl WindowLike for Reversi { } else if key_press.key == '𐘁' { //backspace self.current_number = None; } - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, _ => WindowMessageResponse::DoNothing, } diff --git a/src/bin/terminal.rs b/src/bin/terminal.rs index eb68c0d..02949d5 100644 --- a/src/bin/terminal.rs +++ b/src/bin/terminal.rs @@ -44,11 +44,11 @@ impl WindowLike for Terminal { self.current_path = "/".to_string(); self.lines = vec!["Mingde Terminal".to_string(), "".to_string()]; self.calc_actual_lines(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::ChangeDimensions(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { if self.state == State::Input { @@ -68,7 +68,7 @@ impl WindowLike for Terminal { } self.calc_actual_lines(); self.actual_line_num = self.actual_lines.len().checked_sub(self.get_max_lines()).unwrap_or(0); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else if key_press.key.len_utf8() == 1 { //update let running_process = self.running_process.as_mut().unwrap(); @@ -85,7 +85,7 @@ impl WindowLike for Terminal { } self.state = State::Input; self.calc_actual_lines(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { //still running WindowMessageResponse::DoNothing @@ -101,15 +101,15 @@ impl WindowLike for Terminal { //kills and running_process is now None let _ = self.running_process.take().unwrap().kill(); self.state = State::Input; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else if self.state == State::Input && (key_press.key == 'p' || key_press.key == 'n') { //only the last command is saved unlike other terminals. good enough for me if key_press.key == 'p' && self.last_command.is_some() { self.current_input = self.last_command.clone().unwrap(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else if key_press.key == 'n' { self.current_input = String::new(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { WindowMessageResponse::DoNothing } diff --git a/src/bin/test.rs b/src/bin/test.rs deleted file mode 100644 index 9e651c1..0000000 --- a/src/bin/test.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::process::{ Command, Stdio }; -use std::io::{ Read, Write }; - -use ron; - -use ming_wm::messages::WindowMessage; - -fn main() { - println!("{}", ron::to_string(&WindowMessage::Init([100,100])).unwrap()); - //println!("{}", &ron::to_string(&[122, 400]).unwrap()); -} diff --git a/src/components/highlight_button.rs b/src/components/highlight_button.rs index 2250e2c..42a7741 100644 --- a/src/components/highlight_button.rs +++ b/src/components/highlight_button.rs @@ -11,7 +11,7 @@ pub struct HighlightButton { name_: String, top_left: Point, size: Dimensions, - text: &'static str, + text: String, pub highlighted: bool, click_return: T, toggle_highlight_return: T, //also unhighlight return @@ -38,12 +38,12 @@ impl Component for HighlightButton { vec![ //highlight background DrawInstructions::Rect(self.top_left, self.size, theme_info.top), - DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], vec!["times-new-roman".to_string()], self.text.to_string(), theme_info.top_text, theme_info.top, None, None), + DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], vec!["times-new-roman".to_string()], self.text.clone(), theme_info.top_text, theme_info.top, None, None), ] } else { vec![ DrawInstructions::Rect(self.top_left, self.size, theme_info.background), - DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], vec!["times-new-roman".to_string()], self.text.to_string(), theme_info.text, theme_info.background, None, None), + DrawInstructions::Text([self.top_left[0] + 4, self.top_left[1] + (self.size[1] - font_height) / 2], vec!["times-new-roman".to_string()], self.text.clone(), theme_info.text, theme_info.background, None, None), ] } } @@ -63,7 +63,7 @@ impl Component for HighlightButton { } impl HighlightButton { - pub fn new(name_: String, top_left: Point, size: Dimensions, text: &'static str, click_return: T, toggle_highlight_return: T, highlighted: bool) -> Self { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: String, click_return: T, toggle_highlight_return: T, highlighted: bool) -> Self { Self { name_, top_left, diff --git a/src/components/mod.rs b/src/components/mod.rs index ed15e7c..35b56e9 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -6,6 +6,7 @@ use crate::window_manager::DrawInstructions; pub mod toggle_button; pub mod highlight_button; +pub mod paragraph; pub trait Component { fn handle_message(&mut self, message: WindowMessage) -> Option; diff --git a/src/components/paragraph.rs b/src/components/paragraph.rs new file mode 100644 index 0000000..57741c7 --- /dev/null +++ b/src/components/paragraph.rs @@ -0,0 +1,91 @@ +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; +use crate::utils::calc_actual_lines; + +const MONO_WIDTH: u8 = 10; +const LINE_HEIGHT: usize = 18; + +pub struct Paragraph { + name_: String, + actual_lines: Vec<(bool, usize, String)>, //first, line #, actual line + top_left: Point, + size: Dimensions, + line_pos: usize, + key_return: T, +} + +impl Component for Paragraph { + fn handle_message(&mut self, message: WindowMessage) -> Option { + if let WindowMessage::KeyPress(key_press) = message { + if key_press.key == 'j' { + //down + if self.line_pos != self.actual_lines.len() - 1 { + self.line_pos += 1; + } + Some(self.key_return) + } else if key_press.key == 'k' { + //up + if self.line_pos != 0 { + self.line_pos -= 1; + } + Some(self.key_return) + } else { + None + } + } else { + None + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = Vec::new(); + let max_lines = self.size[1] / LINE_HEIGHT; + let mut start_y = self.top_left[1]; + for line in self.actual_lines.iter().skip(self.line_pos).take(max_lines) { + instructions.push(DrawInstructions::Text([self.top_left[0], start_y], vec!["times-new-romono".to_string()], line.2.clone(), theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH))); + start_y += LINE_HEIGHT; + } + instructions + } + + //properties + fn focusable(&self) -> bool { + true + } + + fn clickable(&self) -> bool { + false + } + + fn name(&self) -> &String { + &self.name_ + } +} + +impl Paragraph { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: String, key_return: T) -> Self { + let mut s = Self { + name_, + actual_lines: Vec::new(), + top_left, + size, + line_pos: 0, + key_return, + }; + s.new_text(text); + s + } + + pub fn new_text(&mut self, text: String) { + let max_chars_per_line = self.size[0] / MONO_WIDTH as usize; + let lines: Vec = text.split("\n").map(|s| s.to_string()).collect(); + self.actual_lines = calc_actual_lines(lines.iter(), max_chars_per_line, true); + } +} + diff --git a/src/components/toggle_button.rs b/src/components/toggle_button.rs index 74124bd..bddcae7 100644 --- a/src/components/toggle_button.rs +++ b/src/components/toggle_button.rs @@ -7,12 +7,6 @@ use crate::themes::ThemeInfo; use crate::messages::WindowMessage; use crate::window_manager::DrawInstructions; -//we need a text width and height measure function first -pub enum ToggleButtonAlignment { - Centre, - Left, -} - pub struct ToggleButton { name_: String, top_left: Point, @@ -20,7 +14,6 @@ pub struct ToggleButton { text: String, draw_bg: bool, pub inverted: bool, //whether is it clicked or not - alignment: ToggleButtonAlignment, click_return: T, unclick_return: T, } @@ -73,7 +66,7 @@ impl Component for ToggleButton { } impl ToggleButton { - pub fn new(name_: String, top_left: Point, size: Dimensions, text: String, click_return: T, unclick_return: T, draw_bg: bool, alignment: Option) -> Self { + pub fn new(name_: String, top_left: Point, size: Dimensions, text: String, click_return: T, unclick_return: T, draw_bg: bool) -> Self { Self { name_, top_left, @@ -83,7 +76,6 @@ impl ToggleButton { unclick_return, draw_bg, inverted: false, - alignment: alignment.unwrap_or(ToggleButtonAlignment::Centre), } } } diff --git a/src/essential/about.rs b/src/essential/about.rs new file mode 100644 index 0000000..2a1a65b --- /dev/null +++ b/src/essential/about.rs @@ -0,0 +1,62 @@ +use std::vec::Vec; +use std::boxed::Box; +use std::fs::read_to_string; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::Component; +use crate::components::paragraph::Paragraph; + +pub struct About { + dimensions: Dimensions, + components: Vec + Send>>, +} + +impl WindowLike for About { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.components.push(Box::new(Paragraph::new("help".to_string(), [2, 2], [self.dimensions[0] - 4, self.dimensions[1] - 4], read_to_string("docs/system/README.md").unwrap_or("docs/system/README.md not found".to_string()), ()))); + WindowMessageResponse::JustRedraw + }, + WindowMessage::KeyPress(key_press) => { + if self.components[0].handle_message(WindowMessage::KeyPress(key_press)).is_some() { + WindowMessageResponse::JustRedraw + } else { + WindowMessageResponse::DoNothing + } + }, + _ => WindowMessageResponse::DoNothing + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + self.components[0].draw(theme_info) + } + + //properties + fn title(&self) -> String { + "About".to_string() + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Window + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [500, 600] + } +} + +impl About { + pub fn new() -> Self { + Self { + dimensions: [0, 0], + components: Vec::new(), + } + } +} + diff --git a/src/essential/desktop_background.rs b/src/essential/desktop_background.rs index be261bf..4b0a862 100644 --- a/src/essential/desktop_background.rs +++ b/src/essential/desktop_background.rs @@ -21,13 +21,13 @@ impl WindowLike for DesktopBackground { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::Shortcut(shortcut) => { match shortcut { ShortcutType::SwitchWorkspace(workspace) => { self.current_workspace = workspace; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, _ => WindowMessageResponse::DoNothing, } diff --git a/src/essential/help.rs b/src/essential/help.rs new file mode 100644 index 0000000..3d7fe14 --- /dev/null +++ b/src/essential/help.rs @@ -0,0 +1,89 @@ +use std::vec::Vec; +use std::boxed::Box; +use std::fs::{ read_to_string, read_dir }; +use std::path::PathBuf; + +use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; +use crate::messages::{ WindowMessage, WindowMessageResponse }; +use crate::framebuffer::Dimensions; +use crate::themes::ThemeInfo; +use crate::components::paragraph::Paragraph; +use crate::components::Component; + +pub struct Help { + dimensions: Dimensions, + file_index: usize, + files: Vec, + paragraph: Option>>, +} + +impl WindowLike for Help { + fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse { + match message { + WindowMessage::Init(dimensions) => { + self.dimensions = dimensions; + self.paragraph = Some(Box::new(Paragraph::new("help".to_string(), [2, 22], [self.dimensions[0] - 4, self.dimensions[1] - 24], "Press the 'h' and 'l' keys to read the different help pages".to_string(), ()))); + WindowMessageResponse::JustRedraw + }, + WindowMessage::KeyPress(key_press) => { + if key_press.key == 'h' || key_press.key == 'l' { + if key_press.key == 'h' { + if self.file_index == 0 { + self.file_index = self.files.len() - 1; + } else { + self.file_index -= 1; + } + } else { + if self.file_index == self.files.len() - 1 { + self.file_index = 0; + } else { + self.file_index += 1; + } + } + self.paragraph.as_mut().unwrap().new_text(read_to_string(self.files[self.file_index].clone()).unwrap()); + WindowMessageResponse::JustRedraw + } else if self.paragraph.as_mut().unwrap().handle_message(WindowMessage::KeyPress(key_press)).is_some() { + WindowMessageResponse::JustRedraw + } else { + WindowMessageResponse::DoNothing + } + }, + _ => WindowMessageResponse::DoNothing + } + } + + fn draw(&self, theme_info: &ThemeInfo) -> Vec { + let mut instructions = vec![DrawInstructions::Text([2, 2], vec!["times-new-romono".to_string()], self.files[self.file_index].clone().file_name().unwrap().to_string_lossy().to_string(), theme_info.text, theme_info.background, Some(0), None)]; + instructions.extend(self.paragraph.as_ref().unwrap().draw(theme_info)); + instructions + } + + //properties + fn title(&self) -> String { + "About".to_string() + } + + fn subtype(&self) -> WindowLikeType { + WindowLikeType::Window + } + + fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions { + [500, 600] + } +} + +impl Help { + pub fn new() -> Self { + let mut files = vec![PathBuf::from("docs/system/shortcuts.md")]; + for entry in read_dir("docs/window-likes").unwrap() { + files.push(entry.unwrap().path()); + } + Self { + dimensions: [0, 0], + file_index: 0, + files, + paragraph: None, + } + } +} + diff --git a/src/essential/lock_screen.rs b/src/essential/lock_screen.rs index 0b32d12..5231222 100644 --- a/src/essential/lock_screen.rs +++ b/src/essential/lock_screen.rs @@ -7,7 +7,9 @@ use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerReques use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; use blake2::{ Blake2b512, Digest }; -const PASSWORD_HASH: [u8; 64] = [220, 88, 183, 188, 240, 27, 107, 181, 58, 191, 198, 170, 114, 38, 7, 148, 6, 179, 75, 128, 231, 171, 172, 220, 85, 38, 36, 113, 116, 146, 70, 197, 163, 179, 158, 192, 130, 53, 247, 48, 47, 209, 95, 96, 179, 211, 4, 122, 254, 127, 21, 165, 139, 199, 151, 226, 216, 176, 123, 41, 194, 221, 58, 69]; +include!(concat!(env!("OUT_DIR"), "/password.rs")); + +//const PASSWORD_HASH: [u8; 64] = [220, 88, 183, 188, 240, 27, 107, 181, 58, 191, 198, 170, 114, 38, 7, 148, 6, 179, 75, 128, 231, 171, 172, 220, 85, 38, 36, 113, 116, 146, 70, 197, 163, 179, 158, 192, 130, 53, 247, 48, 47, 209, 95, 96, 179, 211, 4, 122, 254, 127, 21, 165, 139, 199, 151, 226, 216, 176, 123, 41, 194, 221, 58, 69]; pub struct LockScreen { dimensions: Dimensions, @@ -19,28 +21,28 @@ impl WindowLike for LockScreen { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { if key_press.key == '𐘂' { //the enter key //check password let mut hasher = Blake2b512::new(); - hasher.update(self.input_password.as_bytes()); + hasher.update((self.input_password.clone() + "salt?sorrycryptographers").as_bytes()); if hasher.finalize() == PASSWORD_HASH.into() { WindowMessageResponse::Request(WindowManagerRequest::Unlock) } else { self.input_password = String::new(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } } else if key_press.key == '𐘁' { //backspace let p_len = self.input_password.len(); if p_len != 0 { self.input_password = self.input_password[..p_len - 1].to_string(); } - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { self.input_password += &key_press.key.to_string(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } }, _ => WindowMessageResponse::DoNothing, diff --git a/src/essential/mod.rs b/src/essential/mod.rs index 39a401d..0800750 100644 --- a/src/essential/mod.rs +++ b/src/essential/mod.rs @@ -4,3 +4,6 @@ pub mod lock_screen; pub mod workspace_indicator; pub mod start_menu; +pub mod about; +pub mod help; + diff --git a/src/essential/start_menu.rs b/src/essential/start_menu.rs index cbae2b8..7c35feb 100644 --- a/src/essential/start_menu.rs +++ b/src/essential/start_menu.rs @@ -1,5 +1,3 @@ -#![allow(warnings)] - use std::vec; use std::vec::Vec; use std::boxed::Box; @@ -10,9 +8,6 @@ use crate::framebuffer::Dimensions; use crate::themes::ThemeInfo; use crate::components::Component; use crate::components::highlight_button::HighlightButton; -use crate::ipc::listen; - -//todo: move to essential static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "System", "Misc", "Help", "Logout"]; @@ -39,7 +34,7 @@ impl WindowLike for StartMenu { self.dimensions = dimensions; self.y_each = (self.dimensions[1] - 1) / CATEGORIES.len(); self.add_category_components(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::KeyPress(key_press) => { //up and down @@ -62,7 +57,7 @@ impl WindowLike for StartMenu { self.old_focus = self.current_focus.to_string(); self.current_focus = self.components[current_focus_index].name().to_string(); self.components[current_focus_index].handle_message(WindowMessage::Focus); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else if key_press.key == '𐘂' { //the enter key let focus_index = self.get_focus_index(); if let Some(focus_index) = focus_index { @@ -79,7 +74,7 @@ impl WindowLike for StartMenu { self.old_focus = self.current_focus.clone(); self.current_focus = self.components[current_focus_index + n_index].name().to_string(); self.components[current_focus_index + n_index].handle_message(WindowMessage::Focus); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } else { WindowMessageResponse::DoNothing } @@ -135,11 +130,14 @@ impl StartMenu { StartMenuMessage::CategoryClick(name) => { if name == "Logout" { WindowMessageResponse::Request(WindowManagerRequest::Lock) + } else if name == "About" || name == "Help" { + //todo above: also do the same for Help + WindowMessageResponse::Request(WindowManagerRequest::OpenWindow(name.to_string())) } else { self.current_focus = "Back".to_string(); self.components = vec![ Box::new(HighlightButton::new( - "Back".to_string(), [42, 1], [self.dimensions[0] - 42 - 1, self.y_each], "Back", StartMenuMessage::Back, StartMenuMessage::ChangeAcknowledge, true + "Back".to_string(), [42, 1], [self.dimensions[0] - 42 - 1, self.y_each], "Back".to_string(), StartMenuMessage::Back, StartMenuMessage::ChangeAcknowledge, true )) ]; //add window buttons @@ -157,10 +155,10 @@ impl StartMenu { for a in 0..to_add.len() { let w_name = to_add[a]; self.components.push(Box::new(HighlightButton::new( - w_name.to_string(), [42, (a + 1) * self.y_each], [self.dimensions[0] - 42 - 1, self.y_each], w_name, StartMenuMessage::WindowClick(w_name), StartMenuMessage::ChangeAcknowledge, false + w_name.to_string(), [42, (a + 1) * self.y_each], [self.dimensions[0] - 42 - 1, self.y_each], w_name.to_string(), StartMenuMessage::WindowClick(w_name), StartMenuMessage::ChangeAcknowledge, false ))); } - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } }, StartMenuMessage::WindowClick(name) => { @@ -169,15 +167,15 @@ impl StartMenu { }, StartMenuMessage::Back => { self.add_category_components(); - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, StartMenuMessage::ChangeAcknowledge => { // - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, } } else { - //maybe should be JustRerender? + //maybe should be JustRedraw? WindowMessageResponse::DoNothing } } @@ -188,7 +186,7 @@ impl StartMenu { for c in 0..CATEGORIES.len() { let name = CATEGORIES[c]; self.components.push(Box::new(HighlightButton::new( - name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name, StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0 + name.to_string(), [42, self.y_each * c + 1], [self.dimensions[0] - 42 - 1, self.y_each], name.to_string(), StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0 ))); } } @@ -198,7 +196,3 @@ impl StartMenu { } } -pub fn main() { - listen(StartMenu::new()); -} - diff --git a/src/essential/taskbar.rs b/src/essential/taskbar.rs index cbbaae8..3f231a3 100644 --- a/src/essential/taskbar.rs +++ b/src/essential/taskbar.rs @@ -7,7 +7,7 @@ use crate::messages::{ WindowMessage, WindowMessageResponse, WindowManagerReques use crate::framebuffer::Dimensions; use crate::themes::ThemeInfo; use crate::components::Component; -use crate::components::toggle_button::{ ToggleButton, ToggleButtonAlignment }; +use crate::components::toggle_button::ToggleButton; const PADDING: usize = 4; const META_WIDTH: usize = 175; //of the window button @@ -33,9 +33,9 @@ impl WindowLike for Taskbar { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; self.components = vec![ - Box::new(ToggleButton::new("start-button".to_string(), [PADDING, PADDING], [44, self.dimensions[1] - (PADDING * 2)], "Start".to_string(), TaskbarMessage::ShowStartMenu, TaskbarMessage::HideStartMenu, false, Some(ToggleButtonAlignment::Left))), + Box::new(ToggleButton::new("start-button".to_string(), [PADDING, PADDING], [44, self.dimensions[1] - (PADDING * 2)], "Start".to_string(), TaskbarMessage::ShowStartMenu, TaskbarMessage::HideStartMenu, false)), ]; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::Shortcut(shortcut) => { match shortcut { @@ -52,7 +52,7 @@ impl WindowLike for Taskbar { InfoType::WindowsInWorkspace(windows, focused_id) => { self.windows_in_workspace = windows; self.focused_id = focused_id; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw } _ => WindowMessageResponse::DoNothing, } @@ -80,7 +80,7 @@ impl WindowLike for Taskbar { } let info = &self.windows_in_workspace[wi]; let name = &info.1; - let mut b = ToggleButton::new(name.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], name.to_string(), TaskbarMessage::Nothing, TaskbarMessage::Nothing, false, Some(ToggleButtonAlignment::Left)); + let mut b = ToggleButton::new(name.to_string() + "-window", [PADDING * 2 + 44 + (META_WIDTH + PADDING) * wi, PADDING], [META_WIDTH, self.dimensions[1] - (PADDING * 2)], name.to_string(), TaskbarMessage::Nothing, TaskbarMessage::Nothing, false); b.inverted = info.0 == self.focused_id; instructions.extend(b.draw(theme_info)); } @@ -119,7 +119,7 @@ impl Taskbar { _ => WindowMessageResponse::DoNothing, } } else { - //maybe should be JustRerender? + //maybe should be JustRedraw? WindowMessageResponse::DoNothing } } diff --git a/src/essential/workspace_indicator.rs b/src/essential/workspace_indicator.rs index 287ef22..95a5a80 100644 --- a/src/essential/workspace_indicator.rs +++ b/src/essential/workspace_indicator.rs @@ -22,13 +22,13 @@ impl WindowLike for WorkspaceIndicator { match message { WindowMessage::Init(dimensions) => { self.dimensions = dimensions; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, WindowMessage::Shortcut(shortcut) => { match shortcut { ShortcutType::SwitchWorkspace(workspace) => { self.current_workspace = workspace; - WindowMessageResponse::JustRerender + WindowMessageResponse::JustRedraw }, _ => WindowMessageResponse::DoNothing, } diff --git a/src/messages.rs b/src/messages.rs index c604fcc..132ff80 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -43,7 +43,7 @@ impl fmt::Debug for WindowManagerRequest{ #[derive(PartialEq, Debug, Serialize, Deserialize)] pub enum WindowMessageResponse { Request(WindowManagerRequest), - JustRerender, + JustRedraw, DoNothing, } diff --git a/src/proxy_window_like.rs b/src/proxy_window_like.rs index a1ee776..ae766ac 100644 --- a/src/proxy_window_like.rs +++ b/src/proxy_window_like.rs @@ -24,7 +24,7 @@ impl WindowLike for ProxyWindowLike { let _ = stdin.write_all(("handle_message ".to_string() + &ron::to_string(&message).unwrap() + "\n").as_bytes()); } let output = self.read_line(); - ron::from_str(&output).unwrap_or(WindowMessageResponse::JustRerender) + ron::from_str(&output).unwrap_or(WindowMessageResponse::JustRedraw) } fn draw(&self, theme_info: &ThemeInfo) -> Vec { diff --git a/src/window_manager.rs b/src/window_manager.rs index fd3356d..f4a9d97 100644 --- a/src/window_manager.rs +++ b/src/window_manager.rs @@ -3,9 +3,9 @@ use std::vec; use std::collections::{ HashMap, VecDeque }; use std::fmt; use std::boxed::Box; -use std::sync::{ LazyLock, Mutex }; use std::io::{ stdin, stdout, Write }; use std::process::exit; +use std::cell::RefCell; use linux_framebuffer::Framebuffer; use termion::input::TermRead; @@ -23,24 +23,26 @@ use crate::essential::taskbar::Taskbar; use crate::essential::lock_screen::LockScreen; use crate::essential::workspace_indicator::WorkspaceIndicator; use crate::essential::start_menu::StartMenu; -use crate::logging::log; +use crate::essential::about::About; +use crate::essential::help::Help; +//use crate::logging::log; pub const TASKBAR_HEIGHT: usize = 38; pub const INDICATOR_HEIGHT: usize = 20; const WINDOW_TOP_HEIGHT: usize = 26; -static WRITER: LazyLock> = LazyLock::new(|| Mutex::new(Default::default())); - pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { let dimensions = [framebuffer_info.width, framebuffer_info.height]; println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT); - WRITER.lock().unwrap().init(framebuffer_info.clone()); + let mut writer: FramebufferWriter = Default::default(); - let mut wm: WindowManager = WindowManager::new(framebuffer, dimensions); + writer.init(framebuffer_info.clone()); - wm.render(None, false); + let mut wm: WindowManager = WindowManager::new(writer, framebuffer, dimensions); + + wm.draw(None, false); let stdin = stdin().lock(); let mut stdout = stdout().into_raw_mode().unwrap(); @@ -52,8 +54,8 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) { 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 { + //if kc == KeyChar::Alt('E') { + if kc == KeyChar::Alt('E') && !wm.locked { write!(stdout, "{}", cursor::Show).unwrap(); stdout.suspend_raw_mode().unwrap(); exit(0); @@ -130,6 +132,7 @@ impl fmt::Debug for WindowLikeInfo { } pub struct WindowManager { + writer: RefCell, id_count: usize, window_infos: Vec, dimensions: Dimensions, @@ -144,8 +147,9 @@ pub struct WindowManager { //1 is up, 2 is down impl WindowManager { - pub fn new(framebuffer: Framebuffer, dimensions: Dimensions) -> Self { + pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions) -> Self { let mut wm = WindowManager { + writer: RefCell::new(writer), id_count: 0, window_infos: Vec::new(), dimensions, @@ -186,7 +190,7 @@ impl WindowManager { self.window_infos.iter().position(|w| w.id == self.focused_id) } - //should return an iterator but fuck it! + //used to return Vec<&WindowLikeInfo>, doesn't anymore for good reason fn get_windows_in_workspace(&self, include_non_window: bool) -> Vec<&WindowLikeInfo> { self.window_infos.iter().filter(|w| { match w.workspace { @@ -303,7 +307,7 @@ impl WindowManager { //send to taskbar press_response = self.toggle_start_menu(false); if press_response != WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu) { - //only thing that needs to be rerendered is the start menu and taskbar + //only thing that needs to be redrawed is the start menu and taskbar let start_menu_id = self.id_count + 1; let taskbar_id = self.window_infos.iter().find(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap().id; redraw_ids = Some(vec![start_menu_id, taskbar_id]); @@ -354,7 +358,7 @@ impl WindowManager { } } if changed { - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; //avoid drawing everything under the moving window, much more efficient use_saved_buffer = true; redraw_ids = Some(vec![self.focused_id]); @@ -375,7 +379,7 @@ impl WindowManager { self.focused_id = self.window_infos[indicator_index].id; self.window_infos[indicator_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::SwitchWorkspace(self.current_workspace))); self.taskbar_update_windows(); - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; } }, &ShortcutType::MoveWindowToWorkspace(workspace) => { @@ -384,7 +388,7 @@ impl WindowManager { if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window { self.window_infos[focused_index].workspace = Workspace::Workspace(workspace); self.taskbar_update_windows(); - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; } } } @@ -411,7 +415,7 @@ impl WindowManager { //elevate it to the top self.move_index_to_top(new_focus_index); self.taskbar_update_windows(); - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; break; } else if new_focus_index == current_index { break; //did a full loop, found no windows @@ -423,7 +427,7 @@ impl WindowManager { if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window { self.window_infos.remove(focused_index); self.taskbar_update_windows(); - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; } } }, @@ -432,7 +436,7 @@ impl WindowManager { let window_dimensions = &self.window_infos[focused_index].dimensions; self.window_infos[focused_index].top_left = [self.dimensions[0] / 2 - window_dimensions[0] / 2, self.dimensions[1] / 2 - window_dimensions[1] / 2]; use_saved_buffer = true; - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; } }, &ShortcutType::FullscreenWindow => { @@ -451,7 +455,7 @@ impl WindowManager { new_dimensions = self.window_infos[focused_index].dimensions; } self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT])); - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; } } }, @@ -465,7 +469,7 @@ impl WindowManager { let new_dimensions = [self.dimensions[0] / 2, self.dimensions[1] - INDICATOR_HEIGHT - TASKBAR_HEIGHT]; self.window_infos[focused_index].dimensions = new_dimensions; self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions([new_dimensions[0], new_dimensions[1] - WINDOW_TOP_HEIGHT])); - press_response = WindowMessageResponse::JustRerender; + press_response = WindowMessageResponse::JustRedraw; } } }, @@ -503,10 +507,10 @@ impl WindowManager { key: c, }) }); - //at most, only the focused window needs to be rerendered + //at most, only the focused window needs to be redrawed redraw_ids = Some(vec![self.window_infos[focused_index].id]); //requests can result in window openings and closings, etc - if press_response != WindowMessageResponse::JustRerender { + if press_response != WindowMessageResponse::JustRedraw { redraw_ids = None; } } @@ -521,7 +525,7 @@ impl WindowManager { WindowMessageResponse::Request(request) => self.handle_request(request), _ => {}, }; - self.render(redraw_ids, use_saved_buffer); + self.draw(redraw_ids, use_saved_buffer); } } @@ -541,6 +545,8 @@ impl WindowManager { "Audio Player" => Some(Box::new(ProxyWindowLike::new_rust("audio_player"))), "File Explorer" => Some(Box::new(ProxyWindowLike::new_rust("file_explorer"))), "StartMenu" => Some(Box::new(StartMenu::new())), + "About" => Some(Box::new(About::new())), + "Help" => Some(Box::new(Help::new())), _ => None, }; if w.is_none() { @@ -581,7 +587,6 @@ impl WindowManager { }, WindowManagerRequest::ClipboardCopy(content) => { self.clipboard = Some(content); - log(&format!("{:?}", self.clipboard)); }, }; } @@ -591,18 +596,18 @@ impl WindowManager { } //another issue with a huge vector of draw instructions; it takes up heap memory - pub fn render(&mut self, maybe_redraw_ids: Option>, use_saved_buffer: bool) { + pub fn draw(&mut self, maybe_redraw_ids: Option>, use_saved_buffer: bool) { let theme_info = get_theme_info(&self.theme).unwrap(); //use in conjunction with redraw ids, so a window moving can work without redrawing everything, //can just redraw the saved state + window if use_saved_buffer { - WRITER.lock().unwrap().write_saved_buffer_to_raw(); + self.writer.borrow_mut().write_saved_buffer_to_raw(); } //get windows to redraw let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new()); - let redraw_windows = self.get_windows_in_workspace(true); - let maybe_length = redraw_windows.len(); - let redraw_windows = redraw_windows.iter().filter(|w| { + let all_in_workspace = self.get_windows_in_workspace(true); + 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) @@ -614,7 +619,6 @@ impl WindowManager { let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1; let mut w_index = 0; for window_info in redraw_windows { - //unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", &window_info.window_like.subtype())); } let window_dimensions = if window_info.fullscreen { [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT] } else { @@ -625,7 +629,7 @@ impl WindowManager { if is_window { //if this is the top most window to draw, snapshot if w_index == max_index && !use_saved_buffer && redraw_ids.len() == 0 { - WRITER.lock().unwrap().save_buffer(); + self.writer.borrow_mut().save_buffer(); } //offset top left by the window top height for windows (because windows can't draw in that region) instructions = instructions.iter().map(|instruction| { @@ -654,7 +658,7 @@ impl WindowManager { DrawInstructions::Rect([1, window_dimensions[1] - 1], [window_dimensions[0] - 1, 1], theme_info.border_right_bottom), ]); } - let mut framebuffer_info = WRITER.lock().unwrap().get_info(); + let mut framebuffer_info = self.writer.borrow().get_info(); let bytes_per_pixel = framebuffer_info.bytes_per_pixel; let window_width = window_dimensions[0]; let window_height = window_dimensions[1]; @@ -690,9 +694,9 @@ impl WindowManager { }, } } - WRITER.lock().unwrap().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.get_buffer()); + 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(WRITER.lock().unwrap().get_buffer()); + self.framebuffer.write_frame(self.writer.borrow().get_buffer()); } }