change project structure to make more sense
(move wm only stuff to wm dir)
This commit is contained in:
14
wm/Cargo.toml
Normal file
14
wm/Cargo.toml
Normal 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
8
wm/linux/Cargo.toml
Normal 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
146
wm/linux/src/fb.rs
Normal 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
1
wm/linux/src/keys.rs
Normal file
@@ -0,0 +1 @@
|
||||
//
|
||||
2
wm/linux/src/lib.rs
Normal file
2
wm/linux/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod fb;
|
||||
pub mod raw;
|
||||
65
wm/linux/src/raw.rs
Normal file
65
wm/linux/src/raw.rs
Normal 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
63
wm/src/essential/about.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
wm/src/essential/desktop_background.rs
Normal file
81
wm/src/essential/desktop_background.rs
Normal 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
104
wm/src/essential/help.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
83
wm/src/essential/lock_screen.rs
Normal file
83
wm/src/essential/lock_screen.rs
Normal 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
10
wm/src/essential/mod.rs
Normal 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;
|
||||
|
||||
201
wm/src/essential/onscreen_keyboard.rs
Normal file
201
wm/src/essential/onscreen_keyboard.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
206
wm/src/essential/start_menu.rs
Normal file
206
wm/src/essential/start_menu.rs
Normal 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
127
wm/src/essential/taskbar.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
84
wm/src/essential/workspace_indicator.rs
Normal file
84
wm/src/essential/workspace_indicator.rs
Normal 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
269
wm/src/framebuffer.rs
Normal 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
58
wm/src/fs.rs
Normal 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
9
wm/src/lib.rs
Normal 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
101
wm/src/proxy_window_like.rs
Normal 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
756
wm/src/window_manager.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user