change project structure to make more sense

(move wm only stuff to wm dir)
This commit is contained in:
stjet
2025-04-21 06:57:42 +00:00
parent c5a41244b4
commit 667b4cd2d9
31 changed files with 87 additions and 71 deletions

14
wm/Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "wm"
version = "1.0.3"
repository = "https://github.com/stjet/ming-wm"
license = "GPL-3.0-or-later"
edition = "2021"
[dependencies]
ming-wm-lib = { path = "../ming-wm-lib" }
linux = { path = "linux" }
blake2 = { version = "0.10.6", default-features = false }
termion = { version = "4.0.3" }
bmp-rust = "0.5.0"

8
wm/linux/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "linux"
version = "0.1.0"
license = "GPL-3.0-or-later"
edition = "2021"
[dependencies]
libc = "0.2"

146
wm/linux/src/fb.rs Normal file
View File

@@ -0,0 +1,146 @@
use std::fs::{ File, OpenOptions };
use std::os::fd::AsRawFd;
use std::ptr;
use libc::{ ioctl, mmap, munmap, c_ulong, c_int };
//https://stackoverflow.com/a/75402838
//https://github.com/torvalds/linux/blob/master/include/uapi/linux/fb.h
const FBIOGET_VSCREENINFO: c_ulong = 0x4600;
const FBIOGET_FSCREENINFO: c_ulong = 0x4602;
//https://www.kernel.org/doc/html/latest/fb/api.html
#[derive(Default)]
#[repr(C)]
pub struct FB_BITFIELD {
offset: u32,
length: u32,
msb_right: u32,
}
#[derive(Default)]
#[repr(C)]
pub struct FB_VAR_SCREENINFO {
pub xres: u32,
pub yres: u32,
pub xres_virtual: u32,
pub yres_virtual: u32,
pub xoffset: u32,
pub yoffset: u32,
pub bits_per_pixel: u32,
pub grayscale: u32,
pub red: FB_BITFIELD,
pub green: FB_BITFIELD,
pub blue: FB_BITFIELD,
pub transp: FB_BITFIELD,
pub nonstd: u32,
pub activate: u32,
pub height: u32,
pub width: u32,
pub accel_flags: u32,
pub pixclock: u32,
pub left_margin: u32,
pub right_margin: u32,
pub upper_margin: u32,
pub lower_margin: u32,
pub hsync_len: u32,
pub wsync_len: u32,
pub sync: u32,
pub vmode: u32,
pub rotate: u32,
pub colorspace: u32,
pub reserved: [u32; 4],
}
#[derive(Default)]
#[repr(C)]
pub struct FB_FIX_SCREENINFO {
pub id: [u8; 16],
pub smem_start: usize,
pub smem_len: u32,
pub r#type: u32,
pub type_aux: u32,
pub visual: u32,
pub xpanstep: u16,
pub ypanstep: u16,
pub ywrapstep: u16,
pub line_length: u32,
pub mmio_len: u32,
pub accel: u32,
pub capabilities: u16,
pub reserved: [u16; 2],
}
pub struct Framebuffer {
pointer: *mut libc::c_void,
pub var_screen_info: FB_VAR_SCREENINFO,
pub fix_screen_info: FB_FIX_SCREENINFO,
size: usize,
}
impl Framebuffer {
pub fn open(path: &str) -> Result<Self, ()> {
let file = Framebuffer::open_file(path)?;
let vi = Framebuffer::get_vscreeninfo(file.as_raw_fd())?;
let fi = Framebuffer::get_fscreeninfo(file.as_raw_fd())?;
//then mmap or something
let size = vi.yres_virtual * fi.line_length * (vi.bits_per_pixel / 8);
let pointer = unsafe {
mmap(ptr::null_mut(), size.try_into().unwrap(), libc::PROT_READ | libc::PROT_WRITE, libc::MAP_SHARED, file.as_raw_fd(), 0)
};
if pointer == libc::MAP_FAILED {
return Err(());
}
Ok(Self {
pointer,
var_screen_info: vi,
fix_screen_info: fi,
size: size as usize,
})
}
fn open_file(path: &str) -> Result<File, ()> {
OpenOptions::new().read(true).write(true).open(path).map_err(|_| ())
}
fn get_vscreeninfo(raw_fd: c_int) -> Result<FB_VAR_SCREENINFO, ()> {
let mut vi: FB_VAR_SCREENINFO = Default::default();
let result = unsafe {
ioctl(raw_fd, FBIOGET_VSCREENINFO.try_into().unwrap(), &mut vi)
};
if result != -1 {
Ok(vi)
} else {
Err(())
}
}
fn get_fscreeninfo(raw_fd: c_int) -> Result<FB_FIX_SCREENINFO, ()> {
let mut fi: FB_FIX_SCREENINFO = Default::default();
let result = unsafe {
ioctl(raw_fd, FBIOGET_FSCREENINFO.try_into().unwrap(), &mut fi)
};
if result != -1 {
Ok(fi)
} else {
Err(())
}
}
pub fn write_frame(&mut self, frame: &[u8]) {
unsafe {
ptr::copy_nonoverlapping(frame.as_ptr(), self.pointer as *mut u8, frame.len());
};
}
}
impl Drop for Framebuffer {
fn drop(&mut self) {
unsafe {
munmap(self.pointer, self.size);
}
}
}

1
wm/linux/src/keys.rs Normal file
View File

@@ -0,0 +1 @@
//

2
wm/linux/src/lib.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod fb;
pub mod raw;

65
wm/linux/src/raw.rs Normal file
View File

@@ -0,0 +1,65 @@
use std::io::Stdout;
use std::mem::zeroed;
use std::os::fd::AsRawFd;
use libc::{ cfmakeraw, c_int, tcgetattr, tcsetattr, termios, TCSAFLUSH };
//https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
//on TCSAFLUSH: "The TCSAFLUSH argument specifies when to apply the change: in this case, it waits for all pending output to be written to the terminal, and also discards any input that hasn't been read."
//https://www.man7.org/linux/man-pages/man3/termios.3.html
//(use cfmakeraw instead doing all those bitwise stuff manually)
//enter and exit tty raw mode
pub struct RawStdout {
pub stdout: Stdout,
old_termios: termios,
}
impl RawStdout {
pub fn new(stdout: Stdout) -> Self {
RawStdout {
stdout,
old_termios: unsafe { zeroed() },
}
}
pub fn get_termios(raw_fd: c_int) -> Result<termios, ()> {
let mut termios_struct: termios = unsafe { zeroed() };
let result = unsafe {
tcgetattr(raw_fd, &mut termios_struct)
};
if result != -1 {
Ok(termios_struct)
} else {
Err(())
}
}
pub fn enter_raw_mode(&mut self) -> Result<(), ()> {
let raw_fd = self.stdout.as_raw_fd();
let mut termios_struct = Self::get_termios(raw_fd)?;
self.old_termios = termios_struct;
let result = unsafe {
cfmakeraw(&mut termios_struct);
tcsetattr(raw_fd, TCSAFLUSH, &mut termios_struct)
};
if result != -1 {
Ok(())
} else {
Err(())
}
}
pub fn exit_raw_mode(&mut self) -> Result<(), ()> {
let result = unsafe {
tcsetattr(self.stdout.as_raw_fd(), TCSAFLUSH, &mut self.old_termios)
};
if result != -1 {
Ok(())
} else {
Err(())
}
}
}

63
wm/src/essential/about.rs Normal file
View File

@@ -0,0 +1,63 @@
use std::vec::Vec;
use std::boxed::Box;
use std::fs::read_to_string;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::dirs::exe_dir;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::paragraph::Paragraph;
pub struct About {
dimensions: Dimensions,
components: Vec<Box<dyn Component<()> + 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(exe_dir(Some("ming_docs/system/README.md"))).unwrap_or("ming_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<DrawInstructions> {
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(),
}
}
}

View File

@@ -0,0 +1,81 @@
use std::vec;
use std::vec::Vec;
use std::fs::File;
use std::io::Read;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType, TASKBAR_HEIGHT, INDICATOR_HEIGHT };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, ShortcutType };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::{ hex_to_u8, is_hex };
use ming_wm_lib::dirs::config_dir;
pub struct DesktopBackground {
dimensions: Dimensions,
current_workspace: u8,
}
impl WindowLike for DesktopBackground {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRedraw
},
WindowMessage::Shortcut(shortcut) => {
match shortcut {
ShortcutType::SwitchWorkspace(workspace) => {
self.current_workspace = workspace;
WindowMessageResponse::JustRedraw
},
_ => WindowMessageResponse::DoNothing,
}
},
_ => WindowMessageResponse::DoNothing,
}
}
//simple
fn draw(&self, _theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
if let Ok(mut file) = File::open(format!("{}/ming-wm/desktop-background", config_dir().unwrap().into_os_string().into_string().unwrap())) {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let lines: Vec<&str> = contents.split("\n").collect();
if lines.len() > self.current_workspace.into() {
let line = lines[self.current_workspace as usize];
if line.starts_with("#") && line.len() == 7 {
let line_hex = &line[1..];
//if all characters are valid hex
if line_hex.find(|c| !is_hex(c)).is_none() {
let mut chars = line_hex.chars();
let color = [hex_to_u8(chars.next().unwrap(), chars.next().unwrap()), hex_to_u8(chars.next().unwrap(), chars.next().unwrap()), hex_to_u8(chars.next().unwrap(), chars.next().unwrap())];
return vec![DrawInstructions::Rect([0, 0], self.dimensions, color)];
}
} else if line.len() > 1 {
//first character of line is either r or any other character, but is not part of the path
return vec![DrawInstructions::Bmp([0, 0], line[1..].to_string(), line.chars().next().unwrap() == 'r')];
}
}
}
vec![DrawInstructions::Rect([0, 0], self.dimensions, [0, 128, 128])]
}
//properties
fn subtype(&self) -> WindowLikeType {
WindowLikeType::DesktopBackground
}
fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions {
[dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]
}
}
impl DesktopBackground {
pub fn new() -> Self {
Self {
dimensions: [0, 0],
current_workspace: 0,
}
}
}

104
wm/src/essential/help.rs Normal file
View File

@@ -0,0 +1,104 @@
use std::vec::Vec;
use std::boxed::Box;
use std::fs::{ read_to_string, read_dir };
use std::path::PathBuf;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::dirs::exe_dir;
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::components::paragraph::Paragraph;
use ming_wm_lib::components::Component;
pub struct Help {
dimensions: Dimensions,
file_index: usize,
files: Vec<PathBuf>,
paragraph: Option<Box<Paragraph<()>>>,
}
impl WindowLike for Help {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
let first_content = if self.files.len() > 0 {
read_to_string(self.files[0].clone()).unwrap()
} else {
String::new()
};
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 (or the left and right arrow keys) to read the different help pages".to_string() + &first_content, ())));
WindowMessageResponse::JustRedraw
},
WindowMessage::KeyPress(key_press) => {
if key_press.key == 'h' || key_press.is_left_arrow() || key_press.key == 'l' || key_press.is_right_arrow() {
if key_press.key == 'h' || key_press.is_left_arrow() {
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;
}
}
if self.files.len() > 0 {
self.paragraph.as_mut().unwrap().new_text(read_to_string(self.files[self.file_index].clone()).unwrap());
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} 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<DrawInstructions> {
let mut instructions = Vec::new();
if self.files.len() > 0 {
instructions.push(DrawInstructions::Text([2, 2], vec!["nimbus-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 {
"Help".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::new();
if let Ok(contents) = read_dir(exe_dir(Some("ming_docs/window-likes"))) {
files.push(exe_dir(Some("ming_docs/system/shortcuts.md")));
for entry in contents {
files.push(entry.unwrap().path());
}
}
Self {
dimensions: [0, 0],
file_index: 0,
files,
paragraph: None,
}
}
}

View File

@@ -0,0 +1,83 @@
use std::vec;
use std::vec::Vec;
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
use ming_wm_lib::window_manager_types::{ 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];
pub struct LockScreen {
dimensions: Dimensions,
input_password: String,
password_hash: [u8; 64],
}
impl WindowLike for LockScreen {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRedraw
},
WindowMessage::KeyPress(key_press) => {
if key_press.is_enter() {
//check password
let mut hasher = Blake2b512::new();
hasher.update((self.input_password.clone() + "salt?sorrycryptographers").as_bytes());
if hasher.finalize() == self.password_hash.into() {
WindowMessageResponse::Request(WindowManagerRequest::Unlock)
} else {
self.input_password = String::new();
WindowMessageResponse::JustRedraw
}
} else if key_press.is_backspace() {
let p_len = self.input_password.len();
if p_len != 0 {
self.input_password = self.input_password[..p_len - 1].to_string();
}
WindowMessageResponse::JustRedraw
} else if key_press.is_regular() {
self.input_password += &key_press.key.to_string();
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
},
_ => WindowMessageResponse::DoNothing,
}
}
fn draw(&self, _theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
vec![
DrawInstructions::Rect([0, 0], self.dimensions, [0, 0, 0]),
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], "The bulldozer outside the kitchen window was quite a big one.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16], vec!["nimbus-roman".to_string()], "\"Yellow,\" he thought, and stomped off back to his bedroom to get dressed.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16 * 2], vec!["nimbus-roman".to_string()], "He stared at it.".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([4, 4 + 16 * 3], vec!["nimbus-roman".to_string()], "Password: ".to_string(), [255, 255, 255], [0, 0, 0], None, None),
DrawInstructions::Text([80, 4 + 16 * 3], vec!["nimbus-roman".to_string()], "*".repeat(self.input_password.len()), [255, 255, 255], [0, 0, 0], None, None),
]
}
//properties
fn subtype(&self) -> WindowLikeType {
WindowLikeType::LockScreen
}
fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions {
dimensions //fullscreen
}
}
impl LockScreen {
pub fn new(password_hash: [u8; 64]) -> Self {
Self {
dimensions: [0, 0],
input_password: String::new(),
password_hash,
}
}
}

10
wm/src/essential/mod.rs Normal file
View File

@@ -0,0 +1,10 @@
pub mod desktop_background;
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;

View File

@@ -0,0 +1,201 @@
use std::vec;
use std::vec::Vec;
use std::collections::HashMap;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType, KeyChar };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::utils::point_inside;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::press_button::PressButton;
//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<Box<PressButton<KeyResponse>>>,
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();
self.set_key_components();
WindowMessageResponse::JustRedraw
},
};
}
}
}
WindowMessageResponse::DoNothing
},
_ => WindowMessageResponse::DoNothing,
}
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
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] - 180, 250]
}
}
impl OnscreenKeyboard {
pub fn new() -> Self {
Default::default()
}
fn set_key_components(&mut self) {
self.components = Vec::new();
let rows: [HashMap<Board, Vec<char>>; 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;
}
}
}

View File

@@ -0,0 +1,206 @@
use std::vec;
use std::vec::Vec;
use std::boxed::Box;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::dirs::exe_dir;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::highlight_button::HighlightButton;
use crate::fs::{ ExeWindowInfos, get_all_executable_windows };
static CATEGORIES: [&'static str; 9] = ["About", "Utils", "Games", "Editing", "Files", "Internet", "Misc", "Help", "Lock"];
#[derive(Clone)]
enum StartMenuMessage {
CategoryClick(&'static str),
WindowClick(String),
Back,
ChangeAcknowledge,
}
#[derive(Default)]
pub struct StartMenu {
dimensions: Dimensions,
executable_windows: ExeWindowInfos,
components: Vec<Box<HighlightButton<StartMenuMessage>>>,
current_focus: String,
y_each: usize,
}
impl WindowLike for StartMenu {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
self.y_each = (self.dimensions[1] - 1) / CATEGORIES.len();
self.add_category_components();
self.executable_windows = get_all_executable_windows();
WindowMessageResponse::JustRedraw
},
WindowMessage::KeyPress(key_press) => {
//up and down
if key_press.key == 'j' || key_press.is_down_arrow() || key_press.key == 'k' || key_press.is_up_arrow() {
let old_focus_index = self.get_focus_index().unwrap();
self.components[old_focus_index].handle_message(WindowMessage::Unfocus);
let current_focus_index = if key_press.key == 'j' || key_press.is_down_arrow() {
if old_focus_index + 1 == self.components.len() {
0
} else {
old_focus_index + 1
}
} else {
if old_focus_index == 0 {
self.components.len() - 1
} else {
old_focus_index - 1
}
};
self.current_focus = self.components[current_focus_index].name().to_string();
self.components[current_focus_index].handle_message(WindowMessage::Focus);
WindowMessageResponse::JustRedraw
} else if key_press.is_enter() {
let focus_index = self.get_focus_index();
if let Some(focus_index) = focus_index {
let r = self.components[focus_index].handle_message(WindowMessage::FocusClick);
self.handle_start_menu_message(r)
} else {
WindowMessageResponse::DoNothing
}
} else {
let current_focus_index = self.get_focus_index().unwrap();
if key_press.key.is_lowercase() {
//look forwards to see category/window that starts with that char
if let Some(n_index) = self.components[current_focus_index..].iter().position(|c| c.text.chars().next().unwrap_or('𐘂').to_lowercase().next().unwrap() == key_press.key) {
//now old focus, not current focus
self.components[current_focus_index].handle_message(WindowMessage::Unfocus);
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::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else {
//look backwards to see category/window that starts with that char
if let Some(n_index) = self.components[..current_focus_index].iter().rev().position(|c| c.text.chars().next().unwrap_or('𐘂').to_uppercase().next().unwrap() == key_press.key) {
//now old focus, not current focus
self.components[current_focus_index].handle_message(WindowMessage::Unfocus);
self.current_focus = self.components[current_focus_index - n_index - 1].name().to_string();
self.components[current_focus_index - n_index - 1].handle_message(WindowMessage::Focus);
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
}
}
},
_ => WindowMessageResponse::DoNothing,
}
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![
//top thin border
DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top),
//right thin border
DrawInstructions::Rect([self.dimensions[0] - 1, 0], [1, self.dimensions[1]], theme_info.border_right_bottom),
//background
DrawInstructions::Rect([0, 1], [self.dimensions[0] - 1, self.dimensions[1] - 1], theme_info.background),
//ming-wm logo
DrawInstructions::Bmp([2, 2], exe_dir(Some("ming_bmps/ming-wm.bmp")).to_string_lossy().to_string(), true),
//I truly don't know why, it should be - 44 but - 30 seems to work better :shrug:
DrawInstructions::Gradient([2, 42], [40, self.dimensions[1] - 30], [255, 201, 14], [225, 219, 100], 15), //[225, 219, 77]
];
let max_per_page = CATEGORIES.len();
let current_focus = self.get_focus_index().unwrap();
for (index, component) in self.components.iter().enumerate() {
//supports multiple pages of window options per category
if (index >= max_per_page && current_focus >= max_per_page) || (index < max_per_page && current_focus < max_per_page) {
instructions.extend(component.draw(theme_info));
}
}
instructions
}
//properties
fn subtype(&self) -> WindowLikeType {
WindowLikeType::StartMenu
}
fn ideal_dimensions(&self, _dimensions: Dimensions) -> Dimensions {
[180, 250]
}
}
impl StartMenu {
pub fn new() -> Self {
Default::default()
}
fn handle_start_menu_message(&mut self, message: Option<StartMenuMessage>) -> WindowMessageResponse {
if let Some(message) = message {
match message {
StartMenuMessage::CategoryClick(name) => {
if name == "Lock" {
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".to_string(), StartMenuMessage::Back, StartMenuMessage::ChangeAcknowledge, true
))
];
//add window buttons
if let Some(to_add) = self.executable_windows.get(&("ming".to_string() + name)) {
let max_per_page = CATEGORIES.len();
//starts at 1 because of back button
for a in 1..=to_add.len() {
let ta = &to_add[a - 1];
//the modulo is for multiple pages for windows per category
self.components.push(Box::new(HighlightButton::new(
ta.1.to_string(), [42, (a % max_per_page) * self.y_each], [self.dimensions[0] - 42 - 1, self.y_each], ta.0.to_string(), StartMenuMessage::WindowClick(ta.1.clone()), StartMenuMessage::ChangeAcknowledge, false
)));
}
}
WindowMessageResponse::JustRedraw
}
},
StartMenuMessage::WindowClick(name) => {
//open the selected window
WindowMessageResponse::Request(WindowManagerRequest::OpenWindow(name.to_string()))
},
StartMenuMessage::Back => {
self.add_category_components();
WindowMessageResponse::JustRedraw
},
StartMenuMessage::ChangeAcknowledge => {
//
WindowMessageResponse::JustRedraw
},
}
} else {
//maybe should be JustRedraw?
WindowMessageResponse::DoNothing
}
}
pub fn add_category_components(&mut self) {
self.current_focus = "About".to_string();
self.components = Vec::new();
for (c, name) in CATEGORIES.iter().enumerate() {
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.to_string(), StartMenuMessage::CategoryClick(name), StartMenuMessage::ChangeAcknowledge, c == 0
)));
}
}
pub fn get_focus_index(&self) -> Option<usize> {
self.components.iter().filter(|c| c.focusable()).position(|c| c.name() == &self.current_focus)
}
}

127
wm/src/essential/taskbar.rs Normal file
View File

@@ -0,0 +1,127 @@
use std::vec;
use std::vec::Vec;
use std::boxed::Box;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType, TASKBAR_HEIGHT };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, WindowManagerRequest, ShortcutType, InfoType, WindowsVec };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::components::Component;
use ming_wm_lib::components::toggle_button::ToggleButton;
const PADDING: usize = 4;
const META_WIDTH: usize = 175; //of the window button
#[derive(Clone)]
enum TaskbarMessage {
ShowStartMenu,
HideStartMenu,
Nothing,
//
}
pub struct Taskbar {
dimensions: Dimensions,
components: Vec<Box<dyn Component<TaskbarMessage> + Send>>,
windows_in_workspace: WindowsVec,
focused_id: usize,
}
impl WindowLike for Taskbar {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
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)),
];
WindowMessageResponse::JustRedraw
},
WindowMessage::Shortcut(shortcut) => {
match shortcut {
ShortcutType::StartMenu => {
let start_index = self.components.iter().position(|c| c.name() == "start-button").unwrap();
let start_response = self.components[start_index].handle_message(WindowMessage::FocusClick);
self.handle_taskbar_message(start_response)
}
_ => WindowMessageResponse::DoNothing,
}
},
WindowMessage::Info(info) => {
if let InfoType::WindowsInWorkspace(windows, focused_id) = info {
self.windows_in_workspace = windows;
self.focused_id = focused_id;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
},
_ => WindowMessageResponse::DoNothing,
}
}
//simple
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![
//top thin white border
DrawInstructions::Rect([0, 0], [self.dimensions[0], 1], theme_info.border_left_top),
//the actual taskbar background
DrawInstructions::Rect([0, 1], [self.dimensions[0], self.dimensions[1] - 1], theme_info.background),
];
for component in &self.components {
instructions.extend(component.draw(theme_info));
}
for wi in 0..self.windows_in_workspace.len() {
//if too many windows to fit in taskbar...
if wi > (self.dimensions[0] - 200) / META_WIDTH {
//
break;
}
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);
b.inverted = info.0 == self.focused_id;
instructions.extend(b.draw(theme_info));
}
instructions
}
//properties
fn subtype(&self) -> WindowLikeType {
WindowLikeType::Taskbar
}
fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions {
[dimensions[0], TASKBAR_HEIGHT]
}
}
impl Taskbar {
pub fn new() -> Self {
Self {
dimensions: [0, 0],
components: Vec::new(),
windows_in_workspace: Vec::new(),
focused_id: 0,
}
}
fn handle_taskbar_message(&mut self, message: Option<TaskbarMessage>) -> WindowMessageResponse {
if let Some(message) = message {
match message {
TaskbarMessage::ShowStartMenu => {
WindowMessageResponse::Request(WindowManagerRequest::OpenWindow("StartMenu".to_string()))
},
TaskbarMessage::HideStartMenu => {
WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu)
},
_ => WindowMessageResponse::DoNothing,
}
} else {
//maybe should be JustRedraw?
WindowMessageResponse::DoNothing
}
}
}

View File

@@ -0,0 +1,84 @@
use std::vec;
use std::vec::Vec;
use std::time::{ SystemTime, UNIX_EPOCH };
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType, INDICATOR_HEIGHT };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse, ShortcutType };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
const WIDTH: usize = 15;
const ONE_MINUTE: u64 = 60;
const ONE_HOUR: u64 = 60 * ONE_MINUTE;
const ONE_DAY: u64 = 24 * ONE_HOUR;
pub struct WorkspaceIndicator {
dimensions: Dimensions,
current_workspace: u8,
}
impl WindowLike for WorkspaceIndicator {
fn handle_message(&mut self, message: WindowMessage) -> WindowMessageResponse {
match message {
WindowMessage::Init(dimensions) => {
self.dimensions = dimensions;
WindowMessageResponse::JustRedraw
},
WindowMessage::Shortcut(shortcut) => {
match shortcut {
ShortcutType::SwitchWorkspace(workspace) => {
self.current_workspace = workspace;
WindowMessageResponse::JustRedraw
},
_ => WindowMessageResponse::DoNothing,
}
},
_ => WindowMessageResponse::DoNothing,
}
}
//simple
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![
//background
DrawInstructions::Rect([0, 0], [self.dimensions[0], self.dimensions[1] - 1], theme_info.background),
//bottom border
DrawInstructions::Rect([0, self.dimensions[1] - 1], [self.dimensions[0], 1], theme_info.border_right_bottom),
];
for w in 0..9 {
if w == self.current_workspace as usize {
instructions.push(DrawInstructions::Rect([w * WIDTH, 0], [WIDTH, self.dimensions[1]], theme_info.top));
instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], vec!["nimbus-roman".to_string()], (w + 1).to_string(), theme_info.top_text, theme_info.top, None, None));
} else {
instructions.push(DrawInstructions::Text([w * WIDTH + 5, 4], vec!["nimbus-roman".to_string()], (w + 1).to_string(), theme_info.text, theme_info.background, None, None));
}
}
//also add the utc time in the right edge
let today_secs = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() % ONE_DAY;
let hours = (today_secs / ONE_HOUR).to_string();
let minutes = ((today_secs % ONE_HOUR) / ONE_MINUTE).to_string();
let time_string = format!("{}:{}~ UTC", if hours.len() == 1 { "0".to_string() + &hours } else { hours }, if minutes.len() == 1 { "0".to_string() + &minutes } else { minutes });
instructions.push(DrawInstructions::Text([self.dimensions[0] - 90, 4], vec!["nimbus-roman".to_string()], time_string, theme_info.text, theme_info.background, None, None));
instructions
}
//properties
fn subtype(&self) -> WindowLikeType {
WindowLikeType::WorkspaceIndicator
}
fn ideal_dimensions(&self, dimensions: Dimensions) -> Dimensions {
[dimensions[0], INDICATOR_HEIGHT]
}
}
impl WorkspaceIndicator {
pub fn new() -> Self {
Self {
dimensions: [0, 0],
current_workspace: 0,
}
}
}

269
wm/src/framebuffer.rs Normal file
View File

@@ -0,0 +1,269 @@
use std::vec::Vec;
//use core::ptr;
use bmp_rust::bmp::BMP;
use ming_wm_lib::framebuffer_types::*;
use crate::fs::get_font_char_from_fonts;
type FontChar = (char, Vec<Vec<u8>>, u8);
fn color_with_alpha(color: RGBColor, bg_color: RGBColor, alpha: u8) -> RGBColor {
/*let factor: f32 = alpha as f32 / 255.0;
[
(bg_color[0] as f32 * (1.0 - factor)) as u8 + (color[0] as f32 * factor) as u8,
(bg_color[1] as f32 * (1.0 - factor)) as u8 + (color[1] as f32 * factor) as u8,
(bg_color[2] as f32 * (1.0 - factor)) as u8 + (color[2] as f32 * factor) as u8,
]*/
//255 * 255 < max(u16)
if alpha == 255 {
color
} else {
let alpha = alpha as u16;
[
(bg_color[0] as u16 * (255 - alpha) / 255) as u8 + (color[0] as u16 * alpha / 255) as u8,
(bg_color[1] as u16 * (255 - alpha) / 255) as u8 + (color[1] as u16 * alpha / 255) as u8,
(bg_color[2] as u16 * (255 - alpha) / 255) as u8 + (color[2] as u16 * alpha / 255) as u8,
]
}
}
fn color_to_grayscale(color: RGBColor) -> RGBColor {
//0.3, 0.6, 0.1 weighting
let gray = color[0] / 10 * 3 + color[1] / 10 * 6 + color[2] / 10;
[gray; 3]
}
#[derive(Clone, Default)]
pub struct FramebufferInfo {
pub byte_len: usize,
pub width: usize,
pub height: usize,
pub bytes_per_pixel: usize,
pub stride: usize,
pub old_stride: Option<usize>, //used/set only when rotate is true
}
//currently doesn't check if writing onto next line accidentally
pub struct FramebufferWriter {
info: FramebufferInfo,
buffer: Vec<u8>,
saved_buffer: Option<Vec<u8>>,
rotate_buffer: Option<Vec<u8>>,
grayscale: bool,
}
impl FramebufferWriter {
pub fn new(grayscale: bool) -> Self {
Self {
info: Default::default(),
buffer: Vec::new(),
saved_buffer: None,
rotate_buffer: None,
grayscale,
}
}
pub fn init(&mut self, info: FramebufferInfo) {
self.info = info;
self.buffer = vec![0; self.info.byte_len];
}
pub fn get_info(&self) -> FramebufferInfo {
self.info.clone()
}
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());
}
pub fn write_saved_buffer_to_raw(&mut self) {
self.buffer[..]
.copy_from_slice(&self.saved_buffer.as_ref().unwrap()[..]);
}
fn _draw_pixel(&mut self, start_pos: usize, color: RGBColor) {
let color = [color[2], color[1], color[0]];
let color = if self.grayscale { color_to_grayscale(color) } else { color };
self.buffer[start_pos..(start_pos + 3)]
.copy_from_slice(&color[..]);
}
fn _draw_line(&mut self, start_pos: usize, bytes: &[u8]) {
self.buffer[start_pos..(start_pos + bytes.len())]
.copy_from_slice(bytes);
}
pub fn draw_buffer(&mut self, top_left: Point, height: usize, bytes_per_line: usize, bytes: &[u8]) {
//for our framebuffer
let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
//of the buffer we want to draw on
let mut start = 0;
for _y in 0..height {
self.buffer[start_pos..(start_pos + bytes_per_line)]
.copy_from_slice(&bytes[start..(start + bytes_per_line)]);
//let _ = unsafe { ptr::read_volatile(&self.buffer[start_pos]) };
start += bytes_per_line;
start_pos += self.info.stride * self.info.bytes_per_pixel;
}
}
pub fn draw_char(&mut self, top_left: Point, char_info: &FontChar, color: RGBColor, bg_color: RGBColor) {
let mut start_pos;
for row in 0..char_info.1.len() {
//char_info.2 is vertical offset
start_pos = ((top_left[1] + row + char_info.2 as usize) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
for col in &char_info.1[row] {
if col > &0 {
if start_pos + 3 < self.info.byte_len {
self._draw_pixel(start_pos, color_with_alpha(color, bg_color, *col));
}
}
start_pos += self.info.bytes_per_pixel;
}
}
}
//dots
pub fn draw_pixel(&mut self, point: Point, color: RGBColor) {
let start_pos = (point[1] * self.info.stride + point[0]) * self.info.bytes_per_pixel;
self._draw_pixel(start_pos, color);
}
//shapes
pub fn draw_rect(&mut self, top_left: Point, dimensions: Dimensions, color: RGBColor) {
let color = if self.grayscale { color_to_grayscale(color) } else { color };
let line_bytes = if self.info.bytes_per_pixel > 3 {
[color[2], color[1], color[0], 255].repeat(dimensions[0])
} else {
color.repeat(dimensions[0])
};
let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
for _row in 0..dimensions[1] {
//use _draw_line instead for MUCH more efficiency
self._draw_line(start_pos, &line_bytes[..]);
start_pos += self.info.stride * self.info.bytes_per_pixel;
}
}
//can optimise (?) by turning into lines and doing _draw_line instead?
pub fn draw_circle(&mut self, centre: Point, radius: usize, color: RGBColor) {
//x^2 + y^2 <= r^2
for y in 0..radius {
for x in 0..radius {
if (x.pow(2) + y.pow(2)) <= radius.pow(2) {
self.draw_pixel([centre[0] + x, centre[1] + y], color);
self.draw_pixel([centre[0] - x, centre[1] + y], color);
self.draw_pixel([centre[0] - x, centre[1] - y], color);
self.draw_pixel([centre[0] + x, centre[1] - y], color);
}
}
}
}
//direction is top to bottom
pub fn draw_gradient(&mut self, top_left: Point, dimensions: Dimensions, start_color: RGBColor, end_color: RGBColor, steps: usize) {
let delta_r = (end_color[0] as f32 - start_color[0] as f32) / steps as f32;
let delta_g = (end_color[1] as f32 - start_color[1] as f32) / steps as f32;
let delta_b = (end_color[2] as f32 - start_color[2] as f32) / steps as f32;
let mut start_pos = (top_left[1] * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
if steps <= dimensions[1] {
//rounds down
let mut y_per = dimensions[1] / steps;
for s in 0..steps {
let color;
if s == steps - 1 {
color = end_color;
//the remaining lines are the last one
y_per = dimensions[1] - (y_per * steps);
} else {
color = [(start_color[0] as f32 + (delta_r * s as f32)) as u8, (start_color[1] as f32 + (delta_g * s as f32)) as u8, (start_color[2] as f32 + (delta_b * s as f32)) as u8];
};
let color = if self.grayscale { color_to_grayscale(color) } else { color };
let line_bytes = if self.info.bytes_per_pixel > 3 {
[color[2], color[1], color[0], 255].repeat(dimensions[0])
} else {
color.repeat(dimensions[0])
};
for _y in 0..y_per {
self._draw_line(start_pos, &line_bytes[..]);
start_pos += self.info.stride * self.info.bytes_per_pixel;
}
}
}
}
//text
pub fn draw_text(&mut self, top_left: Point, fonts: Vec<String>, text: &str, color: RGBColor, bg_color: RGBColor, horiz_spacing: usize, mono_width: Option<u8>) {
let mut top_left = top_left;
//todo, config space
for c in text.chars() {
if c == ' ' {
top_left[0] += mono_width.unwrap_or(5) as usize;
} else {
let char_info = get_font_char_from_fonts(&fonts, c);
let char_width = char_info.1[0].len();
let add_after: usize;
if let Some(mono_width) = mono_width {
let mono_width = mono_width as usize;
if mono_width < char_width {
add_after = mono_width;
} else {
let remainder = mono_width - char_width;
top_left[0] += remainder / 2;
add_after = remainder - remainder / 2 + char_width;
};
} else {
add_after = char_width + horiz_spacing;
}
self.draw_char(top_left, &char_info, color, bg_color);
top_left[0] += add_after;
}
}
}
//bmps
//reverse is workaround for when my bmp lib returns rgba instead of bgra
pub fn draw_bmp(&mut self, top_left: Point, path: String, reverse: bool) {
let b = BMP::new_from_file(&path);
if let Ok(b) = b {
let dib_header = b.get_dib_header().unwrap();
let pixel_data = b.get_pixel_data().unwrap();
let height = dib_header.height as usize;
let width = dib_header.width as usize;
let mut start_pos;
for row in 0..height {
start_pos = ((top_left[1] + row) * self.info.stride + top_left[0]) * self.info.bytes_per_pixel;
for column in 0..width {
let color = b.get_color_of_pixel_efficient(column, row, &dib_header, &pixel_data).unwrap();
self._draw_pixel(start_pos, if reverse { [color[2], color[1], color[0]] } else { [color[0], color[1], color[2]] });
start_pos += self.info.bytes_per_pixel;
}
}
}
}
}

58
wm/src/fs.rs Normal file
View File

@@ -0,0 +1,58 @@
use std::fs::{ read_dir, File };
use std::io::Read;
use std::collections::HashMap;
use ming_wm_lib::dirs;
use ming_wm_lib::utils::get_rest_of_split;
fn get_font_char(dir: &str, c: char) -> Option<(char, Vec<Vec<u8>>, u8)> {
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<u8>> = Vec::new();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let lines: Vec<&str> = contents.split("\n").collect();
for l in 1..lines.len() {
//.unwrap_or(0) is important because zeroes are just empty
ch.push(lines[l].split(",").map(|n| n.parse().unwrap_or(0)).collect());
}
return Some((c, ch, lines[0].parse().unwrap()));
}
None
}
pub fn get_font_char_from_fonts(fonts: &[String], c: char) -> (char, Vec<Vec<u8>>, u8) {
for font in fonts {
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &font))).to_string_lossy().to_string();
if let Some(font_char) = get_font_char(&p, c) {
return font_char;
}
}
let p = dirs::exe_dir(Some(&("ming_bmps/".to_string() + &fonts[0]))).to_string_lossy().to_string();
//so a ? char should be in every font. otherwise will just return blank
get_font_char(&p, '?').unwrap_or(('?', vec![vec![0]], 0))
}
//Category, Vec<Display name, file name>
pub type ExeWindowInfos = HashMap<String, Vec<(String, String)>>;
//well, doesn't actually look to see if its executable. Just if it contains a _ and has no file extension, and is a file
pub fn get_all_executable_windows() -> ExeWindowInfos {
let mut exes = HashMap::new();
for entry in read_dir(dirs::exe_dir(None)).unwrap() {
let pb = entry.unwrap().path();
if pb.is_file() && pb.extension().is_none() {
let parts = pb.file_stem().unwrap().to_string_lossy().to_string();
let mut parts = parts.split('_');
let category = parts.next().unwrap();
let display = get_rest_of_split(&mut parts, Some(" "));
let file_name = pb.file_name().unwrap().to_string_lossy().to_string();
if display != String::new() && category.starts_with("ming") {
let pair = (display, file_name);
exes.entry(category.to_string()).and_modify(|v: &mut Vec<(String, String)>| (*v).push(pair.clone())).or_insert(vec![pair]);
}
}
}
exes
}

9
wm/src/lib.rs Normal file
View File

@@ -0,0 +1,9 @@
pub use linux;
pub use termion;
pub mod framebuffer;
pub mod window_manager;
pub mod fs;
mod proxy_window_like;
mod essential;

101
wm/src/proxy_window_like.rs Normal file
View File

@@ -0,0 +1,101 @@
use std::vec::Vec;
use std::process::{ Command, Child, Stdio };
use std::io::{ BufReader, BufRead, Write };
use std::cell::RefCell;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
use ming_wm_lib::dirs;
use ming_wm_lib::serialize::{ Serializable, DrawInstructionsVec };
pub struct ProxyWindowLike {
process: RefCell<Child>,
}
//try to handle panics of child processes so the entire wm doesn't crash
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() + &message.serialize() + "\n").as_bytes());
}
let output = self.read_line();
WindowMessageResponse::deserialize(&output).unwrap_or(WindowMessageResponse::JustRedraw)
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() {
let _ = stdin.write_all(("draw ".to_string() + &theme_info.serialize() + "\n").as_bytes());
}
let output = self.read_line();
DrawInstructionsVec::deserialize(&output).unwrap_or(Vec::new())
}
//properties
fn title(&self) -> String {
if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() {
let _ = stdin.write_all("title\n".as_bytes());
}
self.read_line().chars().filter(|c| *c != '\n').collect()
}
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();
output == "true\n"
}
fn subtype(&self) -> WindowLikeType {
if let Some(stdin) = self.process.borrow_mut().stdin.as_mut() {
let _ = stdin.write_all("subtype\n".to_string().as_bytes());
}
let output = self.read_line();
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() + &dimensions.serialize() + "\n").as_bytes());
}
let output = self.read_line();
Dimensions::deserialize(&output).unwrap_or([420, 420])
}
}
//kill process when this window like dropped
impl Drop for ProxyWindowLike {
fn drop(&mut self) {
let _ = self.process.borrow_mut().kill();
}
}
impl ProxyWindowLike {
pub fn new(name: &str) -> Self {
let loc = dirs::exe_dir(Some(name)).to_string_lossy().to_string();
ProxyWindowLike {
process: RefCell::new(Command::new(loc).stdout(Stdio::piped()).stdin(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap()),
}
}
//return empty string if error, do not propogate Err becuase that's messy
//or maybe return "panicked"?
fn read_line(&self) -> String {
let mut buffer = self.process.borrow_mut();
if let Some(buffer) = buffer.stdout.as_mut() {
let mut output = String::new();
let mut reader = BufReader::new(buffer);
if let Ok(_) = reader.read_line(&mut output) {
output
} else {
String::new()
}
} else {
String::new()
}
}
}

756
wm/src/window_manager.rs Normal file
View File

@@ -0,0 +1,756 @@
use std::vec::Vec;
use std::vec;
use std::collections::{ HashMap, VecDeque };
use std::fmt;
use std::boxed::Box;
use std::cell::RefCell;
use std::fs::File;
use std::io::Read;
use linux::fb::Framebuffer;
use ming_wm_lib::framebuffer_types::{ Point, Dimensions };
use ming_wm_lib::themes::{ Themes, get_theme_info };
use ming_wm_lib::utils::{ min, point_inside };
use ming_wm_lib::messages::*;
use ming_wm_lib::dirs::config_dir;
use ming_wm_lib::window_manager_types::*;
use crate::framebuffer::FramebufferWriter;
use crate::proxy_window_like::ProxyWindowLike;
use crate::essential::desktop_background::DesktopBackground;
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::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
const WINDOW_TOP_HEIGHT: usize = 26;
#[derive(PartialEq)]
enum Workspace {
All,
Workspace(u8), //goes from 0-8
}
struct WindowLikeInfo {
id: usize,
window_like: WindowBox,
top_left: Point,
dimensions: Dimensions,
workspace: Workspace,
fullscreen: bool,
}
impl fmt::Debug for WindowLikeInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowLikeInfo").field("id", &self.id).field("top_left", &self.top_left).field("dimensions", &self.dimensions).field("window_like", &"todo: print this out too").finish()
}
}
pub struct WindowManager {
writer: RefCell<FramebufferWriter>,
rotate: bool,
grayscale: bool,
id_count: usize,
window_infos: Vec<WindowLikeInfo>,
osk: Option<WindowLikeInfo>,
dimensions: Dimensions,
theme: Themes,
focused_id: usize,
pub locked: bool,
current_workspace: u8,
framebuffer: Framebuffer,
clipboard: Option<String>,
password_hash: [u8; 64],
}
//1 is up, 2 is down
impl WindowManager {
pub fn new(writer: FramebufferWriter, framebuffer: Framebuffer, dimensions: Dimensions, rotate: bool, grayscale: bool, password_hash: [u8; 64]) -> Self {
//println!("bg: {}x{}", dimensions[0], dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT);
let mut wm = WindowManager {
writer: RefCell::new(writer),
rotate,
grayscale,
id_count: 0,
window_infos: Vec::new(),
osk: None,
dimensions,
theme: Default::default(),
focused_id: 0,
locked: false,
current_workspace: 0,
framebuffer,
clipboard: None,
password_hash,
};
wm.lock();
wm.change_theme();
wm
}
pub fn add_window_like(&mut self, mut window_like: Box<dyn WindowLike>, top_left: Point, dimensions: Option<Dimensions>) {
let subtype = window_like.subtype();
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
self.id_count += 1;
let id = self.id_count;
window_like.handle_message(WindowMessage::Init(dimensions));
let dimensions = if subtype == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
let window_info = WindowLikeInfo {
id,
window_like,
top_left,
dimensions,
workspace: if subtype == WindowLikeType::Window {
Workspace::Workspace(self.current_workspace)
} else {
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<usize> {
self.window_infos.iter().position(|w| w.id == self.focused_id)
}
//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 {
Workspace::Workspace(workspace) => workspace == self.current_workspace,
_ => include_non_window, //filter out taskbar, indicator, background, start menu, etc if true
}
}).collect()
}
fn lock(&mut self) {
self.locked = true;
self.window_infos = Vec::new();
self.add_window_like(Box::new(LockScreen::new(self.password_hash)), [0, 0], None);
}
fn unlock(&mut self) {
self.locked = false;
self.window_infos = Vec::new();
self.add_window_like(Box::new(DesktopBackground::new()), [0, INDICATOR_HEIGHT], None);
self.add_window_like(Box::new(Taskbar::new()), [0, self.dimensions[1] - TASKBAR_HEIGHT], None);
self.add_window_like(Box::new(WorkspaceIndicator::new()), [0, 0], None);
}
fn change_theme(&mut self) {
self.theme = Default::default();
if let Ok(mut file) = File::open(format!("{}/ming-wm/themes", config_dir().unwrap().into_os_string().into_string().unwrap())) {
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
let lines: Vec<&str> = contents.split("\n").collect();
if lines.len() > self.current_workspace.into() {
self.theme = Themes::from_str(lines[self.current_workspace as usize]).unwrap_or_default();
}
}
}
//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 {
let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap();
self.focused_id = self.window_infos[taskbar_index].id;
if off_only {
self.handle_request(WindowManagerRequest::CloseStartMenu);
}
self.window_infos[taskbar_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::StartMenu))
} else {
WindowMessageResponse::DoNothing
}
}
fn taskbar_update_windows(&mut self) {
let taskbar_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::Taskbar).unwrap();
let mut relevant: WindowsVec = self.get_windows_in_workspace(false).iter().map(|w| (w.id, w.window_like.title().to_string())).collect();
relevant.sort_by(|a, b| a.0.cmp(&b.0)); //sort by ids so order is consistent
let message = WindowMessage::Info(InfoType::WindowsInWorkspace(
relevant,
self.focused_id
));
self.window_infos[taskbar_index].window_like.handle_message(message);
}
fn move_index_to_top(&mut self, index: usize) {
let removed = self.window_infos.remove(index);
self.window_infos.push(removed);
}
pub fn handle_message(&mut self, message: WindowManagerMessage) {
let mut use_saved_buffer = false;
let mut redraw_ids = None;
let response: WindowMessageResponse = match message {
WindowManagerMessage::KeyChar(key_char) => {
//check if is special key (key releases are guaranteed to be special keys)
//eg: ctrl, alt, command/windows, shift, or caps lock
match key_char {
KeyChar::Alt(c) => {
let mut press_response = WindowMessageResponse::DoNothing;
if !self.locked {
//keyboard shortcut
let shortcuts = HashMap::from([
//alt+E kills ming-wm when it is unlocked, but that is handled at a higher level
('s', ShortcutType::StartMenu),
('[', ShortcutType::FocusPrevWindow),
(']', ShortcutType::FocusNextWindow),
('q', ShortcutType::QuitWindow),
('c', ShortcutType::CenterWindow),
('f', ShortcutType::FullscreenWindow),
('w', ShortcutType::HalfWidthWindow),
('C', ShortcutType::ClipboardCopy),
('P', ShortcutType::ClipboardPaste(String::new())),
//move window a small amount
('h', ShortcutType::MoveWindow(Direction::Left)),
('j', ShortcutType::MoveWindow(Direction::Down)),
('k', ShortcutType::MoveWindow(Direction::Up)),
('l', ShortcutType::MoveWindow(Direction::Right)),
//move window to edges
('H', ShortcutType::MoveWindowToEdge(Direction::Left)),
('J', ShortcutType::MoveWindowToEdge(Direction::Down)),
('K', ShortcutType::MoveWindowToEdge(Direction::Up)),
('L', ShortcutType::MoveWindowToEdge(Direction::Right)),
//expand window size
('n', ShortcutType::ChangeWindowSize(Direction::Right)),
('m', ShortcutType::ChangeWindowSize(Direction::Down)),
//shrink window size
('N', ShortcutType::ChangeWindowSize(Direction::Left)),
('M', ShortcutType::ChangeWindowSize(Direction::Up)),
//no 10th workspace
('1', ShortcutType::SwitchWorkspace(0)),
('2', ShortcutType::SwitchWorkspace(1)),
('3', ShortcutType::SwitchWorkspace(2)),
('4', ShortcutType::SwitchWorkspace(3)),
('5', ShortcutType::SwitchWorkspace(4)),
('6', ShortcutType::SwitchWorkspace(5)),
('7', ShortcutType::SwitchWorkspace(6)),
('8', ShortcutType::SwitchWorkspace(7)),
('9', ShortcutType::SwitchWorkspace(8)),
//shfit + num key
('!', ShortcutType::MoveWindowToWorkspace(0)),
('@', ShortcutType::MoveWindowToWorkspace(1)),
('#', ShortcutType::MoveWindowToWorkspace(2)),
('$', ShortcutType::MoveWindowToWorkspace(3)),
('%', ShortcutType::MoveWindowToWorkspace(4)),
('^', ShortcutType::MoveWindowToWorkspace(5)),
('&', ShortcutType::MoveWindowToWorkspace(6)),
('*', ShortcutType::MoveWindowToWorkspace(7)),
('(', ShortcutType::MoveWindowToWorkspace(8)),
//
]);
if let Some(shortcut) = shortcuts.get(&c) {
match shortcut {
&ShortcutType::StartMenu => {
//send to taskbar
press_response = self.toggle_start_menu(false);
if press_response != WindowMessageResponse::Request(WindowManagerRequest::CloseStartMenu) {
//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]);
}
},
&ShortcutType::MoveWindow(direction) | &ShortcutType::MoveWindowToEdge(direction) => {
if let Some(focused_index) = self.get_focused_index() {
let focused_info = &self.window_infos[focused_index];
if focused_info.window_like.subtype() == WindowLikeType::Window && !focused_info.fullscreen {
let delta = 15;
let window_x = self.window_infos[focused_index].top_left[0];
let window_y = self.window_infos[focused_index].top_left[1];
let mut changed = true;
if direction == Direction::Left {
if window_x == 0 {
changed = false;
} else if window_x < delta || shortcut == &ShortcutType::MoveWindowToEdge(direction) {
self.window_infos[focused_index].top_left[0] = 0;
} else {
self.window_infos[focused_index].top_left[0] -= delta;
}
} else if direction == Direction::Down {
let max_y = self.dimensions[1] - TASKBAR_HEIGHT - focused_info.dimensions[1];
if window_y == max_y {
changed = false;
} else if window_y > (max_y - delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) {
self.window_infos[focused_index].top_left[1] = max_y;
} else {
self.window_infos[focused_index].top_left[1] += delta;
}
} else if direction == Direction::Up {
let min_y = INDICATOR_HEIGHT;
if window_y == min_y {
changed = false;
} else if window_y < (min_y + delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) {
self.window_infos[focused_index].top_left[1] = min_y;
} else {
self.window_infos[focused_index].top_left[1] -= delta;
}
} else if direction == Direction::Right {
let max_x = self.dimensions[0] - focused_info.dimensions[0];
if window_x == max_x {
changed = false;
} else if window_x > (max_x - delta) || shortcut == &ShortcutType::MoveWindowToEdge(direction) {
self.window_infos[focused_index].top_left[0] = max_x;
} else {
self.window_infos[focused_index].top_left[0] += delta;
}
}
if changed {
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]);
}
}
}
},
&ShortcutType::ChangeWindowSize(direction) => {
if let Some(focused_index) = self.get_focused_index() {
let focused_info = &self.window_infos[focused_index];
if focused_info.window_like.subtype() == WindowLikeType::Window && focused_info.window_like.resizable() && !focused_info.fullscreen {
//mostly arbitrary
let min_window_size = [100, WINDOW_TOP_HEIGHT + 100];
let mut changed = false;
let delta = 15;
let window = &mut self.window_infos[focused_index];
if direction == Direction::Right {
//expand x
if window.dimensions[0] + delta != self.dimensions[0] {
window.dimensions[0] += delta;
let max_width = self.dimensions[0] - window.top_left[0];
if window.dimensions[0] > max_width {
window.dimensions[0] = max_width;
}
changed = true;
}
} else if direction == Direction::Down {
//expand y
let max_height = self.dimensions[1] - window.top_left[1] - INDICATOR_HEIGHT - TASKBAR_HEIGHT;
if window.dimensions[1] + delta != max_height {
window.dimensions[1] += delta;
if window.dimensions[1] > max_height {
window.dimensions[1] = max_height;
}
changed = true;
}
} else if direction == Direction::Left {
//shrink x
if window.dimensions[0] - delta != min_window_size[0] {
window.dimensions[0] -= delta;
if window.dimensions[0] < min_window_size[0] {
window.dimensions[0] = min_window_size[0];
}
changed = true;
}
} else if direction == Direction::Up {
//shrink y
if window.dimensions[1] - delta != min_window_size[1] {
window.dimensions[1] -= delta;
if window.dimensions[1] < min_window_size[1] {
window.dimensions[1] = min_window_size[1];
}
changed = true;
}
}
if changed {
let new_dimensions = [window.dimensions[0], window.dimensions[1] - WINDOW_TOP_HEIGHT];
self.window_infos[focused_index].window_like.handle_message(WindowMessage::ChangeDimensions(new_dimensions));
press_response = WindowMessageResponse::JustRedraw;
use_saved_buffer = true;
redraw_ids = Some(vec![self.focused_id]);
}
}
}
},
&ShortcutType::SwitchWorkspace(workspace) => {
if self.current_workspace != workspace {
//close start menu if open
self.toggle_start_menu(true);
self.current_workspace = workspace;
//change theme
self.change_theme();
//send to desktop background
let desktop_background_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::DesktopBackground).unwrap();
self.window_infos[desktop_background_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::SwitchWorkspace(self.current_workspace)));
//send to workspace indicator
let indicator_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::WorkspaceIndicator).unwrap();
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::JustRedraw;
}
},
&ShortcutType::MoveWindowToWorkspace(workspace) => {
if self.current_workspace != workspace {
if let Some(focused_index) = self.get_focused_index() {
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::JustRedraw;
}
}
}
},
&ShortcutType::FocusPrevWindow | &ShortcutType::FocusNextWindow => {
self.toggle_start_menu(true);
let current_index = self.get_focused_index().unwrap_or(0);
let mut new_focus_index = current_index;
loop {
if shortcut == &ShortcutType::FocusPrevWindow {
if new_focus_index == 0 {
new_focus_index = self.window_infos.len() - 1;
} else {
new_focus_index -= 1;
}
} else {
new_focus_index += 1;
if new_focus_index == self.window_infos.len() {
new_focus_index = 0;
}
}
if self.window_infos[new_focus_index].window_like.subtype() == WindowLikeType::Window && self.window_infos[new_focus_index].workspace == Workspace::Workspace(self.current_workspace) {
//switch focus to this
self.focused_id = self.window_infos[new_focus_index].id;
//elevate it to the top
self.move_index_to_top(new_focus_index);
self.taskbar_update_windows();
press_response = WindowMessageResponse::JustRedraw;
break;
} else if new_focus_index == current_index {
break; //did a full loop, found no windows
}
}
},
&ShortcutType::QuitWindow => {
if let Some(focused_index) = self.get_focused_index() {
if self.window_infos[focused_index].window_like.subtype() == WindowLikeType::Window {
self.window_infos.remove(focused_index);
self.taskbar_update_windows();
press_response = WindowMessageResponse::JustRedraw;
}
}
},
&ShortcutType::CenterWindow => {
if let Some(focused_index) = self.get_focused_index() {
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::JustRedraw;
}
},
&ShortcutType::FullscreenWindow => {
if let Some(focused_index) = self.get_focused_index() {
let window_like = &self.window_infos[focused_index].window_like;
if window_like.subtype() == WindowLikeType::Window && window_like.resizable() {
//toggle fullscreen
self.window_infos[focused_index].fullscreen ^= true;
//todo: send message to window about resize
let new_dimensions;
if self.window_infos[focused_index].fullscreen {
new_dimensions = [self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT];
self.window_infos[focused_index].top_left = [0, INDICATOR_HEIGHT];
redraw_ids = Some(vec![self.window_infos[focused_index].id]);
} else {
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::JustRedraw;
}
}
},
&ShortcutType::HalfWidthWindow => {
if let Some(focused_index) = self.get_focused_index() {
let window_like = &self.window_infos[focused_index].window_like;
if window_like.subtype() == WindowLikeType::Window && window_like.resizable() {
self.window_infos[focused_index].fullscreen = false;
let top_left = &mut self.window_infos[focused_index].top_left;
if top_left[0] > self.dimensions[0] / 2 {
top_left[0] = self.dimensions[0] / 2;
} else {
top_left[0] = 0;
}
top_left[1] = INDICATOR_HEIGHT;
//full height, half width
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::JustRedraw;
}
}
},
&ShortcutType::ClipboardCopy => {
if let Some(focused_index) = self.get_focused_index() {
let window_like = &self.window_infos[focused_index].window_like;
if window_like.subtype() == WindowLikeType::Window {
press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::ClipboardCopy));
}
}
},
&ShortcutType::ClipboardPaste(_) => {
if let Some(focused_index) = self.get_focused_index() {
let window_like = &self.window_infos[focused_index].window_like;
if window_like.subtype() == WindowLikeType::Window && self.clipboard.is_some() {
press_response = self.window_infos[focused_index].window_like.handle_message(WindowMessage::Shortcut(ShortcutType::ClipboardPaste(self.clipboard.clone().unwrap())));
}
}
},
};
}
}
press_response
},
KeyChar::Press(c) | KeyChar::Ctrl(c) => {
let mut press_response = WindowMessageResponse::DoNothing;
//send to focused window
if let Some(focused_index) = self.get_focused_index() {
press_response = self.window_infos[focused_index].window_like.handle_message(if key_char == KeyChar::Press(c) {
WindowMessage::KeyPress(KeyPress {
key: c,
})
} else {
WindowMessage::CtrlKeyPress(KeyPress {
key: c,
})
});
//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::JustRedraw {
redraw_ids = None;
}
}
press_response
},
}
},
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),
_ => {},
};
if !is_key_char_request {
self.draw(redraw_ids, use_saved_buffer);
}
}
}
pub fn handle_request(&mut self, request: WindowManagerRequest) {
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;
}
let w: Option<WindowBox> = match w.as_str() {
"StartMenu" => Some(Box::new(StartMenu::new())),
"About" => Some(Box::new(About::new())),
"Help" => Some(Box::new(Help::new())),
_ => Some(Box::new(ProxyWindowLike::new(&w))),
};
if w.is_none() {
return;
}
let w = w.unwrap();
//close start menu if open
self.toggle_start_menu(true);
let ideal_dimensions = w.ideal_dimensions(self.dimensions);
let top_left = match w.subtype() {
WindowLikeType::StartMenu => [0, self.dimensions[1] - TASKBAR_HEIGHT - ideal_dimensions[1]],
WindowLikeType::Window => [42, 42],
_ => [0, 0],
};
self.add_window_like(w, top_left, Some(ideal_dimensions));
self.taskbar_update_windows();
},
WindowManagerRequest::CloseStartMenu => {
let subtype = subtype.unwrap();
if subtype != WindowLikeType::Taskbar && subtype != WindowLikeType::StartMenu {
return;
}
let start_menu_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::StartMenu);
if let Some(start_menu_index) = start_menu_index {
self.window_infos.remove(start_menu_index);
}
},
WindowManagerRequest::Unlock => {
if subtype.unwrap() != WindowLikeType::LockScreen {
return;
}
self.unlock();
},
WindowManagerRequest::Lock => {
if subtype.unwrap() != WindowLikeType::StartMenu {
return;
}
self.lock();
},
WindowManagerRequest::ClipboardCopy(content) => {
self.clipboard = Some(content);
},
WindowManagerRequest::DoKeyChar(kc) => {
self.handle_message(WindowManagerMessage::KeyChar(kc));
},
};
}
fn get_true_top_left(top_left: &Point, is_window: bool) -> Point {
[top_left[0], top_left[1] + if is_window { WINDOW_TOP_HEIGHT } else { 0 }]
}
//another issue with a huge vector of draw instructions; it takes up heap memory
pub fn draw(&mut self, maybe_redraw_ids: Option<Vec<usize>>, 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 {
self.writer.borrow_mut().write_saved_buffer_to_raw();
}
//get windows to redraw
let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new());
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) || w.window_like.subtype() == WindowLikeType::OnscreenKeyboard
} else {
true
}
});
//these are needed to decide when to snapshot
let max_index = if redraw_ids.len() > 0 { redraw_ids.len() } else { maybe_length } - 1;
for (w_index, window_info) in redraw_windows.enumerate() {
let window_dimensions = if window_info.fullscreen {
[self.dimensions[0], self.dimensions[1] - TASKBAR_HEIGHT - INDICATOR_HEIGHT]
} else {
window_info.dimensions
};
let mut instructions = VecDeque::from(window_info.window_like.draw(&theme_info));
let is_window = window_info.window_like.subtype() == WindowLikeType::Window;
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 {
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| {
match instruction {
DrawInstructions::Rect(top_left, dimensions, color) => DrawInstructions::Rect(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *color),
DrawInstructions::Circle(centre, radius, color) => DrawInstructions::Circle(WindowManager::get_true_top_left(centre, is_window), *radius, *color),
DrawInstructions::Text(top_left, fonts, text, color, bg_color, horiz_spacing, mono_width) => DrawInstructions::Text(WindowManager::get_true_top_left(top_left, is_window), fonts.clone(), text.clone(), *color, *bg_color, *horiz_spacing, *mono_width),
DrawInstructions::Bmp(top_left, path, reverse) => DrawInstructions::Bmp(WindowManager::get_true_top_left(top_left, is_window), path.to_string(), *reverse),
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => DrawInstructions::Gradient(WindowManager::get_true_top_left(top_left, is_window), *dimensions, *start_color, *end_color, *steps),
}
}).collect();
//draw window background
instructions.push_front(DrawInstructions::Rect([0, 0], window_dimensions, theme_info.background));
//draw window top decorations and what not
instructions.extend(vec![
//left top border
DrawInstructions::Rect([0, 0], [window_dimensions[0], 1], theme_info.border_left_top),
DrawInstructions::Rect([0, 0], [1, window_dimensions[1]], theme_info.border_left_top),
//top
DrawInstructions::Rect([1, 1], [window_dimensions[0] - 2, WINDOW_TOP_HEIGHT - 3], theme_info.top),
DrawInstructions::Text([4, 4], vec!["nimbus-roman".to_string()], window_info.window_like.title().to_string(), theme_info.top_text, theme_info.top, None, None),
//top bottom border
DrawInstructions::Rect([1, WINDOW_TOP_HEIGHT - 2], [window_dimensions[0] - 2, 2], theme_info.border_left_top),
//right bottom border
DrawInstructions::Rect([window_dimensions[0] - 1, 1], [1, window_dimensions[1] - 1], theme_info.border_right_bottom),
DrawInstructions::Rect([1, window_dimensions[1] - 1], [window_dimensions[0] - 1, 1], theme_info.border_right_bottom),
]);
}
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];
framebuffer_info.width = window_width;
framebuffer_info.height = window_height;
framebuffer_info.stride = window_width;
framebuffer_info.byte_len = window_width * window_height * bytes_per_pixel;
//make a writer just for the window
let mut window_writer: FramebufferWriter = FramebufferWriter::new(self.grayscale);
window_writer.init(framebuffer_info);
for instruction in instructions {
//unsafe { SERIAL1.lock().write_text(&format!("{:?}\n", instruction)); }
match instruction {
DrawInstructions::Rect(top_left, dimensions, color) => {
//try and prevent overflows out of the window
let true_dimensions = [
min(dimensions[0], window_dimensions[0] - top_left[0]),
min(dimensions[1], window_dimensions[1] - top_left[1]),
];
window_writer.draw_rect(top_left, true_dimensions, color);
},
DrawInstructions::Circle(centre, radius, color) => {
window_writer.draw_circle(centre, radius, color);
},
DrawInstructions::Text(top_left, fonts, text, color, bg_color, horiz_spacing, mono_width) => {
//if overflow, won't draw, I think
window_writer.draw_text(top_left, fonts, &text, color, bg_color, horiz_spacing.unwrap_or(1), mono_width);
},
DrawInstructions::Bmp(top_left, path, reverse) => {
window_writer.draw_bmp(top_left, path, reverse);
},
DrawInstructions::Gradient(top_left, dimensions, start_color, end_color, steps) => {
window_writer.draw_gradient(top_left, dimensions, start_color, end_color, steps);
},
}
}
self.writer.borrow_mut().draw_buffer(window_info.top_left, window_dimensions[1], window_dimensions[0] * bytes_per_pixel, &window_writer.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);
}
}