├── .gitignore ├── .gitlab-ci.yml ├── redoxer.sh ├── Cargo.toml ├── LICENSE ├── src ├── core │ ├── rect.rs │ ├── display.rs │ ├── image.rs │ └── mod.rs ├── window_order.rs ├── main.rs ├── config.rs ├── compositor.rs ├── window.rs └── scheme.rs ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | *.iml -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "redoxos/redoxer:latest" 2 | 3 | test:redox: 4 | script: 5 | - redoxer test --verbose 6 | -------------------------------------------------------------------------------- /redoxer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ "$(uname)" != "Redox" ] 6 | then 7 | redoxer build --verbose 8 | redoxer build --verbose --examples 9 | exec redoxer exec --folder . --gui -- sh -- ./redoxer.sh simple 10 | fi 11 | 12 | old_path="file:/bin/orbital" 13 | new_path="target/x86_64-unknown-redox/debug/orbital" 14 | if [ -e "${new_path}" ] 15 | then 16 | mv -v "${new_path}" "${old_path}" 17 | shutdown --reboot 18 | fi 19 | 20 | while [ "$#" != "0" ] 21 | do 22 | example="$1" 23 | shift 24 | 25 | echo "# ${example} #" 26 | "target/x86_64-unknown-redox/debug/examples/${example}" 27 | done 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jeremy Soller "] 3 | description = "The Orbital Windowing System and Compositor" 4 | license-file = "LICENSE" 5 | name = "orbital" 6 | readme = "README.md" 7 | repository = "https://gitlab.redox-os.org/redox-os/orbital" 8 | version = "0.1.0" 9 | edition = "2024" 10 | 11 | [dependencies] 12 | graphics-ipc = { git = "https://gitlab.redox-os.org/redox-os/base.git" } 13 | inputd = { git = "https://gitlab.redox-os.org/redox-os/base.git" } 14 | libc = "0.2.48" 15 | log = "0.4.14" 16 | orbclient = "0.3.47" 17 | orbfont = "0.1" 18 | orbimage = "0.1" 19 | redox-log = "0.1" 20 | redox-scheme = "0.6" 21 | redox_syscall = { version = "0.5", features = ["std"] } 22 | serde = "1" 23 | serde_derive = "1" 24 | thiserror = "2.0.12" 25 | toml = "0.7" 26 | # Downgrade due to improper x86 CPU feature detection 27 | ab_glyph_rasterizer = "=0.1.5" 28 | libredox = "0.1.3" 29 | redox_event = { version = "0.4" } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jeremy Soller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/core/rect.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{max, min}; 2 | 3 | #[derive(Copy, Clone, Debug, Default)] 4 | pub struct Rect { 5 | x: i32, 6 | y: i32, 7 | w: i32, 8 | h: i32, 9 | } 10 | 11 | impl Rect { 12 | pub fn new(x: i32, y: i32, w: i32, h: i32) -> Rect { 13 | assert!(w >= 0); 14 | assert!(h >= 0); 15 | 16 | Rect { x, y, w, h } 17 | } 18 | 19 | pub fn area(&self) -> i32 { 20 | self.w * self.h 21 | } 22 | 23 | pub fn left(&self) -> i32 { 24 | self.x 25 | } 26 | 27 | pub fn right(&self) -> i32 { 28 | self.x + self.w 29 | } 30 | 31 | pub fn top(&self) -> i32 { 32 | self.y 33 | } 34 | 35 | pub fn bottom(&self) -> i32 { 36 | self.y + self.h 37 | } 38 | 39 | pub fn width(&self) -> i32 { 40 | self.w 41 | } 42 | 43 | pub fn height(&self) -> i32 { 44 | self.h 45 | } 46 | 47 | pub fn container(&self, other: &Rect) -> Rect { 48 | let left = min(self.left(), other.left()); 49 | let right = max(self.right(), other.right()); 50 | let top = min(self.top(), other.top()); 51 | let bottom = max(self.bottom(), other.bottom()); 52 | 53 | assert!(left <= right); 54 | assert!(top <= bottom); 55 | 56 | Rect::new(left, top, right - left, bottom - top) 57 | } 58 | 59 | pub fn contains(&self, x: i32, y: i32) -> bool { 60 | self.left() <= x && self.right() >= x && self.top() <= y && self.bottom() >= y 61 | } 62 | 63 | pub fn is_empty(&self) -> bool { 64 | self.w == 0 || self.h == 0 65 | } 66 | 67 | pub fn intersection(&self, other: &Rect) -> Rect { 68 | let left = max(self.left(), other.left()); 69 | let right = min(self.right(), other.right()); 70 | let top = max(self.top(), other.top()); 71 | let bottom = min(self.bottom(), other.bottom()); 72 | 73 | Rect::new(left, top, max(0, right - left), max(0, bottom - top)) 74 | } 75 | 76 | pub fn offset(&self, x: i32, y: i32) -> Rect { 77 | Rect::new(self.x + x, self.y + y, self.w, self.h) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/window_order.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] 4 | pub(crate) enum WindowZOrder { 5 | Back, 6 | Normal, 7 | Front, 8 | } 9 | 10 | pub(crate) struct WindowOrder { 11 | focus_order: VecDeque, 12 | zbuffer: Vec<(usize, WindowZOrder, bool)>, 13 | } 14 | 15 | impl WindowOrder { 16 | pub(crate) fn new() -> WindowOrder { 17 | WindowOrder { 18 | focus_order: VecDeque::new(), 19 | zbuffer: Vec::new(), 20 | } 21 | } 22 | 23 | pub(crate) fn add_window(&mut self, id: usize, zorder: WindowZOrder) { 24 | match zorder { 25 | WindowZOrder::Front | WindowZOrder::Normal => { 26 | self.focus_order.push_front(id); 27 | } 28 | WindowZOrder::Back => { 29 | self.focus_order.push_back(id); 30 | } 31 | } 32 | } 33 | 34 | pub(crate) fn remove_window(&mut self, id: usize) { 35 | self.focus_order.retain(|&e| e != id); 36 | } 37 | 38 | pub(crate) fn make_focused(&mut self, id: usize) { 39 | let index = self.focus_order.iter().position(|&e| e == id).unwrap(); 40 | self.focus_order.remove(index).unwrap(); 41 | self.focus_order.push_front(id); 42 | } 43 | 44 | pub(crate) fn move_focused_after(&mut self, id: usize) { 45 | let after_index = self.focus_order.iter().position(|&e| e == id).unwrap(); 46 | let front_id = self.focus_order.pop_front().unwrap(); 47 | self.focus_order.insert(after_index, front_id); 48 | } 49 | 50 | pub(crate) fn rezbuffer(&mut self, get_zorder: &dyn Fn(usize) -> WindowZOrder) { 51 | self.zbuffer.clear(); 52 | 53 | for (i, &id) in self.focus_order.iter().enumerate() { 54 | self.zbuffer.push((id, get_zorder(id), i == 0)); 55 | } 56 | 57 | self.zbuffer.sort_by(|a, b| b.1.cmp(&a.1)); 58 | } 59 | 60 | pub(crate) fn focused(&self) -> Option { 61 | self.focus_order.front().copied() 62 | } 63 | 64 | pub(crate) fn focus_order(&self) -> impl Iterator { 65 | self.focus_order.iter().copied() 66 | } 67 | 68 | pub(crate) fn iter_front_to_back(&self) -> impl Iterator { 69 | self.zbuffer.iter().map(|&(id, _, _)| id) 70 | } 71 | 72 | pub(crate) fn iter_back_to_front(&self) -> impl Iterator { 73 | self.zbuffer 74 | .iter() 75 | .map(|&(id, _, focused)| (id, focused)) 76 | .rev() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![forbid(clippy::unwrap_used)] 2 | #![forbid(clippy::expect_used)] 3 | 4 | use crate::core::Orbital; 5 | use log::{debug, error, info, warn}; 6 | use redox_log::{OutputBuilder, RedoxLogger}; 7 | use std::{env, process::Command, rc::Rc}; 8 | 9 | use config::Config; 10 | use scheme::OrbitalScheme; 11 | 12 | mod compositor; 13 | mod config; 14 | mod core; 15 | mod window_order; 16 | mod scheme; 17 | mod window; 18 | 19 | /// Run orbital main event loop, starting a login command before entering the event loop. 20 | fn orbital() -> Result<(), String> { 21 | // Ignore possible errors while enabling logging 22 | let _ = RedoxLogger::new() 23 | .with_output( 24 | OutputBuilder::stdout() 25 | .with_filter(log::LevelFilter::Warn) 26 | .with_ansi_escape_codes() 27 | .build(), 28 | ) 29 | .with_process_name("orbital".into()) 30 | .enable(); 31 | 32 | let mut args = env::args().skip(1); 33 | let vt = env::var("VT").expect("`VT` environment variable not set"); 34 | unsafe { 35 | env::remove_var("VT"); 36 | } 37 | let login_cmd = args.next().ok_or("no login manager argument")?; 38 | 39 | let (orbital, displays) = Orbital::open_display(&vt) 40 | .map_err(|e| format!("could not open display, caused by: {}", e))?; 41 | 42 | //TODO: integrate this into orbital 43 | match Command::new("inputd").arg("-A").arg(&vt).status() { 44 | Ok(status) => { 45 | if !status.success() { 46 | warn!("inputd -A '{}' exited with status: {:?}", vt, status); 47 | } 48 | } 49 | Err(err) => { 50 | warn!("inputd -A '{}' failed to run with error: {}", vt, err); 51 | } 52 | } 53 | 54 | debug!( 55 | "found display {}x{}", 56 | displays[0].image.width(), 57 | displays[0].image.height() 58 | ); 59 | let config = Rc::new(Config::from_path("/ui/orbital.toml")); 60 | let scheme = OrbitalScheme::new(displays, config)?; 61 | 62 | Command::new(login_cmd) 63 | .args(args) 64 | .spawn() 65 | .map_err(|_| "failed to spawn login_cmd")?; 66 | 67 | orbital 68 | .run(scheme) 69 | .map_err(|e| format!("error in main loop, caused by {}", e)) 70 | } 71 | 72 | /// Start orbital. This will start orbital main event loop. 73 | /// 74 | /// Startup messages and errors are logged to RedoxLogger with filter set to DEBUG 75 | fn main() { 76 | match orbital() { 77 | Ok(()) => { 78 | info!("ran to completion successfully, exiting with status=0"); 79 | std::process::exit(0); 80 | } 81 | Err(e) => { 82 | error!("error during daemon execution, exiting with status=1: {e}"); 83 | std::process::exit(1); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Orbital 2 | 3 | The Orbital desktop environment provides a display server, window manager and compositor. 4 | 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 6 | 7 | Redox 8 | 9 | ## Comparison with X11/Wayland 10 | 11 | This display server is more simple than X11 and Wayland making the porting task more quick and easy, it's not advanced like X11 and Wayland yet but enough to port most Linux/BSD programs. 12 | 13 | Compared to Wayland, Orbital has one server implementation, while Wayland provide protocols for compositors. 14 | 15 | ## Features 16 | 17 | - Custom Resolutions 18 | - App Launcher (bottom bar) 19 | - File Manager 20 | - Text Editor 21 | - Calculator 22 | - Terminal Emulator 23 | 24 | If you hold the **Super** key (generally the key with a Windows logo) it will show all keyboard shortcuts in a pop-up. 25 | 26 | ## Libraries 27 | 28 | The programs written with these libraries can run on Orbital. 29 | 30 | - SDL1.2 31 | - SDL2 32 | - winit 33 | - softbuffer 34 | - Slint (through winit and softbuffer) 35 | - Iced (through winit and softbuffer) 36 | - egui (can use winit or SDL2) 37 | 38 | ## Clients 39 | 40 | Apps (or 'clients') create a window and draw to it by using the [orbclient](https://gitlab.redox-os.org/redox-os/orbclient) 41 | client. 42 | 43 | ### Client Examples 44 | 45 | If you wish to see examples of client apps that use [orbclient](https://gitlab.redox-os.org/redox-os/orbclient) 46 | to "talk" to Orbital and create windows and draw to them, then you can find some in [orbclient/examples](https://gitlab.redox-os.org/redox-os/orbclient/-/tree/master/examples) 47 | folder. 48 | 49 | ## Porting 50 | 51 | If you want to port a program to Orbital, see below: 52 | 53 | - If the program is written in Rust probably it works on Orbital because the `winit` crate is used in most places, but there are programs that access X11 or Wayland directly. You need to port these programs to `winit` and merge on upstream. 54 | 55 | - If the program is written in C or C++ and access X11 or Wayland directly, it must be ported to the [Orbital library](https://gitlab.redox-os.org/redox-os/liborbital). 56 | 57 | ## How To Contribute 58 | 59 | To learn how to contribute to this system component you need to read the following document: 60 | 61 | - [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) 62 | 63 | ## Development 64 | 65 | To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages. 66 | 67 | ### How To Build 68 | 69 | To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page. 70 | 71 | This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux. 72 | -------------------------------------------------------------------------------- /src/core/display.rs: -------------------------------------------------------------------------------- 1 | use libredox::{call::MmapArgs, flag}; 2 | use log::error; 3 | use orbclient::{Color, Renderer}; 4 | use std::{convert::TryInto, fs::File, io, os::unix::io::AsRawFd, slice}; 5 | 6 | use crate::core::{ 7 | image::{ImageRef, ImageRoiMut}, 8 | rect::Rect, 9 | }; 10 | 11 | fn display_fd_map( 12 | width: i32, 13 | height: i32, 14 | display_fd: usize, 15 | ) -> libredox::error::Result> { 16 | unsafe { 17 | let display_ptr = libredox::call::mmap(MmapArgs { 18 | fd: display_fd, 19 | offset: 0, 20 | length: (width * height * 4) as usize, 21 | prot: flag::PROT_READ | flag::PROT_WRITE, 22 | flags: flag::MAP_SHARED, 23 | addr: core::ptr::null_mut(), 24 | })?; 25 | let display_slice = 26 | slice::from_raw_parts_mut(display_ptr as *mut Color, (width * height) as usize); 27 | Ok(ImageRef::from_data(width, height, display_slice)) 28 | } 29 | } 30 | 31 | fn display_fd_unmap(image: &mut ImageRef) { 32 | unsafe { 33 | let _ = libredox::call::munmap( 34 | image.data().as_ptr() as *mut (), 35 | (image.width() * image.height() * 4) as usize, 36 | ); 37 | } 38 | } 39 | 40 | pub struct Display { 41 | pub x: i32, 42 | pub y: i32, 43 | pub scale: i32, 44 | pub file: File, 45 | pub image: ImageRef<'static>, 46 | } 47 | 48 | impl Display { 49 | pub fn new(x: i32, y: i32, width: i32, height: i32, file: File) -> io::Result { 50 | let scale = (height / 1600) + 1; 51 | let image = display_fd_map(width, height, file.as_raw_fd() as usize).map_err(|err| { 52 | error!("failed to map display: {}", err); 53 | io::Error::from_raw_os_error(err.errno()) 54 | })?; 55 | Ok(Self { 56 | x, 57 | y, 58 | scale, 59 | file, 60 | image, 61 | }) 62 | } 63 | 64 | pub fn rect(&mut self, rect: &Rect, color: Color) { 65 | self.image.rect( 66 | rect.left() - self.x, 67 | rect.top() - self.y, 68 | rect.width().try_into().unwrap_or(0), 69 | rect.height().try_into().unwrap_or(0), 70 | color, 71 | ); 72 | } 73 | 74 | pub fn resize(&mut self, width: i32, height: i32) { 75 | match display_fd_map(width, height, self.file.as_raw_fd() as usize) { 76 | Ok(ok) => { 77 | display_fd_unmap(&mut self.image); 78 | self.image = ok; 79 | } 80 | Err(err) => { 81 | error!("failed to resize display to {}x{}: {}", width, height, err); 82 | } 83 | } 84 | } 85 | 86 | pub fn roi_mut(&mut self, rect: &Rect) -> ImageRoiMut<'_> { 87 | self.image.roi_mut(&Rect::new( 88 | rect.left() - self.x, 89 | rect.top() - self.y, 90 | rect.width(), 91 | rect.height(), 92 | )) 93 | } 94 | 95 | pub fn screen_rect(&self) -> Rect { 96 | Rect::new(self.x, self.y, self.image.width(), self.image.height()) 97 | } 98 | } 99 | 100 | impl Drop for Display { 101 | fn drop(&mut self) { 102 | display_fd_unmap(&mut self.image); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, error}; 2 | use orbclient::Color; 3 | use serde_derive::Deserialize; 4 | use std::fs::File; 5 | use std::io::Read; 6 | 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)] 8 | pub struct ConfigColor { 9 | data: u32, 10 | } 11 | 12 | impl From for Color { 13 | fn from(value: ConfigColor) -> Self { 14 | Self { data: value.data } 15 | } 16 | } 17 | impl From for ConfigColor { 18 | fn from(value: Color) -> Self { 19 | Self { data: value.data } 20 | } 21 | } 22 | 23 | #[derive(Deserialize, Clone)] 24 | pub struct Config { 25 | pub cursor: String, 26 | pub bottom_left_corner: String, 27 | pub bottom_right_corner: String, 28 | pub bottom_side: String, 29 | pub left_side: String, 30 | pub right_side: String, 31 | pub window_max: String, 32 | pub window_max_unfocused: String, 33 | pub window_close: String, 34 | pub window_close_unfocused: String, 35 | 36 | #[serde(default = "background_color_default")] 37 | pub background_color: ConfigColor, 38 | #[serde(default = "bar_color_default")] 39 | pub bar_color: ConfigColor, 40 | #[serde(default = "bar_highlight_color_default")] 41 | pub bar_highlight_color: ConfigColor, 42 | #[serde(default = "text_color_default")] 43 | pub text_color: ConfigColor, 44 | #[serde(default = "text_highlight_color_default")] 45 | pub text_highlight_color: ConfigColor, 46 | } 47 | 48 | fn background_color_default() -> ConfigColor { 49 | Color::rgb(0, 0, 0).into() 50 | } 51 | fn bar_color_default() -> ConfigColor { 52 | Color::rgba(0x1B, 0x1B, 0x1B, 224).into() 53 | } 54 | fn bar_highlight_color_default() -> ConfigColor { 55 | Color::rgba(0x36, 0x36, 0x36, 224).into() 56 | } 57 | fn text_color_default() -> ConfigColor { 58 | Color::rgb(0xE7, 0xE7, 0xE7).into() 59 | } 60 | fn text_highlight_color_default() -> ConfigColor { 61 | Color::rgb(0xE7, 0xE7, 0xE7).into() 62 | } 63 | 64 | /// Create a sane default Orbital [Config] in case none is supplied or it is unreadable 65 | impl Default for Config { 66 | fn default() -> Self { 67 | // Cannot use "..Default::default() for all these fields as that is recursive, so they 68 | // all have to be "defaulted" manually. 69 | Config { 70 | // TODO: What would be good or better defaults for these config values? 71 | cursor: String::default(), 72 | bottom_left_corner: String::default(), 73 | bottom_right_corner: String::default(), 74 | bottom_side: String::default(), 75 | left_side: String::default(), 76 | right_side: String::default(), 77 | window_max: String::default(), 78 | window_max_unfocused: String::default(), 79 | window_close: String::default(), 80 | window_close_unfocused: String::default(), 81 | 82 | // These are the default colors for Orbital that have been defined 83 | background_color: background_color_default(), 84 | bar_color: bar_color_default(), 85 | bar_highlight_color: bar_highlight_color_default(), 86 | text_color: text_color_default(), 87 | text_highlight_color: text_highlight_color_default(), 88 | } 89 | } 90 | } 91 | 92 | /// [Config] holds configuration information for Orbital, such as colors, cursors etc. 93 | impl Config { 94 | // returns the default config if the string passed is not a valid config 95 | fn config_from_string(config: &str) -> Config { 96 | match toml::from_str(config) { 97 | Ok(config) => config, 98 | Err(err) => { 99 | error!("failed to parse config '{}'", err); 100 | Config::default() 101 | } 102 | } 103 | } 104 | 105 | /// Read an Orbital configuration from a toml file at `path` 106 | pub fn from_path(path: &str) -> Config { 107 | let mut string = String::new(); 108 | 109 | match File::open(path) { 110 | Ok(mut file) => match file.read_to_string(&mut string) { 111 | Ok(_) => debug!("reading config from path: '{}'", path), 112 | Err(err) => error!("failed to read config '{}': {}", path, err), 113 | }, 114 | Err(err) => error!("failed to open config '{}': {}", path, err), 115 | } 116 | 117 | Self::config_from_string(&string) 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod test { 123 | use crate::config::{background_color_default, text_highlight_color_default, Config}; 124 | 125 | #[test] 126 | fn non_existent_config_file() { 127 | let config = Config::from_path("no-such-file.toml"); 128 | assert_eq!(config.cursor, ""); 129 | assert_eq!(config.text_highlight_color, text_highlight_color_default()); 130 | } 131 | 132 | #[test] 133 | fn partial_config() { 134 | let config_str = r##" 135 | background_color = "#FFFFFFFF" 136 | "##; 137 | let config = Config::config_from_string(config_str); 138 | assert_eq!(config.background_color, background_color_default()); 139 | } 140 | 141 | #[test] 142 | fn valid_partial_config() { 143 | let config_str = r##"cursor = "/ui/left_ptr.png" 144 | bottom_left_corner = "/ui/bottom_left_corner.png" 145 | bottom_right_corner = "/ui/bottom_right_corner.png" 146 | bottom_side = "/ui/bottom_side.png" 147 | left_side = "/ui/left_side.png" 148 | right_side = "/ui/right_side.png" 149 | window_max = "/ui/window_max.png" 150 | window_max_unfocused = "/ui/window_max_unfocused.png" 151 | window_close = "/ui/window_close.png" 152 | window_close_unfocused = "/ui/window_close_unfocused.png""##; 153 | let config = Config::config_from_string(config_str); 154 | assert_eq!(config.background_color, background_color_default()); 155 | assert_eq!(config.bottom_left_corner, "/ui/bottom_left_corner.png"); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/compositor.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | use std::sync::Arc; 3 | use std::time::Instant; 4 | use std::{mem, slice}; 5 | 6 | use log::{error, info}; 7 | 8 | use crate::core::display::Display; 9 | use crate::core::image::Image; 10 | use crate::core::rect::Rect; 11 | 12 | #[repr(C, packed)] 13 | struct CursorCommand { 14 | //header flag that indicates update_cursor or move_cursor 15 | header: u32, 16 | x: i32, 17 | y: i32, 18 | hot_x: i32, 19 | hot_y: i32, 20 | w: i32, 21 | h: i32, 22 | cursor_img: [u32; 4096], 23 | } 24 | 25 | pub struct Compositor { 26 | displays: Vec, 27 | 28 | redraws: Vec, 29 | 30 | hw_cursor: bool, 31 | //QEMU UIs do not grab the pointer in case an absolute pointing device is present 32 | //and since releasing our gpu cursor makes it disappear, updating it every second fixes it 33 | update_cursor_timer: Instant, 34 | cursor: Arc, 35 | cursor_x: i32, 36 | cursor_y: i32, 37 | cursor_hot_x: i32, 38 | cursor_hot_y: i32, 39 | } 40 | 41 | impl Compositor { 42 | pub fn new(mut displays: Vec) -> Self { 43 | let mut redraws = Vec::new(); 44 | for display in displays.iter() { 45 | redraws.push(display.screen_rect()); 46 | } 47 | 48 | //Reading display file is only used to check if GPU cursor is supported 49 | let mut buf_array = [0; 1]; 50 | let buf: &mut [u8] = &mut buf_array; 51 | let _ret = displays[0].file.read(buf); 52 | 53 | let mut hw_cursor: bool = false; 54 | 55 | if buf[0] == 1 { 56 | info!("Hardware cursor detected"); 57 | hw_cursor = true; 58 | } 59 | 60 | Compositor { 61 | displays, 62 | 63 | redraws, 64 | 65 | hw_cursor, 66 | update_cursor_timer: Instant::now(), 67 | cursor: Arc::new(Image::new(0, 0)), 68 | cursor_x: 0, 69 | cursor_y: 0, 70 | cursor_hot_x: 0, 71 | cursor_hot_y: 0, 72 | } 73 | } 74 | 75 | pub fn displays(&self) -> &[Display] { 76 | &self.displays 77 | } 78 | 79 | /// Return the screen rectangle 80 | pub fn screen_rect(&self) -> Rect { 81 | self.displays[0].screen_rect() 82 | } 83 | 84 | /// Find the display that a window (`rect`) most overlaps and return it's screen_rect 85 | pub fn get_screen_rect_for_window(&self, rect: &Rect) -> Rect { 86 | let mut screen_rect = self.displays[0].screen_rect(); 87 | let mut max_intersection_area = 0; 88 | for display in &self.displays { 89 | let intersect = display.screen_rect().intersection(rect); 90 | if intersect.area() > max_intersection_area { 91 | screen_rect = display.screen_rect(); 92 | max_intersection_area = intersect.area(); 93 | } 94 | } 95 | screen_rect 96 | } 97 | 98 | /// Reduce the rect height based on orblauncher bar height 99 | pub fn get_window_rect_from_screen_rect(&self, screen_rect: &Rect) -> Rect { 100 | let mut height = screen_rect.height(); 101 | // TODO: This is a hack, orblauncher should 102 | // talk with orbital to register this value 103 | height -= 48 * ((height / 1600) + 1); 104 | Rect::new( 105 | screen_rect.left(), 106 | screen_rect.top(), 107 | screen_rect.width(), 108 | height, 109 | ) 110 | } 111 | 112 | /// Resize the inner image buffer. 113 | pub fn resize(&mut self, width: i32, height: i32) { 114 | //TODO: should other screens be moved after a resize? 115 | //TODO: support resizing other screens? 116 | self.displays[0].resize(width, height); 117 | 118 | self.schedule(self.screen_rect()); 119 | } 120 | 121 | pub fn schedule(&mut self, request: Rect) { 122 | let mut push = true; 123 | for rect in self.redraws.iter_mut() { 124 | //If contained, ignore new redraw request 125 | let container = rect.container(&request); 126 | if container.area() <= rect.area() + request.area() { 127 | *rect = container; 128 | push = false; 129 | break; 130 | } 131 | } 132 | 133 | if push { 134 | self.redraws.push(request); 135 | } 136 | } 137 | 138 | fn cursor_rect(&self) -> Rect { 139 | Rect::new( 140 | self.cursor_x - self.cursor_hot_x, 141 | self.cursor_y - self.cursor_hot_y, 142 | self.cursor.width(), 143 | self.cursor.height(), 144 | ) 145 | } 146 | 147 | pub fn update_cursor(&mut self, x: i32, y: i32, hot_x: i32, hot_y: i32, cursor: &Arc) { 148 | if !self.hw_cursor { 149 | self.schedule(self.cursor_rect()); 150 | } 151 | 152 | if self.hw_cursor { 153 | if Arc::ptr_eq(&self.cursor, cursor) 154 | && self.cursor_hot_x == hot_x 155 | && self.cursor_hot_y == hot_y 156 | { 157 | self.send_cursor_command(&CursorCommand { 158 | header: 0, 159 | x, 160 | y, 161 | hot_x: 0, 162 | hot_y: 0, 163 | w: 0, 164 | h: 0, 165 | cursor_img: [0; 4096], 166 | }); 167 | } else { 168 | self.send_cursor_command(&CursorCommand { 169 | header: 1, 170 | x, 171 | y, 172 | hot_x, 173 | hot_y, 174 | w: cursor.width(), 175 | h: cursor.height(), 176 | cursor_img: cursor.get_cursor_data(), 177 | }); 178 | } 179 | } 180 | 181 | self.cursor_x = x; 182 | self.cursor_y = y; 183 | self.cursor_hot_x = hot_x; 184 | self.cursor_hot_y = hot_y; 185 | self.cursor = cursor.clone(); 186 | 187 | if !self.hw_cursor { 188 | self.schedule(self.cursor_rect()); 189 | } 190 | } 191 | 192 | fn send_cursor_command(&mut self, cmd: &CursorCommand) { 193 | for (i, display) in self.displays.iter_mut().enumerate() { 194 | match display.file.write(unsafe { 195 | slice::from_raw_parts( 196 | cmd as *const CursorCommand as *const u8, 197 | mem::size_of::(), 198 | ) 199 | }) { 200 | Ok(_) => (), 201 | Err(err) => error!("failed to sync display {}: {}", i, err), 202 | } 203 | } 204 | } 205 | 206 | pub fn redraw_windows( 207 | &mut self, 208 | total_redraw_opt: &mut Option, 209 | draw_windows: impl Fn(&mut Display, Rect), 210 | ) { 211 | // go through the list of rectangles pending a redraw and expand the total redraw rectangle 212 | // to encompass all of them 213 | for original_rect in self.redraws.drain(..) { 214 | if !original_rect.is_empty() { 215 | *total_redraw_opt = Some( 216 | total_redraw_opt 217 | .unwrap_or(original_rect) 218 | .container(&original_rect), 219 | ); 220 | } 221 | 222 | for display in self.displays.iter_mut() { 223 | let rect = original_rect.intersection(&display.screen_rect()); 224 | if rect.is_empty() { 225 | continue; 226 | } 227 | 228 | draw_windows(display, rect); 229 | } 230 | } 231 | } 232 | 233 | pub fn redraw_cursor(&mut self, total_redraw: Option) { 234 | if self.hw_cursor { 235 | if self.update_cursor_timer.elapsed().as_millis() > 1000 { 236 | self.send_cursor_command(&CursorCommand { 237 | header: 1, 238 | x: self.cursor_x, 239 | y: self.cursor_y, 240 | hot_x: self.cursor_hot_x, 241 | hot_y: self.cursor_hot_y, 242 | w: self.cursor.width(), 243 | h: self.cursor.height(), 244 | cursor_img: self.cursor.get_cursor_data(), 245 | }); 246 | self.update_cursor_timer = Instant::now(); 247 | } 248 | 249 | return; 250 | } 251 | 252 | let Some(total_redraw) = total_redraw else { 253 | return; 254 | }; 255 | 256 | let cursor_rect = self.cursor_rect(); 257 | 258 | for display in self.displays.iter_mut() { 259 | let rect = total_redraw.intersection(&display.screen_rect()); 260 | if !rect.is_empty() { 261 | let cursor_intersect = rect.intersection(&cursor_rect); 262 | if !cursor_intersect.is_empty() { 263 | display.roi_mut(&cursor_intersect).blend( 264 | &self 265 | .cursor 266 | .roi(&cursor_intersect.offset(-cursor_rect.left(), -cursor_rect.top())), 267 | ); 268 | } 269 | } 270 | } 271 | } 272 | 273 | pub fn sync_rect(&mut self, total_redraw: Rect) { 274 | // Sync any parts of displays that changed 275 | for (i, display) in self.displays.iter_mut().enumerate() { 276 | let display_redraw = total_redraw.intersection(&display.screen_rect()); 277 | if !display_redraw.is_empty() { 278 | // Keep synced with vesad 279 | #[repr(C, packed)] 280 | struct SyncRect { 281 | x: i32, 282 | y: i32, 283 | w: i32, 284 | h: i32, 285 | } 286 | 287 | let sync_rect = SyncRect { 288 | x: display_redraw.left() - display.x, 289 | y: display_redraw.top() - display.y, 290 | w: display_redraw.width(), 291 | h: display_redraw.height(), 292 | }; 293 | 294 | match display.file.write(unsafe { 295 | slice::from_raw_parts( 296 | &sync_rect as *const SyncRect as *const u8, 297 | mem::size_of::(), 298 | ) 299 | }) { 300 | Ok(_) => (), 301 | Err(err) => error!("failed to sync display {}: {}", i, err), 302 | } 303 | } 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/core/image.rs: -------------------------------------------------------------------------------- 1 | use crate::core::rect::Rect; 2 | use log::{debug, error}; 3 | use orbclient::{Color, Mode, Renderer}; 4 | use std::cell::Cell; 5 | use std::cmp::Ordering; 6 | use std::path::Path; 7 | use std::{cmp, mem, ptr, slice}; 8 | 9 | pub struct ImageRoiRows<'a> { 10 | rect: Rect, 11 | w: i32, 12 | data: &'a [Color], 13 | i: i32, 14 | } 15 | 16 | impl<'a> Iterator for ImageRoiRows<'a> { 17 | type Item = &'a [Color]; 18 | fn next(&mut self) -> Option { 19 | if self.i < self.rect.height() { 20 | let start = (self.rect.top() + self.i) * self.w + self.rect.left(); 21 | let end = start + self.rect.width(); 22 | self.i += 1; 23 | Some(&self.data[start as usize..end as usize]) 24 | } else { 25 | None 26 | } 27 | } 28 | } 29 | 30 | pub struct ImageRoiRowsMut<'a> { 31 | rect: Rect, 32 | w: i32, 33 | data: &'a mut [Color], 34 | i: i32, 35 | } 36 | 37 | impl<'a> Iterator for ImageRoiRowsMut<'a> { 38 | type Item = &'a mut [Color]; 39 | fn next(&mut self) -> Option { 40 | if self.i < self.rect.height() { 41 | let mut data = mem::take(&mut self.data); 42 | 43 | // skip section of data above top of rect 44 | if self.i == 0 { 45 | data = data 46 | .split_at_mut(self.rect.top() as usize * self.w as usize) 47 | .1 48 | }; 49 | 50 | // split after next row 51 | let (row, tail) = data.split_at_mut(self.w as usize); 52 | self.data = tail; // make data point to the remaining rows 53 | let start = self.rect.left() as usize; 54 | let end = self.rect.left() as usize + self.rect.width() as usize; 55 | self.i += 1; 56 | Some(&mut row[start..end]) // return the rect part of the row 57 | } else { 58 | None 59 | } 60 | } 61 | } 62 | 63 | // ImageRoi seems to be a "window" onto an image, i.e. a Rectangular part of an image. 64 | // `rect` defined the area within the larger image, we need to know the width of the image (`w`) 65 | // to move through the data by rows, and `data` is a reference to the data in the actual image 66 | pub struct ImageRoi<'a> { 67 | rect: Rect, 68 | w: i32, 69 | data: &'a [Color], 70 | } 71 | 72 | impl<'a> ImageRoi<'a> { 73 | pub fn rows(&'a self) -> ImageRoiRows<'a> { 74 | ImageRoiRows { 75 | rect: self.rect, 76 | w: self.w, 77 | data: self.data, 78 | i: 0, 79 | } 80 | } 81 | } 82 | 83 | // ImageRoiMut seems to be a "window" onto an image, i.e. a Rectangular part of an image. 84 | // `rect` defined the area within the larger image, we need to know the width of the image (`w`) 85 | // to move through the data by rows, and `data` is a reference to the data in the actual image 86 | pub struct ImageRoiMut<'a> { 87 | rect: Rect, 88 | w: i32, 89 | data: &'a mut [Color], 90 | } 91 | 92 | impl<'a> ImageRoiMut<'a> { 93 | #[expect(dead_code)] 94 | pub fn rows(&'a self) -> ImageRoiRows<'a> { 95 | ImageRoiRows { 96 | rect: self.rect, 97 | w: self.w, 98 | data: self.data, 99 | i: 0, 100 | } 101 | } 102 | 103 | pub fn rows_mut(&'a mut self) -> ImageRoiRowsMut<'a> { 104 | ImageRoiRowsMut { 105 | rect: self.rect, 106 | w: self.w, 107 | data: self.data, 108 | i: 0, 109 | } 110 | } 111 | 112 | pub fn blend(&'a mut self, other: &ImageRoi) { 113 | for (self_row, other_row) in self.rows_mut().zip(other.rows()) { 114 | for (old, new) in self_row.iter_mut().zip(other_row.iter()) { 115 | let alpha = (new.data >> 24) & 0xFF; 116 | if alpha >= 255 { 117 | old.data = new.data; 118 | } else if alpha > 0 { 119 | let n_r = (((new.data >> 16) & 0xFF) * alpha) >> 8; 120 | let n_g = (((new.data >> 8) & 0xFF) * alpha) >> 8; 121 | let n_b = ((new.data & 0xFF) * alpha) >> 8; 122 | 123 | let n_alpha = 255 - alpha; 124 | 125 | let o_r = (((old.data >> 16) & 0xFF) * n_alpha) >> 8; 126 | let o_g = (((old.data >> 8) & 0xFF) * n_alpha) >> 8; 127 | let o_b = ((old.data & 0xFF) * n_alpha) >> 8; 128 | 129 | old.data = ((o_r << 16) | (o_g << 8) | o_b) + ((n_r << 16) | (n_g << 8) | n_b); 130 | } 131 | } 132 | } 133 | } 134 | 135 | pub fn blit(&'a mut self, other: &ImageRoi) { 136 | for (self_row, other_row) in self.rows_mut().zip(other.rows()) { 137 | let len = cmp::min(self_row.len(), other_row.len()); 138 | unsafe { 139 | ptr::copy(other_row.as_ptr(), self_row.as_mut_ptr(), len); 140 | } 141 | } 142 | } 143 | } 144 | 145 | pub struct ImageRef<'a> { 146 | w: i32, 147 | h: i32, 148 | data: &'a mut [Color], 149 | mode: Cell, 150 | } 151 | 152 | impl<'a> ImageRef<'a> { 153 | pub fn from_data(w: i32, h: i32, data: &'a mut [Color]) -> Self { 154 | ImageRef { 155 | w, 156 | h, 157 | data, 158 | mode: Cell::new(Mode::Blend), 159 | } 160 | } 161 | 162 | pub fn width(&self) -> i32 { 163 | self.w 164 | } 165 | 166 | pub fn height(&self) -> i32 { 167 | self.h 168 | } 169 | 170 | #[expect(dead_code)] 171 | pub fn roi(&self, rect: &Rect) -> ImageRoi<'_> { 172 | ImageRoi { 173 | rect: *rect, 174 | w: self.w, 175 | data: self.data, 176 | } 177 | } 178 | 179 | pub fn roi_mut(&mut self, rect: &Rect) -> ImageRoiMut<'_> { 180 | ImageRoiMut { 181 | rect: *rect, 182 | w: self.w, 183 | data: self.data, 184 | } 185 | } 186 | } 187 | 188 | impl<'a> Renderer for ImageRef<'a> { 189 | /// Get the width of the image in pixels 190 | fn width(&self) -> u32 { 191 | self.w as u32 192 | } 193 | 194 | /// Get the height of the image in pixels 195 | fn height(&self) -> u32 { 196 | self.h as u32 197 | } 198 | 199 | /// Return a reference to a slice of colors making up the image 200 | fn data(&self) -> &[Color] { 201 | self.data 202 | } 203 | 204 | /// Return a mutable reference to a slice of colors making up the image 205 | fn data_mut(&mut self) -> &mut [Color] { 206 | self.data 207 | } 208 | 209 | fn sync(&mut self) -> bool { 210 | true 211 | } 212 | 213 | fn mode(&self) -> &Cell { 214 | &self.mode 215 | } 216 | } 217 | 218 | #[derive(Clone)] 219 | pub struct Image { 220 | w: i32, 221 | h: i32, 222 | data: Box<[Color]>, 223 | mode: Cell, 224 | } 225 | 226 | impl Image { 227 | pub fn new(width: i32, height: i32) -> Image { 228 | Image::from_color(width, height, Color::rgb(0, 0, 0)) 229 | } 230 | 231 | pub fn from_color(width: i32, height: i32, color: Color) -> Image { 232 | Image::from_data( 233 | width, 234 | height, 235 | vec![color; width as usize * height as usize].into_boxed_slice(), 236 | ) 237 | } 238 | 239 | pub fn from_data(w: i32, h: i32, data: Box<[Color]>) -> Image { 240 | Image { 241 | w, 242 | h, 243 | data, 244 | mode: Cell::new(Mode::Blend), 245 | } 246 | } 247 | 248 | pub fn from_path_scale>(path: P, scale: i32) -> Option { 249 | match orbimage::Image::from_path(path) { 250 | Ok(orb_image) => { 251 | let width = orb_image.width(); 252 | let height = orb_image.height(); 253 | let data = orb_image.into_data(); 254 | match scale.cmp(&1) { 255 | Ordering::Equal => Some(Image::from_data(width as i32, height as i32, data)), 256 | Ordering::Greater => { 257 | let mut new_data = 258 | vec![Color::rgb(0, 0, 0); data.len() * (scale * scale) as usize] 259 | .into_boxed_slice(); 260 | 261 | for y in 0..height as i32 { 262 | for x in 0..width as i32 { 263 | let i = y * width as i32 + x; 264 | let value = data[i as usize].data; 265 | for y_s in 0..scale { 266 | for x_s in 0..scale { 267 | let new_i = (y * scale + y_s) * width as i32 * scale 268 | + x * scale 269 | + x_s; 270 | new_data[new_i as usize].data = value; 271 | } 272 | } 273 | } 274 | } 275 | 276 | Some(Image::from_data( 277 | width as i32 * scale, 278 | height as i32 * scale, 279 | new_data, 280 | )) 281 | } 282 | Ordering::Less => { 283 | debug!("Image::from_path_scale: scale {} < 1", scale); 284 | None 285 | } 286 | } 287 | } 288 | Err(err) => { 289 | error!("Image::from_path_scale: {}", err); 290 | None 291 | } 292 | } 293 | } 294 | 295 | pub fn width(&self) -> i32 { 296 | self.w 297 | } 298 | 299 | pub fn height(&self) -> i32 { 300 | self.h 301 | } 302 | 303 | pub fn get_cursor_data(&self) -> [u32; 4096] { 304 | let mut img_data = [0; 4096]; 305 | for (i, color) in self.data.iter().enumerate().take(4096) { 306 | img_data[i] = color.data; 307 | } 308 | img_data 309 | } 310 | 311 | pub fn roi(&self, rect: &Rect) -> ImageRoi<'_> { 312 | ImageRoi { 313 | rect: *rect, 314 | w: self.w, 315 | data: &self.data, 316 | } 317 | } 318 | 319 | #[expect(dead_code)] 320 | pub fn roi_mut(&mut self, rect: &Rect) -> ImageRoiMut<'_> { 321 | ImageRoiMut { 322 | rect: *rect, 323 | w: self.w, 324 | data: &mut self.data, 325 | } 326 | } 327 | } 328 | 329 | impl Renderer for Image { 330 | /// Get the width of the image in pixels 331 | fn width(&self) -> u32 { 332 | self.w as u32 333 | } 334 | 335 | /// Get the height of the image in pixels 336 | fn height(&self) -> u32 { 337 | self.h as u32 338 | } 339 | 340 | /// Return a reference to a slice of colors making up the image 341 | fn data(&self) -> &[Color] { 342 | &self.data 343 | } 344 | 345 | /// Return a mutable reference to a slice of colors making up the image 346 | fn data_mut(&mut self) -> &mut [Color] { 347 | &mut self.data 348 | } 349 | 350 | fn sync(&mut self) -> bool { 351 | true 352 | } 353 | 354 | fn mode(&self) -> &Cell { 355 | &self.mode 356 | } 357 | } 358 | 359 | pub struct ImageAligned { 360 | w: i32, 361 | h: i32, 362 | data: &'static mut [Color], 363 | } 364 | 365 | impl Drop for ImageAligned { 366 | fn drop(&mut self) { 367 | unsafe { 368 | libc::free(self.data.as_mut_ptr() as *mut libc::c_void); 369 | } 370 | } 371 | } 372 | 373 | impl ImageAligned { 374 | pub fn new(w: i32, h: i32, align: usize) -> ImageAligned { 375 | let size = (w * h) as usize; 376 | let size_bytes = size * mem::size_of::(); 377 | let size_alignments = (size_bytes + align - 1) / align; 378 | let size_aligned = size_alignments * align; 379 | let data; 380 | unsafe { 381 | let ptr = libc::memalign(align, size_aligned); 382 | libc::memset(ptr, 0, size_aligned); 383 | data = slice::from_raw_parts_mut( 384 | ptr as *mut Color, 385 | size_aligned / mem::size_of::(), 386 | ); 387 | } 388 | ImageAligned { w, h, data } 389 | } 390 | 391 | pub fn width(&self) -> i32 { 392 | self.w 393 | } 394 | 395 | pub fn height(&self) -> i32 { 396 | self.h 397 | } 398 | 399 | pub fn roi(&self, rect: &Rect) -> ImageRoi<'_> { 400 | ImageRoi { 401 | rect: *rect, 402 | w: self.w, 403 | data: self.data, 404 | } 405 | } 406 | 407 | pub fn roi_mut(&mut self, rect: &Rect) -> ImageRoiMut<'_> { 408 | ImageRoiMut { 409 | rect: *rect, 410 | w: self.w, 411 | data: self.data, 412 | } 413 | } 414 | 415 | pub fn data_mut(&mut self) -> &mut [Color] { 416 | &mut self.data 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | core::{ 3 | Properties, 4 | display::Display, 5 | image::{Image, ImageAligned}, 6 | rect::Rect, 7 | }, 8 | scheme::TilePosition, 9 | window_order::WindowZOrder, 10 | }; 11 | use orbclient::{Color, Event, Renderer}; 12 | use orbfont::Font; 13 | 14 | use std::cmp::{max, min}; 15 | use std::collections::VecDeque; 16 | 17 | use std::rc::Rc; 18 | 19 | // use theme::{BAR_COLOR, BAR_HIGHLIGHT_COLOR, TEXT_COLOR, TEXT_HIGHLIGHT_COLOR}; 20 | use crate::config::Config; 21 | 22 | //TODO: move to orbclient? 23 | pub const ORBITAL_FLAG_ASYNC: char = 'a'; 24 | pub const ORBITAL_FLAG_BACK: char = 'b'; 25 | pub const ORBITAL_FLAG_FRONT: char = 'f'; 26 | pub const ORBITAL_FLAG_HIDDEN: char = 'h'; 27 | pub const ORBITAL_FLAG_BORDERLESS: char = 'l'; 28 | pub const ORBITAL_FLAG_MAXIMIZED: char = 'm'; 29 | pub const ORBITAL_FLAG_FULLSCREEN: char = 'M'; 30 | pub const ORBITAL_FLAG_RESIZABLE: char = 'r'; 31 | pub const ORBITAL_FLAG_TRANSPARENT: char = 't'; 32 | pub const ORBITAL_FLAG_UNCLOSABLE: char = 'u'; 33 | 34 | pub struct Window { 35 | pub x: i32, 36 | pub y: i32, 37 | pub scale: i32, 38 | pub title: String, 39 | pub asynchronous: bool, 40 | pub borderless: bool, 41 | pub hidden: bool, 42 | pub resizable: bool, 43 | pub transparent: bool, 44 | pub unclosable: bool, 45 | pub zorder: WindowZOrder, 46 | pub restore: Option<(Rect, TilePosition)>, 47 | image: ImageAligned, 48 | title_image: Image, 49 | title_image_unfocused: Image, 50 | pub events: VecDeque, 51 | pub notified_read: bool, 52 | //TODO: implement better clipboard mechanism 53 | pub clipboard_seek: usize, 54 | pub mouse_cursor: bool, 55 | pub mouse_grab: bool, 56 | pub mouse_relative: bool, 57 | pub maps: usize, 58 | 59 | config: Rc, 60 | } 61 | 62 | const TITLE_HEIGHT: i32 = 28; 63 | const TITLE_TEXT_HEIGHT: i32 = 16; 64 | 65 | impl Window { 66 | // TODO Consider creating Rect for the title area, max and close areas and removing a lot 67 | // of the inline size calculations below 68 | pub fn new(x: i32, y: i32, w: i32, h: i32, scale: i32, config: Rc) -> Window { 69 | Window { 70 | x, 71 | y, 72 | scale, 73 | title: String::new(), 74 | asynchronous: false, 75 | borderless: false, 76 | hidden: false, 77 | resizable: false, 78 | transparent: false, 79 | unclosable: false, 80 | zorder: WindowZOrder::Normal, 81 | restore: None, 82 | // TODO: get a system constant for the page size 83 | image: ImageAligned::new(w, h, 4096), // Ensure that image data is page aligned at beginning and end 84 | title_image: Image::new(0, 0), 85 | title_image_unfocused: Image::new(0, 0), 86 | events: VecDeque::new(), 87 | notified_read: false, 88 | //TODO: implement better clipboard mechanism 89 | clipboard_seek: 0, 90 | mouse_cursor: true, 91 | mouse_grab: false, 92 | mouse_relative: false, 93 | maps: 0, 94 | config, 95 | } 96 | } 97 | 98 | pub fn width(&self) -> i32 { 99 | self.image.width() 100 | } 101 | 102 | pub fn height(&self) -> i32 { 103 | self.image.height() 104 | } 105 | 106 | pub fn rect(&self) -> Rect { 107 | if self.hidden { 108 | Rect::new(self.x, self.y, 0, 0) 109 | } else { 110 | Rect::new(self.x, self.y, self.width(), self.height()) 111 | } 112 | } 113 | 114 | pub fn title_rect(&self) -> Rect { 115 | if self.borderless || self.hidden { 116 | Rect::new(self.x, self.y, 0, 0) 117 | } else { 118 | Rect::new( 119 | self.x, 120 | self.y - TITLE_HEIGHT * self.scale, 121 | self.width(), 122 | TITLE_HEIGHT * self.scale, 123 | ) 124 | } 125 | } 126 | 127 | pub fn cascade_rect(&self) -> Rect { 128 | let title_rect = self.title_rect(); 129 | Rect::new(title_rect.left(), title_rect.top(), 32, 32) 130 | } 131 | 132 | pub fn bottom_border_rect(&self) -> Rect { 133 | if self.resizable { 134 | Rect::new(self.x, self.y + self.height(), self.width(), 8 * self.scale) 135 | } else { 136 | Rect::new(-1, -1, 0, 0) 137 | } 138 | } 139 | 140 | pub fn bottom_left_border_rect(&self) -> Rect { 141 | if self.resizable { 142 | Rect::new( 143 | self.x - 8 * self.scale, 144 | self.y + self.height(), 145 | 8 * self.scale, 146 | 8 * self.scale, 147 | ) 148 | } else { 149 | Rect::new(-1, -1, 0, 0) 150 | } 151 | } 152 | 153 | pub fn bottom_right_border_rect(&self) -> Rect { 154 | if self.resizable { 155 | Rect::new( 156 | self.x + self.width(), 157 | self.y + self.height(), 158 | 8 * self.scale, 159 | 8 * self.scale, 160 | ) 161 | } else { 162 | Rect::new(-1, -1, 0, 0) 163 | } 164 | } 165 | 166 | pub fn left_border_rect(&self) -> Rect { 167 | if self.resizable { 168 | Rect::new( 169 | self.x - 8 * self.scale, 170 | self.y, 171 | 8 * self.scale, 172 | self.height(), 173 | ) 174 | } else { 175 | Rect::new(-1, -1, 0, 0) 176 | } 177 | } 178 | 179 | pub fn right_border_rect(&self) -> Rect { 180 | if self.resizable { 181 | Rect::new(self.x + self.width(), self.y, 8 * self.scale, self.height()) 182 | } else { 183 | Rect::new(-1, -1, 0, 0) 184 | } 185 | } 186 | 187 | pub fn max_contains(&self, x: i32, y: i32) -> bool { 188 | !self.borderless 189 | && x >= max( 190 | self.x + 6 * self.scale, 191 | self.x + self.width() - 36 * self.scale, 192 | ) 193 | && y >= self.y - TITLE_HEIGHT * self.scale 194 | && x < self.x + self.width() - 18 * self.scale 195 | && y < self.y 196 | } 197 | 198 | pub fn close_contains(&self, x: i32, y: i32) -> bool { 199 | !self.borderless 200 | && x >= max( 201 | self.x + 6 * self.scale, 202 | self.x + self.width() - 18 * self.scale, 203 | ) 204 | && y >= self.y - TITLE_HEIGHT * self.scale 205 | && x < self.x + self.width() 206 | && y < self.y 207 | } 208 | 209 | pub fn draw_title( 210 | &self, 211 | display: &mut Display, 212 | rect: &Rect, 213 | focused: bool, 214 | window_max: &Image, 215 | window_close: &Image, 216 | ) { 217 | let bar_color = Color::from(self.config.bar_color); 218 | let bar_highlight_color = Color::from(self.config.bar_highlight_color); 219 | 220 | let title_rect = self.title_rect(); 221 | let title_intersect = rect.intersection(&title_rect); 222 | if !title_intersect.is_empty() { 223 | display.rect( 224 | &title_intersect, 225 | if focused { 226 | bar_highlight_color 227 | } else { 228 | bar_color 229 | }, 230 | ); 231 | 232 | let mut x = self.x + 6 * self.scale; 233 | let w = max( 234 | self.x + 6 * self.scale, 235 | self.x + self.width() - 18 * self.scale, 236 | ) - x; 237 | if w > 0 { 238 | let title_image = if focused { 239 | &self.title_image 240 | } else { 241 | &self.title_image_unfocused 242 | }; 243 | let image_rect = Rect::new( 244 | x, 245 | title_rect.top() + 6 * self.scale, 246 | min(w, title_image.width()), 247 | title_image.height(), 248 | ); 249 | let image_intersect = rect.intersection(&image_rect); 250 | if !image_intersect.is_empty() { 251 | display.roi_mut(&image_intersect).blend( 252 | &title_image 253 | .roi(&image_intersect.offset(-image_rect.left(), -image_rect.top())), 254 | ); 255 | } 256 | } 257 | 258 | if self.resizable { 259 | x = max(self.x + 6, self.x + self.width() - 36 * self.scale); 260 | if x + 36 * self.scale <= self.x + self.width() { 261 | let image_rect = Rect::new( 262 | x, 263 | title_rect.top() + 7 * self.scale, 264 | window_max.width(), 265 | window_max.height(), 266 | ); 267 | let image_intersect = rect.intersection(&image_rect); 268 | if !image_intersect.is_empty() { 269 | display.roi_mut(&image_intersect).blend( 270 | &window_max.roi( 271 | &image_intersect.offset(-image_rect.left(), -image_rect.top()), 272 | ), 273 | ); 274 | } 275 | } 276 | } 277 | 278 | if !self.unclosable { 279 | x = max( 280 | self.x + 6 * self.scale, 281 | self.x + self.width() - 18 * self.scale, 282 | ); 283 | if x + 18 * self.scale <= self.x + self.width() { 284 | let image_rect = Rect::new( 285 | x, 286 | title_rect.top() + 7 * self.scale, 287 | window_close.width(), 288 | window_close.height(), 289 | ); 290 | let image_intersect = rect.intersection(&image_rect); 291 | if !image_intersect.is_empty() { 292 | display.roi_mut(&image_intersect).blend( 293 | &window_close.roi( 294 | &image_intersect.offset(-image_rect.left(), -image_rect.top()), 295 | ), 296 | ); 297 | } 298 | } 299 | } 300 | } 301 | } 302 | 303 | pub fn draw(&self, display: &mut Display, rect: &Rect) { 304 | let self_rect = self.rect(); 305 | let intersect = self_rect.intersection(rect); 306 | if !intersect.is_empty() { 307 | if self.transparent { 308 | display.roi_mut(&intersect).blend( 309 | &self 310 | .image 311 | .roi(&intersect.offset(-self_rect.left(), -self_rect.top())), 312 | ); 313 | } else { 314 | display.roi_mut(&intersect).blit( 315 | &self 316 | .image 317 | .roi(&intersect.offset(-self_rect.left(), -self_rect.top())), 318 | ); 319 | } 320 | } 321 | } 322 | 323 | pub fn event(&mut self, event: Event) { 324 | // Combine or replace the last event for some event types where it improves latency without disrupting logic 325 | if let Some(last_event) = self.events.back_mut() { 326 | if last_event.code == event.code { 327 | match event.code { 328 | // Absolute mouse events, window move, window resize, and screen report events can be replaced 329 | orbclient::EVENT_MOUSE 330 | | orbclient::EVENT_MOVE 331 | | orbclient::EVENT_RESIZE 332 | | orbclient::EVENT_SCREEN => { 333 | *last_event = event; 334 | return; 335 | } 336 | // Relative mouse events and scroll events can be combined with addition 337 | orbclient::EVENT_MOUSE_RELATIVE | orbclient::EVENT_SCROLL => { 338 | last_event.a += event.a; 339 | last_event.b += event.b; 340 | return; 341 | } 342 | // Other events cannot be combined or replaced 343 | _ => {} 344 | } 345 | } 346 | } 347 | 348 | // Push event if not combined or replaced 349 | self.events.push_back(event); 350 | } 351 | 352 | pub fn map(&mut self) -> &mut [Color] { 353 | self.image.data_mut() 354 | } 355 | 356 | pub fn read(&mut self, buf: &mut [Event]) -> usize { 357 | for (i, event) in buf.iter_mut().enumerate() { 358 | *event = match self.events.pop_front() { 359 | Some(item) => item, 360 | None => return i, 361 | }; 362 | } 363 | buf.len() 364 | } 365 | 366 | pub fn properties(&self) -> Properties<'_> { 367 | //TODO: avoid allocation 368 | let mut flags = String::with_capacity(9); 369 | if self.asynchronous { 370 | flags.push(ORBITAL_FLAG_ASYNC); 371 | } 372 | if self.borderless { 373 | flags.push(ORBITAL_FLAG_BORDERLESS); 374 | } 375 | if self.hidden { 376 | flags.push(ORBITAL_FLAG_HIDDEN); 377 | } 378 | if let Some((_, position)) = &self.restore { 379 | flags.push(ORBITAL_FLAG_MAXIMIZED); 380 | if matches!(position, TilePosition::FullScreen) { 381 | flags.push(ORBITAL_FLAG_FULLSCREEN); 382 | } 383 | } 384 | if self.resizable { 385 | flags.push(ORBITAL_FLAG_RESIZABLE); 386 | } 387 | if self.transparent { 388 | flags.push(ORBITAL_FLAG_TRANSPARENT); 389 | } 390 | if self.unclosable { 391 | flags.push(ORBITAL_FLAG_UNCLOSABLE); 392 | } 393 | match self.zorder { 394 | WindowZOrder::Back => flags.push(ORBITAL_FLAG_BACK), 395 | WindowZOrder::Normal => {} 396 | WindowZOrder::Front => flags.push(ORBITAL_FLAG_FRONT), 397 | } 398 | Properties { 399 | flags, 400 | x: self.x, 401 | y: self.y, 402 | width: self.width(), 403 | height: self.height(), 404 | title: &self.title, 405 | } 406 | } 407 | 408 | pub fn render_title(&mut self, font: &Font) { 409 | let text_color = self.config.text_color; 410 | let text_highlight_color = self.config.text_highlight_color; 411 | 412 | let title_render = font.render(&self.title, (TITLE_TEXT_HEIGHT * self.scale) as f32); 413 | 414 | let color_blank = Color::rgba(0, 0, 0, 0); 415 | 416 | self.title_image = Image::from_color( 417 | title_render.width() as i32, 418 | title_render.height() as i32, 419 | color_blank, 420 | ); 421 | self.title_image.mode().set(orbclient::Mode::Overwrite); 422 | title_render.draw(&mut self.title_image, 0, 0, text_highlight_color.into()); 423 | 424 | self.title_image_unfocused = Image::from_color( 425 | title_render.width() as i32, 426 | title_render.height() as i32, 427 | color_blank, 428 | ); 429 | self.title_image_unfocused 430 | .mode() 431 | .set(orbclient::Mode::Overwrite); 432 | title_render.draw(&mut self.title_image_unfocused, 0, 0, text_color.into()); 433 | } 434 | 435 | pub fn set_flag(&mut self, flag: char, value: bool) { 436 | match flag { 437 | ORBITAL_FLAG_ASYNC => self.asynchronous = value, 438 | ORBITAL_FLAG_BACK => { 439 | self.zorder = if value { 440 | WindowZOrder::Back 441 | } else { 442 | WindowZOrder::Normal 443 | } 444 | } 445 | ORBITAL_FLAG_FRONT => { 446 | self.zorder = if value { 447 | WindowZOrder::Front 448 | } else { 449 | WindowZOrder::Normal 450 | } 451 | } 452 | ORBITAL_FLAG_HIDDEN => self.hidden = value, 453 | ORBITAL_FLAG_BORDERLESS => self.borderless = value, 454 | ORBITAL_FLAG_RESIZABLE => self.resizable = value, 455 | ORBITAL_FLAG_TRANSPARENT => self.transparent = value, 456 | ORBITAL_FLAG_UNCLOSABLE => self.unclosable = value, 457 | _ => { 458 | log::warn!("unknown window flag {:?}", flag); 459 | } 460 | } 461 | } 462 | 463 | pub fn set_size(&mut self, w: i32, h: i32) { 464 | if self.maps > 0 { 465 | log::warn!("resized while {} mapping(s) still held", self.maps); 466 | } 467 | 468 | //TODO: Invalidate old mappings 469 | let mut new_image = ImageAligned::new(w, h, 4096); 470 | let new_rect = Rect::new(0, 0, w, h); 471 | 472 | let rect = Rect::new(0, 0, self.image.width(), self.image.height()); 473 | let intersect = new_rect.intersection(&rect); 474 | if !intersect.is_empty() { 475 | new_image 476 | .roi_mut(&intersect) 477 | .blit(&self.image.roi(&intersect)); 478 | } 479 | 480 | self.image = new_image; 481 | } 482 | } 483 | 484 | #[cfg(test)] 485 | mod test { 486 | use crate::config::Config; 487 | use crate::window::Window; 488 | use orbclient::{Color, Event}; 489 | use std::rc::Rc; 490 | 491 | // create a default config that can be used to create Windows for testing 492 | // TODO implement or derive Default for orbclient::Color and then just use Config::default() 493 | fn test_config() -> Config { 494 | Config { 495 | cursor: String::default(), 496 | bottom_left_corner: String::default(), 497 | bottom_right_corner: String::default(), 498 | bottom_side: String::default(), 499 | left_side: String::default(), 500 | right_side: String::default(), 501 | window_max: String::default(), 502 | window_max_unfocused: String::default(), 503 | window_close: String::default(), 504 | window_close_unfocused: String::default(), 505 | 506 | background_color: Color::rgba(1, 2, 3, 200).into(), 507 | bar_color: Color::rgba(1, 2, 3, 200).into(), 508 | bar_highlight_color: Color::rgba(1, 2, 3, 200).into(), 509 | text_color: Color::rgba(1, 2, 3, 200).into(), 510 | text_highlight_color: Color::rgba(1, 2, 3, 200).into(), 511 | } 512 | } 513 | 514 | #[test] 515 | fn read_limited_to_buffer_size() { 516 | // create a test Window 517 | let dummy_config = test_config(); 518 | let mut window = Window::new(0, 0, 100, 100, 1, Rc::new(dummy_config)); 519 | 520 | // Add three events to the window's queue of events 521 | let mut event_1 = Event::new(); 522 | event_1.code = 1; 523 | window.events.push_back(event_1); 524 | let mut event_2 = Event::new(); 525 | event_2.code = 2; 526 | window.events.push_back(event_2); 527 | let mut event_3 = Event::new(); 528 | event_3.code = 3; 529 | window.events.push_back(event_3); 530 | 531 | // Our buffer (elements must be initialized!) will only have a length of 2 532 | let mut buf: Vec = vec![Event::new(), Event::new()]; // code = 0 533 | assert_eq!( 534 | buf.as_mut_slice().len(), 535 | 2, 536 | "Buffer is not of length 2 as expected" 537 | ); 538 | 539 | // let's try and read three events from the queue into the buffer of size two 540 | assert_eq!( 541 | window.read(buf.as_mut_slice()), 542 | 2, 543 | "Did not read two events as expected" 544 | ); 545 | // we should not crash with an indexing error beyond the length of the vectors/slices passed to read() 546 | 547 | // buf contains the correct events in the correct order 548 | let code = buf[0].code; // avoid misaligned access for packed Event :-( 549 | assert_eq!(code, 1); 550 | let code = buf[1].code; // avoid misaligned access for packed Event :-( 551 | assert_eq!(code, 2); 552 | } 553 | 554 | #[test] 555 | fn read_limited_to_available_events() { 556 | // create a test Window 557 | let dummy_config = test_config(); 558 | let mut window = Window::new(0, 0, 100, 100, 1, Rc::new(dummy_config)); 559 | 560 | // Add two events to the window's queue of events 561 | let mut event_1 = Event::new(); 562 | event_1.code = 1; 563 | window.events.push_back(event_1); 564 | let mut event_2 = Event::new(); 565 | event_2.code = 2; 566 | window.events.push_back(event_2); 567 | 568 | // Our buffer (elements must be initialized!) will have a length of 4 569 | let mut buf: Vec = vec![Event::new(), Event::new(), Event::new(), Event::new()]; 570 | assert_eq!( 571 | buf.as_mut_slice().len(), 572 | 4, 573 | "Buffer is not of length 4 as expected" 574 | ); 575 | 576 | // let's try and read 2 events from the queue into the buffer 577 | assert_eq!( 578 | window.read(buf.as_mut_slice()), 579 | 2, 580 | "Did not read two events as expected" 581 | ); 582 | // we should not panic with an indexing error beyond the length of the windows event queue 583 | 584 | // buf contains the correct events in the correct order 585 | let code = buf[0].code; // avoid misaligned access for packed Event :-( 586 | assert_eq!(code, 1); 587 | let code = buf[1].code; // avoid misaligned access for packed Event :-( 588 | assert_eq!(code, 2); 589 | } 590 | 591 | #[test] 592 | fn read_empty_queue_returns_zero() { 593 | // create a test Window 594 | let dummy_config = test_config(); 595 | let mut window = Window::new(0, 0, 100, 100, 1, Rc::new(dummy_config)); 596 | 597 | // Our buffer (elements must be initialized!) will have a length of 2 598 | let mut buf: Vec = vec![Event::new(), Event::new()]; 599 | assert_eq!( 600 | buf.as_mut_slice().len(), 601 | 2, 602 | "Buffer is not of length 2 as expected" 603 | ); 604 | 605 | // let's try and read events from the empty queue into the buffer 606 | assert_eq!( 607 | window.read(buf.as_mut_slice()), 608 | 0, 609 | "Did not expect to read any events" 610 | ); 611 | } 612 | } 613 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | env, 4 | fs::File, 5 | io::{self, ErrorKind, Read, Write}, 6 | mem, 7 | os::unix::io::{AsRawFd, FromRawFd, RawFd}, 8 | slice, str, 9 | }; 10 | 11 | use event::{EventQueue, user_data}; 12 | use libredox::flag; 13 | use log::{debug, error}; 14 | use orbclient::{Color, Event}; 15 | use redox_scheme::{ 16 | CallerCtx, OpenResult, RequestKind, Response, SignalBehavior, Socket, 17 | scheme::{IntoTag, Op, OpRead, SchemeSync}, 18 | }; 19 | use syscall::{ 20 | EAGAIN, ECANCELED, EOPNOTSUPP, EWOULDBLOCK, error::EINVAL, flag::EventFlags, 21 | schemev2::NewFdFlags, 22 | }; 23 | 24 | use crate::scheme::OrbitalScheme; 25 | use display::Display; 26 | 27 | pub(crate) mod display; 28 | pub(crate) mod image; 29 | pub(crate) mod rect; 30 | 31 | #[cfg(target_pointer_width = "32")] 32 | const CLIPBOARD_FLAG: usize = 1 << 31; 33 | 34 | #[cfg(target_pointer_width = "64")] 35 | const CLIPBOARD_FLAG: usize = 1 << 63; 36 | 37 | #[derive(Debug, thiserror::Error)] 38 | pub enum Error { 39 | #[error("io error")] 40 | IoError(#[from] io::Error), 41 | #[error("syscall error: {0}")] 42 | SyscallError(syscall::Error), 43 | #[error("system error")] 44 | LibredoxError(#[from] libredox::error::Error), 45 | } 46 | impl From for Error { 47 | fn from(err: syscall::Error) -> Self { 48 | Error::SyscallError(err) 49 | } 50 | } 51 | 52 | /// Convenience function for setting DISPLAY environment variable 53 | pub fn fix_env(display_path: &str) -> io::Result<()> { 54 | unsafe { 55 | env::set_var("DISPLAY", display_path); 56 | } 57 | Ok(()) 58 | } 59 | 60 | fn read_to_slice(mut r: R, buf: &mut [T]) -> io::Result { 61 | unsafe { 62 | r.read(slice::from_raw_parts_mut( 63 | buf.as_mut_ptr() as *mut u8, 64 | buf.len() * mem::size_of::(), 65 | )) 66 | .map(|count| count / mem::size_of::()) 67 | } 68 | } 69 | 70 | pub struct Properties<'a> { 71 | //TODO: avoid allocation 72 | pub flags: String, 73 | pub x: i32, 74 | pub y: i32, 75 | pub width: i32, 76 | pub height: i32, 77 | pub title: &'a str, 78 | } 79 | 80 | pub struct Orbital { 81 | pub scheme: Socket, 82 | pub delayed: VecDeque<(CallerCtx, OpRead)>, 83 | 84 | /// Handle to "/scheme/input/consumer" to receive input events. 85 | pub input: File, 86 | } 87 | 88 | impl Orbital { 89 | fn url_parts(url: &str) -> io::Result<(&str, &str)> { 90 | let mut url_parts = url.split(':'); 91 | let scheme_name = url_parts.next().ok_or(io::Error::new( 92 | ErrorKind::Other, 93 | "Could not get scheme name from url", 94 | ))?; 95 | let path = url_parts.next().ok_or(io::Error::new( 96 | ErrorKind::Other, 97 | "Could not get path from url", 98 | ))?; 99 | Ok((scheme_name, path)) 100 | } 101 | 102 | fn parse_display_path(path: &str) -> (&str, i32, i32) { 103 | let mut path_parts = path.split('/'); 104 | let vt_screen = path_parts.next().unwrap_or(""); 105 | let width = path_parts.next().unwrap_or("").parse::().unwrap_or(0); 106 | let height = path_parts.next().unwrap_or("").parse::().unwrap_or(0); 107 | 108 | (vt_screen, width, height) 109 | } 110 | 111 | /// Open an orbital display and connect to the scheme 112 | pub fn open_display(vt: &str) -> io::Result<(Self, Vec)> { 113 | let mut buffer = [0; 1024]; 114 | 115 | let input_handle = File::open(format!("/scheme/input/consumer/{vt}"))?; 116 | let fd = input_handle.as_raw_fd(); 117 | 118 | let written = libredox::call::fpath(fd as usize, &mut buffer) 119 | .expect("init: failed to get the path to the display device"); 120 | 121 | assert!(written <= buffer.len()); 122 | 123 | let display_path = 124 | std::str::from_utf8(&buffer[..written]).expect("init: display path UTF-8 check failed"); 125 | 126 | fix_env(&display_path)?; 127 | 128 | let display = libredox::call::open( 129 | display_path, 130 | flag::O_CLOEXEC | flag::O_NONBLOCK | flag::O_RDWR, 131 | 0, 132 | ) 133 | .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) 134 | .map_err(|err| { 135 | error!("failed to open display {}: {}", display_path, err); 136 | io::Error::from_raw_os_error(err.errno()) 137 | })?; 138 | 139 | let scheme = Socket::nonblock("orbital").map_err(|err| { 140 | error!("failed to open '/scheme/orbital': {}", err); 141 | err 142 | })?; 143 | 144 | let mut buf: [u8; 4096] = [0; 4096]; 145 | let count = libredox::call::fpath(display.as_raw_fd() as usize, &mut buf).map_err(|e| { 146 | io::Error::new( 147 | ErrorKind::Other, 148 | format!("Could not read display path with fpath(): {e}"), 149 | ) 150 | })?; 151 | 152 | let url = String::from_utf8(Vec::from(&buf[..count])) 153 | .map_err(|_| io::Error::new(ErrorKind::Other, "Could not create Utf8 Url String"))?; 154 | let (scheme_name, path) = Self::url_parts(&url)?; 155 | let (vt_screen, width, height) = Self::parse_display_path(path); 156 | let mut displays = vec![Display::new(0, 0, width, height, display)?]; 157 | 158 | // If display server supports multiple displays in a VT 159 | if vt_screen.contains('.') { 160 | // Look for other screens in the same VT 161 | let mut parts = vt_screen.split('.'); 162 | let vt_i = parts.next().unwrap_or("").parse::().unwrap_or(0); 163 | let start_screen_i = parts.next().unwrap_or("").parse::().unwrap_or(0); 164 | //TODO: determine maximum number of screens 165 | for screen_i in start_screen_i + 1..1024 { 166 | let extra_path = format!("/scheme/{}/{}.{}", scheme_name, vt_i, screen_i); 167 | let extra_file = match libredox::call::open( 168 | &extra_path, 169 | flag::O_CLOEXEC | flag::O_NONBLOCK | flag::O_RDWR, 170 | 0, 171 | ) { 172 | Ok(socket) => unsafe { File::from_raw_fd(socket as RawFd) }, 173 | Err(_err) => break, 174 | }; 175 | 176 | let mut buf: [u8; 4096] = [0; 4096]; 177 | let count = libredox::call::fpath(extra_file.as_raw_fd() as usize, &mut buf) 178 | .map_err(|_| { 179 | io::Error::new(ErrorKind::Other, "Could not open extra_file as_raw_fd()") 180 | })?; 181 | 182 | let url = String::from_utf8(Vec::from(&buf[..count])).map_err(|_| { 183 | io::Error::new(ErrorKind::Other, "Could not create Utf8 Url String") 184 | })?; 185 | 186 | let (_scheme_name, path) = Self::url_parts(&url)?; 187 | let (_vt_screen, width, height) = Self::parse_display_path(path); 188 | 189 | let x = if let Some(last) = displays.last() { 190 | last.x + last.image.width() 191 | } else { 192 | 0 193 | }; 194 | let y = 0; 195 | 196 | debug!( 197 | "Extra display {} at {}, {}, {}, {}", 198 | screen_i, x, y, width, height 199 | ); 200 | 201 | displays.push(Display::new(x, y, width, height, extra_file)?); 202 | } 203 | } 204 | 205 | Ok(( 206 | Orbital { 207 | scheme, 208 | delayed: VecDeque::new(), 209 | input: input_handle, 210 | }, 211 | displays, 212 | )) 213 | } 214 | 215 | /// Write a Packet to scheme I/O 216 | pub fn scheme_write(&mut self, response: Response) -> io::Result<()> { 217 | self.scheme 218 | .write_response(response, SignalBehavior::Restart)?; 219 | Ok(()) 220 | } 221 | 222 | /// Start the main loop 223 | pub fn run(self, handler: OrbitalScheme) -> Result<(), Error> { 224 | user_data! { 225 | enum Source { 226 | Scheme, 227 | Input, 228 | } 229 | } 230 | 231 | let event_queue = EventQueue::::new()?; 232 | 233 | //TODO: Figure out why rand: gets opened after this: libredox::call::setrens(0, 0)?; 234 | 235 | let scheme_fd = self.scheme.inner().raw(); 236 | let input_fd = self.input.as_raw_fd(); 237 | 238 | let mut me = OrbitalHandler { orb: self, handler }; 239 | event_queue.subscribe(scheme_fd, Source::Scheme, event::EventFlags::READ)?; 240 | event_queue.subscribe(input_fd as usize, Source::Input, event::EventFlags::READ)?; 241 | 242 | let mut event_iter = event_queue.map(|e| e.map(|e| e.user_data)); 243 | let mut fake_input_event = None; // TODO: a hack 244 | let mut request_buf = Vec::with_capacity(16); 245 | 246 | 'events: while let Some(event_res) = fake_input_event.take().or_else(|| event_iter.next()) { 247 | match event_res? { 248 | Source::Scheme => { 249 | loop { 250 | match me 251 | .orb 252 | .scheme 253 | .read_requests(&mut request_buf, SignalBehavior::Restart) 254 | { 255 | Ok(()) => (), 256 | Err(err) => { 257 | if err.errno == EWOULDBLOCK || err.errno == EAGAIN { 258 | continue 'events; 259 | } else { 260 | return Err(err.into()); 261 | } 262 | } 263 | } 264 | if request_buf.is_empty() { 265 | break 'events; 266 | } 267 | for request in request_buf.drain(..) { 268 | let req = match request.kind() { 269 | RequestKind::Call(req) => req, 270 | RequestKind::OnClose { id } => { 271 | me.on_close(id); 272 | continue; 273 | } 274 | // TODO: faster than search? 275 | RequestKind::Cancellation(req) => { 276 | if let Some(idx) = me 277 | .orb 278 | .delayed 279 | .iter() 280 | .position(|(_, op)| op.req_id() == req.id) 281 | { 282 | let (_, op) = me 283 | .orb 284 | .delayed 285 | .remove(idx) 286 | .expect("already found at index"); 287 | me.orb.scheme_write(Response::err(ECANCELED, op))?; 288 | } 289 | fake_input_event = Some(Ok(Source::Input)); 290 | continue; 291 | } 292 | _ => continue, // TODO? 293 | }; 294 | let caller_ctx = req.caller(); 295 | let op = match req.op() { 296 | Ok(op) => op, 297 | Err(req) => { 298 | me.orb.scheme_write(Response::err(EOPNOTSUPP, req))?; 299 | continue; 300 | } 301 | }; 302 | if let Op::Read(mut read_op) = op { 303 | let should_delay = me.handler.should_delay(read_op.fd); 304 | let res = me.read( 305 | read_op.fd, 306 | read_op.buf(), 307 | // dont-care 308 | 0, 309 | // dont-care 310 | 0, 311 | &caller_ctx, 312 | ); 313 | if should_delay && res == Ok(0) { 314 | me.orb.delayed.push_back((caller_ctx, read_op)); 315 | } else { 316 | me.orb.scheme_write(Response::new(res, read_op))?; 317 | } 318 | } else { 319 | let resp = op.handle_sync(caller_ctx, &mut me); 320 | me.orb.scheme_write(resp)?; 321 | } 322 | } 323 | me.handler.handle_after(&mut me.orb)?; 324 | } 325 | } 326 | Source::Input => { 327 | let mut events = [Event::new(); 16]; 328 | loop { 329 | match read_to_slice(&mut me.orb.input, &mut events)? { 330 | 0 => break, 331 | count => { 332 | let events = &mut events[..count]; 333 | 334 | let mut delayed_left = me.orb.delayed.len(); 335 | 336 | while delayed_left > 0 337 | && let Some((ctx, mut read_op)) = me.orb.delayed.pop_front() 338 | { 339 | delayed_left -= 1; 340 | 341 | let should_delay = me.handler.should_delay(read_op.fd); 342 | 343 | // TODO: deduplicate with the same code above 344 | let res = me.read( 345 | read_op.fd, 346 | read_op.buf(), 347 | // dont-care 348 | 0, 349 | // dont-care 350 | 0, 351 | &ctx, 352 | ); 353 | if should_delay && res == Ok(0) { 354 | me.orb.delayed.push_back((ctx, read_op)); 355 | } else { 356 | me.orb.scheme_write(Response::new(res, read_op))?; 357 | } 358 | } 359 | 360 | me.handler.handle_input(events); 361 | } 362 | } 363 | } 364 | me.handler.handle_after(&mut me.orb)?; 365 | } 366 | } 367 | } 368 | 369 | //TODO: Cleanup and handle TODO 370 | Ok(()) 371 | } 372 | } 373 | pub struct OrbitalHandler { 374 | orb: Orbital, 375 | handler: OrbitalScheme, 376 | } 377 | impl SchemeSync for OrbitalHandler { 378 | fn open(&mut self, path: &str, _flags: usize, _ctx: &CallerCtx) -> syscall::Result { 379 | let mut parts = path.split('/'); 380 | 381 | let path_first_char = path.chars().nth(0).unwrap_or('\0'); 382 | let flags = if path_first_char.is_ascii_digit() || path_first_char == '-' { 383 | // to handle case like `/scheme/orbital//` being assumed as one slash 384 | "" 385 | } else { 386 | parts.next().unwrap_or("") 387 | }; 388 | 389 | let x = parts.next().unwrap_or("").parse::().unwrap_or(0); 390 | let y = parts.next().unwrap_or("").parse::().unwrap_or(0); 391 | let width = parts.next().unwrap_or("").parse::().unwrap_or(0); 392 | let height = parts.next().unwrap_or("").parse::().unwrap_or(0); 393 | 394 | let mut title = parts.next().unwrap_or("").to_string(); 395 | for part in parts { 396 | title.push('/'); 397 | title.push_str(part); 398 | } 399 | 400 | let id = self 401 | .handler 402 | .handle_window_new(x, y, width, height, flags, title)?; 403 | Ok(OpenResult::ThisScheme { 404 | number: id, 405 | flags: NewFdFlags::empty(), 406 | }) 407 | } 408 | fn dup(&mut self, id: usize, buf: &[u8], _ctx: &CallerCtx) -> syscall::Result { 409 | if buf == b"clipboard" { 410 | //TODO: implement better clipboard mechanism 411 | let id = self 412 | .handler 413 | .handle_clipboard_new(id) 414 | .map(|id| id | CLIPBOARD_FLAG)?; 415 | Ok(OpenResult::ThisScheme { 416 | number: id, 417 | flags: NewFdFlags::empty(), 418 | }) 419 | } else { 420 | Err(syscall::Error::new(EINVAL)) 421 | } 422 | } 423 | fn read( 424 | &mut self, 425 | id: usize, 426 | buf: &mut [u8], 427 | _offset: u64, 428 | _flags: u32, 429 | _ctx: &CallerCtx, 430 | ) -> syscall::Result { 431 | //TODO: implement better clipboard mechanism 432 | if id & CLIPBOARD_FLAG == CLIPBOARD_FLAG { 433 | return self 434 | .handler 435 | .handle_clipboard_read(id & !CLIPBOARD_FLAG, buf); 436 | } 437 | 438 | let slice: &mut [Event] = unsafe { 439 | slice::from_raw_parts_mut( 440 | buf.as_mut_ptr() as *mut Event, 441 | buf.len() / mem::size_of::(), 442 | ) 443 | }; 444 | let n = self.handler.handle_window_read(id, slice)?; 445 | Ok(n * mem::size_of::()) 446 | } 447 | fn write( 448 | &mut self, 449 | id: usize, 450 | buf: &[u8], 451 | _offset: u64, 452 | _flags: u32, 453 | _ctx: &CallerCtx, 454 | ) -> syscall::Result { 455 | //TODO: implement better clipboard mechanism 456 | if id & CLIPBOARD_FLAG == CLIPBOARD_FLAG { 457 | return self 458 | .handler 459 | .handle_clipboard_write(id & !CLIPBOARD_FLAG, buf); 460 | } 461 | 462 | if let Ok(msg) = str::from_utf8(buf) { 463 | let (kind, data) = { 464 | let mut parts = msg.splitn(2, ','); 465 | let kind = parts.next().unwrap_or(""); 466 | let data = parts.next().unwrap_or(""); 467 | (kind, data) 468 | }; 469 | match kind { 470 | "A" => match data { 471 | "0" => { 472 | self.handler.handle_window_async(id, false)?; 473 | Ok(buf.len()) 474 | } 475 | "1" => { 476 | self.handler.handle_window_async(id, true)?; 477 | Ok(buf.len()) 478 | } 479 | _ => Err(syscall::Error::new(EINVAL)), 480 | }, 481 | "D" => match data { 482 | "" => { 483 | self.handler.handle_window_drag(id)?; 484 | Ok(buf.len()) 485 | } 486 | //TODO: resize by dragging edge 487 | // Comma separated 488 | // B is bottom 489 | // L is left 490 | // R is right 491 | // T is top 492 | _ => Err(syscall::Error::new(EINVAL)), 493 | }, 494 | "F" => { 495 | let mut parts = data.split(','); 496 | let flags = parts.next().unwrap_or(""); 497 | let value = match parts.next().unwrap_or("") { 498 | "0" => false, 499 | "1" => true, 500 | _ => return Err(syscall::Error::new(EINVAL)), 501 | }; 502 | for flag in flags.chars() { 503 | self.handler.handle_window_set_flag(id, flag, value)?; 504 | } 505 | Ok(buf.len()) 506 | } 507 | "M" => match data { 508 | "C,0" => { 509 | self.handler.handle_window_mouse_cursor(id, false)?; 510 | Ok(buf.len()) 511 | } 512 | "C,1" => { 513 | self.handler.handle_window_mouse_cursor(id, true)?; 514 | Ok(buf.len()) 515 | } 516 | "G,0" => { 517 | self.handler.handle_window_mouse_grab(id, false)?; 518 | Ok(buf.len()) 519 | } 520 | "G,1" => { 521 | self.handler.handle_window_mouse_grab(id, true)?; 522 | Ok(buf.len()) 523 | } 524 | "R,0" => { 525 | self.handler.handle_window_mouse_relative(id, false)?; 526 | Ok(buf.len()) 527 | } 528 | "R,1" => { 529 | self.handler.handle_window_mouse_relative(id, true)?; 530 | Ok(buf.len()) 531 | } 532 | _ => Err(syscall::Error::new(EINVAL)), 533 | }, 534 | "P" => { 535 | let mut parts = data.split(','); 536 | let x = parts.next().unwrap_or("").parse::().ok(); 537 | let y = parts.next().unwrap_or("").parse::().ok(); 538 | 539 | self.handler.handle_window_position(id, x, y)?; 540 | 541 | Ok(buf.len()) 542 | } 543 | "S" => { 544 | let mut parts = data.split(','); 545 | let w = parts.next().unwrap_or("").parse::().ok(); 546 | let h = parts.next().unwrap_or("").parse::().ok(); 547 | 548 | self.handler.handle_window_resize(id, w, h)?; 549 | 550 | Ok(buf.len()) 551 | } 552 | "T" => { 553 | self.handler.handle_window_title(id, data.to_string())?; 554 | 555 | Ok(buf.len()) 556 | } 557 | _ => Err(syscall::Error::new(EINVAL)), 558 | } 559 | } else { 560 | Err(syscall::Error::new(EINVAL)) 561 | } 562 | } 563 | fn fevent( 564 | &mut self, 565 | id: usize, 566 | _flags: EventFlags, 567 | _ctx: &CallerCtx, 568 | ) -> syscall::Result { 569 | self.handler 570 | .handle_window_clear_notified(id) 571 | .and(Ok(EventFlags::empty())) 572 | } 573 | /* 574 | fn fmap(&mut self, id: usize, map: &syscall::Map) -> syscall::Result { 575 | let page_size = 4096; 576 | let map_pages = (map.offset + map.size + page_size - 1)/page_size; 577 | let data = self.handler.handle_window_map(&mut self.orb, id)?; 578 | let data_addr = data.as_mut_ptr() as usize; 579 | let data_size = data.len() * mem::size_of::(); 580 | // Do not allow leaking data before or after window to the user 581 | if data_addr & (page_size - 1) == 0 && map_pages * page_size <= data_size { 582 | let address = data_addr + map.offset; 583 | self.orb.maps.insert(address, (id, map.size)); 584 | Ok(address) 585 | } else { 586 | self.handler.handle_window_unmap(&mut self.orb, id)?; 587 | Err(syscall::Error::new(EINVAL)) 588 | } 589 | } 590 | fn funmap(&mut self, address: usize, size: usize) -> syscall::Result { 591 | match self.orb.maps.remove(&address) { 592 | Some((id, map_size)) => { 593 | if size != map_size { 594 | log::warn!("orbital: mapping 0x{:x} has size {} instead of {}", address, map_size, size); 595 | } 596 | self.handler.handle_window_unmap(&mut self.orb, id)?; 597 | }, 598 | None => { 599 | error!("failed to found mapping 0x{:x}", address); 600 | } 601 | } 602 | Ok(0) 603 | } 604 | */ 605 | fn fpath(&mut self, id: usize, mut buf: &mut [u8], _ctx: &CallerCtx) -> syscall::Result { 606 | let props = self.handler.handle_window_properties(id)?; 607 | let original_len = buf.len(); 608 | #[allow(clippy::write_literal)] // TODO: Z order 609 | let _ = write!( 610 | buf, 611 | "{}/{}/{}/{}/{}/{}", 612 | props.flags, props.x, props.y, props.width, props.height, props.title 613 | ); 614 | Ok(original_len - buf.len()) 615 | } 616 | fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> syscall::Result<()> { 617 | self.handler.handle_window_sync(id) 618 | } 619 | fn mmap_prep( 620 | &mut self, 621 | id: usize, 622 | _offset: u64, 623 | size: usize, 624 | _flags: syscall::MapFlags, 625 | _ctx: &CallerCtx, 626 | ) -> syscall::Result { 627 | //TODO: handle offset, flags? 628 | let data = self.handler.handle_window_map(id, true)?; 629 | 630 | if size > data.len() * core::mem::size_of::() { 631 | return Err(syscall::Error::new(EINVAL)); 632 | } 633 | 634 | Ok(data.as_mut_ptr() as usize) 635 | } 636 | fn munmap( 637 | &mut self, 638 | id: usize, 639 | _offset: u64, 640 | _size: usize, 641 | _flags: syscall::MunmapFlags, 642 | _ctx: &CallerCtx, 643 | ) -> syscall::Result<()> { 644 | //TODO: handle offset, size, flags? 645 | self.handler.handle_window_unmap(id)?; 646 | 647 | Ok(()) 648 | } 649 | } 650 | impl OrbitalHandler { 651 | fn on_close(&mut self, id: usize) { 652 | //TODO: implement better clipboard mechanism 653 | if id & CLIPBOARD_FLAG == CLIPBOARD_FLAG { 654 | return self.handler.handle_clipboard_close(id & !CLIPBOARD_FLAG); 655 | } 656 | 657 | self.handler.handle_window_close(id) 658 | } 659 | } 660 | 661 | #[cfg(test)] 662 | mod test { 663 | use crate::core::Orbital; 664 | 665 | #[test] 666 | fn invalid_url_no_colon() { 667 | assert!(Orbital::url_parts("foo-no-colon").is_err()); 668 | } 669 | 670 | #[test] 671 | fn valid_url_empty_scheme() { 672 | // until we throw an error for an empty scheme_name... 673 | match Orbital::url_parts(":path") { 674 | Ok((scheme_name, path)) => { 675 | assert!(scheme_name.is_empty()); 676 | assert_eq!(path, "path"); 677 | } 678 | _ => panic!("Could not parse url"), 679 | } 680 | } 681 | 682 | #[test] 683 | fn valid_url_empty_path() { 684 | // until we throw an error for an empty scheme_name... 685 | match Orbital::url_parts("scheme:") { 686 | Ok((scheme_name, path)) => { 687 | assert_eq!(scheme_name, "scheme"); 688 | assert!(path.is_empty()); 689 | } 690 | _ => panic!("Could not parse url"), 691 | } 692 | } 693 | 694 | #[test] 695 | fn valid_url() { 696 | match Orbital::url_parts("scheme:path") { 697 | Ok((scheme_name, path)) => { 698 | assert_eq!(scheme_name, "scheme"); 699 | assert_eq!(path, "path"); 700 | } 701 | _ => panic!("Could not parse url"), 702 | } 703 | } 704 | } 705 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ab_glyph_rasterizer" 7 | version = "0.1.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" 10 | 11 | [[package]] 12 | name = "adler32" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 16 | 17 | [[package]] 18 | name = "android_system_properties" 19 | version = "0.1.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 22 | dependencies = [ 23 | "libc", 24 | ] 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.100" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 31 | 32 | [[package]] 33 | name = "autocfg" 34 | version = "1.5.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 37 | 38 | [[package]] 39 | name = "bitflags" 40 | version = "1.3.2" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "2.10.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 49 | 50 | [[package]] 51 | name = "bumpalo" 52 | version = "3.19.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 55 | 56 | [[package]] 57 | name = "byteorder" 58 | version = "1.5.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 61 | 62 | [[package]] 63 | name = "cc" 64 | version = "1.2.49" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" 67 | dependencies = [ 68 | "find-msvc-tools", 69 | "shlex", 70 | ] 71 | 72 | [[package]] 73 | name = "cfg-if" 74 | version = "1.0.4" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 77 | 78 | [[package]] 79 | name = "chrono" 80 | version = "0.4.42" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 83 | dependencies = [ 84 | "iana-time-zone", 85 | "js-sys", 86 | "num-traits", 87 | "wasm-bindgen", 88 | "windows-link", 89 | ] 90 | 91 | [[package]] 92 | name = "cmake" 93 | version = "0.1.56" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" 96 | dependencies = [ 97 | "cc", 98 | ] 99 | 100 | [[package]] 101 | name = "color_quant" 102 | version = "1.1.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 105 | 106 | [[package]] 107 | name = "common" 108 | version = "0.1.0" 109 | source = "git+https://gitlab.redox-os.org/redox-os/base.git#620b4bd80c4f437adcaeec570b6cbba0487506d3" 110 | dependencies = [ 111 | "libredox", 112 | "log", 113 | "redox-log", 114 | "redox_syscall", 115 | ] 116 | 117 | [[package]] 118 | name = "core-foundation" 119 | version = "0.9.4" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 122 | dependencies = [ 123 | "core-foundation-sys", 124 | "libc", 125 | ] 126 | 127 | [[package]] 128 | name = "core-foundation-sys" 129 | version = "0.8.7" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 132 | 133 | [[package]] 134 | name = "core-graphics" 135 | version = "0.22.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 138 | dependencies = [ 139 | "bitflags 1.3.2", 140 | "core-foundation", 141 | "core-graphics-types", 142 | "foreign-types", 143 | "libc", 144 | ] 145 | 146 | [[package]] 147 | name = "core-graphics-types" 148 | version = "0.1.3" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 151 | dependencies = [ 152 | "bitflags 1.3.2", 153 | "core-foundation", 154 | "libc", 155 | ] 156 | 157 | [[package]] 158 | name = "core-text" 159 | version = "19.2.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" 162 | dependencies = [ 163 | "core-foundation", 164 | "core-graphics", 165 | "foreign-types", 166 | "libc", 167 | ] 168 | 169 | [[package]] 170 | name = "crossbeam-deque" 171 | version = "0.8.6" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 174 | dependencies = [ 175 | "crossbeam-epoch", 176 | "crossbeam-utils", 177 | ] 178 | 179 | [[package]] 180 | name = "crossbeam-epoch" 181 | version = "0.9.18" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 184 | dependencies = [ 185 | "crossbeam-utils", 186 | ] 187 | 188 | [[package]] 189 | name = "crossbeam-utils" 190 | version = "0.8.21" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 193 | 194 | [[package]] 195 | name = "daemon" 196 | version = "0.0.0" 197 | source = "git+https://gitlab.redox-os.org/redox-os/base.git#620b4bd80c4f437adcaeec570b6cbba0487506d3" 198 | dependencies = [ 199 | "libc", 200 | ] 201 | 202 | [[package]] 203 | name = "deflate" 204 | version = "0.7.20" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" 207 | dependencies = [ 208 | "adler32", 209 | "byteorder", 210 | ] 211 | 212 | [[package]] 213 | name = "drm-sys" 214 | version = "0.8.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c" 217 | dependencies = [ 218 | "libc", 219 | "linux-raw-sys", 220 | ] 221 | 222 | [[package]] 223 | name = "either" 224 | version = "1.15.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 227 | 228 | [[package]] 229 | name = "equivalent" 230 | version = "1.0.2" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 233 | 234 | [[package]] 235 | name = "expat-sys" 236 | version = "2.1.6" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" 239 | dependencies = [ 240 | "cmake", 241 | "pkg-config", 242 | ] 243 | 244 | [[package]] 245 | name = "find-msvc-tools" 246 | version = "0.1.5" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 249 | 250 | [[package]] 251 | name = "font-loader" 252 | version = "0.11.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" 255 | dependencies = [ 256 | "core-foundation", 257 | "core-text", 258 | "libc", 259 | "servo-fontconfig", 260 | "winapi", 261 | ] 262 | 263 | [[package]] 264 | name = "foreign-types" 265 | version = "0.3.2" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 268 | dependencies = [ 269 | "foreign-types-shared", 270 | ] 271 | 272 | [[package]] 273 | name = "foreign-types-shared" 274 | version = "0.1.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 277 | 278 | [[package]] 279 | name = "freetype-sys" 280 | version = "0.13.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" 283 | dependencies = [ 284 | "cmake", 285 | "libc", 286 | "pkg-config", 287 | ] 288 | 289 | [[package]] 290 | name = "gif" 291 | version = "0.10.3" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" 294 | dependencies = [ 295 | "color_quant", 296 | "lzw", 297 | ] 298 | 299 | [[package]] 300 | name = "graphics-ipc" 301 | version = "0.1.0" 302 | source = "git+https://gitlab.redox-os.org/redox-os/base.git#620b4bd80c4f437adcaeec570b6cbba0487506d3" 303 | dependencies = [ 304 | "common", 305 | "drm-sys", 306 | "libredox", 307 | "log", 308 | ] 309 | 310 | [[package]] 311 | name = "hashbrown" 312 | version = "0.16.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 315 | 316 | [[package]] 317 | name = "iana-time-zone" 318 | version = "0.1.64" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 321 | dependencies = [ 322 | "android_system_properties", 323 | "core-foundation-sys", 324 | "iana-time-zone-haiku", 325 | "js-sys", 326 | "log", 327 | "wasm-bindgen", 328 | "windows-core", 329 | ] 330 | 331 | [[package]] 332 | name = "iana-time-zone-haiku" 333 | version = "0.1.2" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 336 | dependencies = [ 337 | "cc", 338 | ] 339 | 340 | [[package]] 341 | name = "image" 342 | version = "0.21.3" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "35371e467cd7b0b3d1d6013d619203658467df12d61b0ca43cd67b743b1965eb" 345 | dependencies = [ 346 | "byteorder", 347 | "gif", 348 | "jpeg-decoder", 349 | "lzw", 350 | "num-iter", 351 | "num-rational", 352 | "num-traits", 353 | "png", 354 | "scoped_threadpool", 355 | "tiff", 356 | ] 357 | 358 | [[package]] 359 | name = "indexmap" 360 | version = "2.12.1" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" 363 | dependencies = [ 364 | "equivalent", 365 | "hashbrown", 366 | ] 367 | 368 | [[package]] 369 | name = "inflate" 370 | version = "0.4.5" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" 373 | dependencies = [ 374 | "adler32", 375 | ] 376 | 377 | [[package]] 378 | name = "inputd" 379 | version = "0.1.0" 380 | source = "git+https://gitlab.redox-os.org/redox-os/base.git#620b4bd80c4f437adcaeec570b6cbba0487506d3" 381 | dependencies = [ 382 | "anyhow", 383 | "common", 384 | "daemon", 385 | "libredox", 386 | "log", 387 | "orbclient", 388 | "redox-scheme 0.8.2", 389 | "redox_syscall", 390 | ] 391 | 392 | [[package]] 393 | name = "jpeg-decoder" 394 | version = "0.1.22" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 397 | dependencies = [ 398 | "rayon", 399 | ] 400 | 401 | [[package]] 402 | name = "js-sys" 403 | version = "0.3.83" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" 406 | dependencies = [ 407 | "once_cell", 408 | "wasm-bindgen", 409 | ] 410 | 411 | [[package]] 412 | name = "lazy_static" 413 | version = "1.5.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 416 | 417 | [[package]] 418 | name = "libc" 419 | version = "0.2.178" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 422 | 423 | [[package]] 424 | name = "libredox" 425 | version = "0.1.10" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 428 | dependencies = [ 429 | "bitflags 2.10.0", 430 | "libc", 431 | "redox_syscall", 432 | ] 433 | 434 | [[package]] 435 | name = "linux-raw-sys" 436 | version = "0.6.5" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" 439 | 440 | [[package]] 441 | name = "log" 442 | version = "0.4.29" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 445 | 446 | [[package]] 447 | name = "lzw" 448 | version = "0.10.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 451 | 452 | [[package]] 453 | name = "memchr" 454 | version = "2.7.6" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 457 | 458 | [[package]] 459 | name = "num-derive" 460 | version = "0.2.5" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2" 463 | dependencies = [ 464 | "proc-macro2 0.4.30", 465 | "quote 0.6.13", 466 | "syn 0.15.44", 467 | ] 468 | 469 | [[package]] 470 | name = "num-integer" 471 | version = "0.1.46" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 474 | dependencies = [ 475 | "num-traits", 476 | ] 477 | 478 | [[package]] 479 | name = "num-iter" 480 | version = "0.1.45" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 483 | dependencies = [ 484 | "autocfg", 485 | "num-integer", 486 | "num-traits", 487 | ] 488 | 489 | [[package]] 490 | name = "num-rational" 491 | version = "0.2.4" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" 494 | dependencies = [ 495 | "autocfg", 496 | "num-integer", 497 | "num-traits", 498 | ] 499 | 500 | [[package]] 501 | name = "num-traits" 502 | version = "0.2.19" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 505 | dependencies = [ 506 | "autocfg", 507 | ] 508 | 509 | [[package]] 510 | name = "numtoa" 511 | version = "0.2.4" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" 514 | 515 | [[package]] 516 | name = "once_cell" 517 | version = "1.21.3" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 520 | 521 | [[package]] 522 | name = "orbclient" 523 | version = "0.3.49" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "247ad146e19b9437f8604c21f8652423595cf710ad108af40e77d3ae6e96b827" 526 | dependencies = [ 527 | "libc", 528 | "libredox", 529 | "sdl2", 530 | "sdl2-sys", 531 | ] 532 | 533 | [[package]] 534 | name = "orbfont" 535 | version = "0.1.12" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "fe433418f793feebe362f5dff3110a5de5a8e55c4605aec75dddc4ec3e708aa1" 538 | dependencies = [ 539 | "font-loader", 540 | "orbclient", 541 | "rusttype", 542 | ] 543 | 544 | [[package]] 545 | name = "orbimage" 546 | version = "0.1.17" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "39fe3f9612b797a312bf122a67ebce408936b34996b9be419dbf0b3d4638c424" 549 | dependencies = [ 550 | "image", 551 | "orbclient", 552 | "resize", 553 | ] 554 | 555 | [[package]] 556 | name = "orbital" 557 | version = "0.1.0" 558 | dependencies = [ 559 | "ab_glyph_rasterizer", 560 | "graphics-ipc", 561 | "inputd", 562 | "libc", 563 | "libredox", 564 | "log", 565 | "orbclient", 566 | "orbfont", 567 | "orbimage", 568 | "redox-log", 569 | "redox-scheme 0.6.2", 570 | "redox_event", 571 | "redox_syscall", 572 | "serde", 573 | "serde_derive", 574 | "thiserror", 575 | "toml", 576 | ] 577 | 578 | [[package]] 579 | name = "owned_ttf_parser" 580 | version = "0.15.2" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" 583 | dependencies = [ 584 | "ttf-parser", 585 | ] 586 | 587 | [[package]] 588 | name = "pkg-config" 589 | version = "0.3.32" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 592 | 593 | [[package]] 594 | name = "png" 595 | version = "0.14.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "63daf481fdd0defa2d1d2be15c674fbfa1b0fd71882c303a91f9a79b3252c359" 598 | dependencies = [ 599 | "bitflags 1.3.2", 600 | "deflate", 601 | "inflate", 602 | "num-iter", 603 | ] 604 | 605 | [[package]] 606 | name = "proc-macro2" 607 | version = "0.4.30" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 610 | dependencies = [ 611 | "unicode-xid", 612 | ] 613 | 614 | [[package]] 615 | name = "proc-macro2" 616 | version = "1.0.103" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 619 | dependencies = [ 620 | "unicode-ident", 621 | ] 622 | 623 | [[package]] 624 | name = "quote" 625 | version = "0.6.13" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 628 | dependencies = [ 629 | "proc-macro2 0.4.30", 630 | ] 631 | 632 | [[package]] 633 | name = "quote" 634 | version = "1.0.42" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 637 | dependencies = [ 638 | "proc-macro2 1.0.103", 639 | ] 640 | 641 | [[package]] 642 | name = "rayon" 643 | version = "1.11.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" 646 | dependencies = [ 647 | "either", 648 | "rayon-core", 649 | ] 650 | 651 | [[package]] 652 | name = "rayon-core" 653 | version = "1.13.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" 656 | dependencies = [ 657 | "crossbeam-deque", 658 | "crossbeam-utils", 659 | ] 660 | 661 | [[package]] 662 | name = "redox-log" 663 | version = "0.1.4" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "81460b1526438123d16f0c968dbe42ba7f61e99645109b70e57864a8b66710fb" 666 | dependencies = [ 667 | "chrono", 668 | "log", 669 | "smallvec", 670 | "termion", 671 | ] 672 | 673 | [[package]] 674 | name = "redox-scheme" 675 | version = "0.6.2" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "2c00025a04f76fdcf72c15f10c7a12d9f2fdde93e539be9a57d5d632c4158a9e" 678 | dependencies = [ 679 | "libredox", 680 | "redox_syscall", 681 | ] 682 | 683 | [[package]] 684 | name = "redox-scheme" 685 | version = "0.8.2" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "d54adf6001069dfc77e8e9f62dc62d4c9c1471fff65ca577ca140f0e9bdf4e10" 688 | dependencies = [ 689 | "libredox", 690 | "redox_syscall", 691 | ] 692 | 693 | [[package]] 694 | name = "redox_event" 695 | version = "0.4.1" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "69609faa5d5992247a4ef379917bb3e39be281405d6a0ccd4f942429400b956f" 698 | dependencies = [ 699 | "bitflags 2.10.0", 700 | "libredox", 701 | ] 702 | 703 | [[package]] 704 | name = "redox_syscall" 705 | version = "0.5.18" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 708 | dependencies = [ 709 | "bitflags 2.10.0", 710 | ] 711 | 712 | [[package]] 713 | name = "resize" 714 | version = "0.3.1" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "b9e653e390eafbfebb2b3c5fcfbc90d801bc410d0de1f44f266ffbf2151d28aa" 717 | 718 | [[package]] 719 | name = "rusttype" 720 | version = "0.9.3" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" 723 | dependencies = [ 724 | "ab_glyph_rasterizer", 725 | "owned_ttf_parser", 726 | ] 727 | 728 | [[package]] 729 | name = "rustversion" 730 | version = "1.0.22" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 733 | 734 | [[package]] 735 | name = "scoped_threadpool" 736 | version = "0.1.9" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 739 | 740 | [[package]] 741 | name = "sdl2" 742 | version = "0.35.2" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" 745 | dependencies = [ 746 | "bitflags 1.3.2", 747 | "lazy_static", 748 | "libc", 749 | "sdl2-sys", 750 | ] 751 | 752 | [[package]] 753 | name = "sdl2-sys" 754 | version = "0.35.2" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" 757 | dependencies = [ 758 | "cfg-if", 759 | "cmake", 760 | "libc", 761 | "version-compare", 762 | ] 763 | 764 | [[package]] 765 | name = "serde" 766 | version = "1.0.228" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 769 | dependencies = [ 770 | "serde_core", 771 | ] 772 | 773 | [[package]] 774 | name = "serde_core" 775 | version = "1.0.228" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 778 | dependencies = [ 779 | "serde_derive", 780 | ] 781 | 782 | [[package]] 783 | name = "serde_derive" 784 | version = "1.0.228" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 787 | dependencies = [ 788 | "proc-macro2 1.0.103", 789 | "quote 1.0.42", 790 | "syn 2.0.111", 791 | ] 792 | 793 | [[package]] 794 | name = "serde_spanned" 795 | version = "0.6.9" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 798 | dependencies = [ 799 | "serde", 800 | ] 801 | 802 | [[package]] 803 | name = "servo-fontconfig" 804 | version = "0.5.1" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" 807 | dependencies = [ 808 | "libc", 809 | "servo-fontconfig-sys", 810 | ] 811 | 812 | [[package]] 813 | name = "servo-fontconfig-sys" 814 | version = "5.1.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" 817 | dependencies = [ 818 | "expat-sys", 819 | "freetype-sys", 820 | "pkg-config", 821 | ] 822 | 823 | [[package]] 824 | name = "shlex" 825 | version = "1.3.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 828 | 829 | [[package]] 830 | name = "smallvec" 831 | version = "1.15.1" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 834 | 835 | [[package]] 836 | name = "syn" 837 | version = "0.15.44" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 840 | dependencies = [ 841 | "proc-macro2 0.4.30", 842 | "quote 0.6.13", 843 | "unicode-xid", 844 | ] 845 | 846 | [[package]] 847 | name = "syn" 848 | version = "2.0.111" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 851 | dependencies = [ 852 | "proc-macro2 1.0.103", 853 | "quote 1.0.42", 854 | "unicode-ident", 855 | ] 856 | 857 | [[package]] 858 | name = "termion" 859 | version = "4.0.6" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" 862 | dependencies = [ 863 | "libc", 864 | "numtoa", 865 | ] 866 | 867 | [[package]] 868 | name = "thiserror" 869 | version = "2.0.17" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 872 | dependencies = [ 873 | "thiserror-impl", 874 | ] 875 | 876 | [[package]] 877 | name = "thiserror-impl" 878 | version = "2.0.17" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 881 | dependencies = [ 882 | "proc-macro2 1.0.103", 883 | "quote 1.0.42", 884 | "syn 2.0.111", 885 | ] 886 | 887 | [[package]] 888 | name = "tiff" 889 | version = "0.2.2" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "1e4834f28a0330cb9f3f2c87d2649dca723cb33802e2bdcf18da32759fbec7ce" 892 | dependencies = [ 893 | "byteorder", 894 | "lzw", 895 | "num-derive", 896 | "num-traits", 897 | ] 898 | 899 | [[package]] 900 | name = "toml" 901 | version = "0.7.8" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 904 | dependencies = [ 905 | "serde", 906 | "serde_spanned", 907 | "toml_datetime", 908 | "toml_edit", 909 | ] 910 | 911 | [[package]] 912 | name = "toml_datetime" 913 | version = "0.6.11" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 916 | dependencies = [ 917 | "serde", 918 | ] 919 | 920 | [[package]] 921 | name = "toml_edit" 922 | version = "0.19.15" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 925 | dependencies = [ 926 | "indexmap", 927 | "serde", 928 | "serde_spanned", 929 | "toml_datetime", 930 | "winnow", 931 | ] 932 | 933 | [[package]] 934 | name = "ttf-parser" 935 | version = "0.15.2" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" 938 | 939 | [[package]] 940 | name = "unicode-ident" 941 | version = "1.0.22" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 944 | 945 | [[package]] 946 | name = "unicode-xid" 947 | version = "0.1.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 950 | 951 | [[package]] 952 | name = "version-compare" 953 | version = "0.1.1" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 956 | 957 | [[package]] 958 | name = "wasm-bindgen" 959 | version = "0.2.106" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" 962 | dependencies = [ 963 | "cfg-if", 964 | "once_cell", 965 | "rustversion", 966 | "wasm-bindgen-macro", 967 | "wasm-bindgen-shared", 968 | ] 969 | 970 | [[package]] 971 | name = "wasm-bindgen-macro" 972 | version = "0.2.106" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" 975 | dependencies = [ 976 | "quote 1.0.42", 977 | "wasm-bindgen-macro-support", 978 | ] 979 | 980 | [[package]] 981 | name = "wasm-bindgen-macro-support" 982 | version = "0.2.106" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" 985 | dependencies = [ 986 | "bumpalo", 987 | "proc-macro2 1.0.103", 988 | "quote 1.0.42", 989 | "syn 2.0.111", 990 | "wasm-bindgen-shared", 991 | ] 992 | 993 | [[package]] 994 | name = "wasm-bindgen-shared" 995 | version = "0.2.106" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 998 | dependencies = [ 999 | "unicode-ident", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "winapi" 1004 | version = "0.3.9" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1007 | dependencies = [ 1008 | "winapi-i686-pc-windows-gnu", 1009 | "winapi-x86_64-pc-windows-gnu", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "winapi-i686-pc-windows-gnu" 1014 | version = "0.4.0" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1017 | 1018 | [[package]] 1019 | name = "winapi-x86_64-pc-windows-gnu" 1020 | version = "0.4.0" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1023 | 1024 | [[package]] 1025 | name = "windows-core" 1026 | version = "0.62.2" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 1029 | dependencies = [ 1030 | "windows-implement", 1031 | "windows-interface", 1032 | "windows-link", 1033 | "windows-result", 1034 | "windows-strings", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "windows-implement" 1039 | version = "0.60.2" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 1042 | dependencies = [ 1043 | "proc-macro2 1.0.103", 1044 | "quote 1.0.42", 1045 | "syn 2.0.111", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "windows-interface" 1050 | version = "0.59.3" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 1053 | dependencies = [ 1054 | "proc-macro2 1.0.103", 1055 | "quote 1.0.42", 1056 | "syn 2.0.111", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "windows-link" 1061 | version = "0.2.1" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 1064 | 1065 | [[package]] 1066 | name = "windows-result" 1067 | version = "0.4.1" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 1070 | dependencies = [ 1071 | "windows-link", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "windows-strings" 1076 | version = "0.5.1" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 1079 | dependencies = [ 1080 | "windows-link", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "winnow" 1085 | version = "0.5.40" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 1088 | dependencies = [ 1089 | "memchr", 1090 | ] 1091 | -------------------------------------------------------------------------------- /src/scheme.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | use std::sync::Arc; 3 | use std::{cmp, collections::BTreeMap, fs, io, str}; 4 | 5 | use log::{error, info, warn}; 6 | use orbclient::{ 7 | self, ButtonEvent, ClipboardEvent, Color, Event, EventOption, FocusEvent, HoverEvent, KeyEvent, 8 | MouseEvent, MouseRelativeEvent, MoveEvent, QuitEvent, Renderer, ResizeEvent, ScreenEvent, 9 | TextInputEvent, 10 | }; 11 | use redox_scheme::Response; 12 | use syscall::EVENT_READ; 13 | use syscall::error::{EBADF, Error, Result}; 14 | 15 | use crate::compositor::Compositor; 16 | use crate::config::Config; 17 | use crate::core::{Orbital, Properties, display::Display, image::Image, rect::Rect}; 18 | use crate::window::{self, Window}; 19 | use crate::window_order::{WindowOrder, WindowZOrder}; 20 | 21 | #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] 22 | enum CursorKind { 23 | None, 24 | LeftPtr, 25 | BottomLeftCorner, 26 | BottomRightCorner, 27 | BottomSide, 28 | LeftSide, 29 | RightSide, 30 | } 31 | 32 | enum DragMode { 33 | None, 34 | Title(usize, i32, i32), 35 | LeftBorder(usize, i32, i32), 36 | RightBorder(usize, i32), 37 | BottomBorder(usize, i32), 38 | BottomLeftBorder(usize, i32, i32, i32), 39 | BottomRightBorder(usize, i32, i32), 40 | } 41 | 42 | enum Volume { 43 | Down, 44 | Up, 45 | Toggle, 46 | } 47 | 48 | #[derive(Clone, Copy, Debug)] 49 | pub enum TilePosition { 50 | LeftHalf, 51 | TopHalf, 52 | RightHalf, 53 | BottomHalf, 54 | Maximized, 55 | FullScreen, 56 | } 57 | 58 | const GRID_SIZE: i32 = 16; 59 | 60 | const SHIFT_LEFT_MODIFIER: u8 = 1 << 0; 61 | const SHIFT_RIGHT_MODIFIER: u8 = 1 << 1; 62 | const SHIFT_ANY_MODIFIER: u8 = 1 << 2; 63 | const CONTROL_MODIFIER: u8 = 1 << 3; 64 | const ALT_MODIFIER: u8 = 1 << 4; 65 | const ALT_GR_MODIFIER: u8 = 1 << 5; 66 | const ALT_ANY_MODIFIER: u8 = 1 << 6; 67 | const SUPER_MODIFIER: u8 = 1 << 7; 68 | 69 | pub struct OrbitalScheme { 70 | compositor: Compositor, 71 | 72 | window_max: Image, 73 | window_max_unfocused: Image, 74 | window_close: Image, 75 | window_close_unfocused: Image, 76 | cursors: BTreeMap>, 77 | cursor_x: i32, 78 | cursor_y: i32, 79 | cursor_left: bool, 80 | cursor_middle: bool, 81 | cursor_right: bool, 82 | cursor_simulate_enabled: bool, 83 | cursor_simulate_speed: i32, 84 | dragging: DragMode, 85 | modifier_state: u8, 86 | volume_value: i32, 87 | volume_toggle: i32, 88 | next_id: isize, 89 | hover: Option, 90 | order: WindowOrder, 91 | windows: BTreeMap, 92 | font: orbfont::Font, 93 | clipboard: Vec, 94 | scale: i32, 95 | config: Rc, 96 | // Is the user currently switching windows with win-tab 97 | // Set true when win-tab is pressed, set false when win is released. 98 | // While it is true, redraw() calls draw_window_list() 99 | win_tabbing: bool, 100 | volume_osd: bool, 101 | shortcuts_osd: bool, 102 | last_popup_rect: Option, 103 | } 104 | 105 | impl OrbitalScheme { 106 | pub(crate) fn new(displays: Vec, config: Rc) -> Result { 107 | let mut scale = 1; 108 | for display in displays.iter() { 109 | scale = cmp::max(scale, display.scale); 110 | } 111 | 112 | let mut cursors = BTreeMap::new(); 113 | cursors.insert(CursorKind::None, Arc::new(Image::new(0, 0))); 114 | cursors.insert( 115 | CursorKind::LeftPtr, 116 | Arc::new(Image::from_path_scale(&config.cursor, scale).unwrap_or(Image::new(0, 0))), 117 | ); 118 | cursors.insert( 119 | CursorKind::BottomLeftCorner, 120 | Arc::new( 121 | Image::from_path_scale(&config.bottom_left_corner, scale) 122 | .unwrap_or(Image::new(0, 0)), 123 | ), 124 | ); 125 | cursors.insert( 126 | CursorKind::BottomRightCorner, 127 | Arc::new( 128 | Image::from_path_scale(&config.bottom_right_corner, scale) 129 | .unwrap_or(Image::new(0, 0)), 130 | ), 131 | ); 132 | cursors.insert( 133 | CursorKind::BottomSide, 134 | Arc::new( 135 | Image::from_path_scale(&config.bottom_side, scale).unwrap_or(Image::new(0, 0)), 136 | ), 137 | ); 138 | cursors.insert( 139 | CursorKind::LeftSide, 140 | Arc::new(Image::from_path_scale(&config.left_side, scale).unwrap_or(Image::new(0, 0))), 141 | ); 142 | cursors.insert( 143 | CursorKind::RightSide, 144 | Arc::new(Image::from_path_scale(&config.right_side, scale).unwrap_or(Image::new(0, 0))), 145 | ); 146 | 147 | let font = orbfont::Font::find(Some("Sans"), None, None)?; 148 | 149 | let mut orbital_scheme = OrbitalScheme { 150 | compositor: Compositor::new(displays), 151 | 152 | window_max: Image::from_path_scale(&config.window_max, scale) 153 | .unwrap_or(Image::new(0, 0)), 154 | window_max_unfocused: Image::from_path_scale(&config.window_max_unfocused, scale) 155 | .unwrap_or(Image::new(0, 0)), 156 | window_close: Image::from_path_scale(&config.window_close, scale) 157 | .unwrap_or(Image::new(0, 0)), 158 | window_close_unfocused: Image::from_path_scale(&config.window_close_unfocused, scale) 159 | .unwrap_or(Image::new(0, 0)), 160 | cursors, 161 | cursor_x: 0, 162 | cursor_y: 0, 163 | cursor_left: false, 164 | cursor_middle: false, 165 | cursor_right: false, 166 | cursor_simulate_speed: 32, 167 | cursor_simulate_enabled: false, 168 | dragging: DragMode::None, 169 | modifier_state: 0, 170 | volume_value: 0, 171 | volume_toggle: 0, 172 | next_id: 1, 173 | hover: None, 174 | order: WindowOrder::new(), 175 | windows: BTreeMap::new(), 176 | font, 177 | clipboard: Vec::new(), 178 | scale, 179 | config: Rc::clone(&config), 180 | win_tabbing: false, 181 | volume_osd: false, 182 | shortcuts_osd: false, 183 | last_popup_rect: None, 184 | }; 185 | 186 | orbital_scheme.update_cursor(0, 0, CursorKind::LeftPtr); 187 | 188 | Ok(orbital_scheme) 189 | } 190 | 191 | fn update_window( 192 | compositor: &mut Compositor, 193 | window: &mut Window, 194 | f: impl FnOnce(&Compositor, &mut Window), 195 | ) { 196 | compositor.schedule(window.title_rect()); 197 | compositor.schedule(window.rect()); 198 | 199 | f(compositor, window); 200 | 201 | compositor.schedule(window.title_rect()); 202 | compositor.schedule(window.rect()); 203 | } 204 | 205 | fn focus(&mut self, id: usize, focused: bool) { 206 | if let Some(window) = self.windows.get_mut(&id) { 207 | Self::update_window(&mut self.compositor, window, |_compositor, window| { 208 | window.event(FocusEvent { focused }.to_event()); 209 | }); 210 | } 211 | } 212 | 213 | //TODO: update cursor in more places to ensure consistency: 214 | // - Window resizes 215 | // - Window sets cursor on/off 216 | // - Window moves 217 | fn update_cursor(&mut self, x: i32, y: i32, kind: CursorKind) { 218 | self.cursor_x = x; 219 | self.cursor_y = y; 220 | 221 | let cursor = self.cursors.get(&kind).unwrap(); 222 | 223 | let w: i32 = cursor.width(); 224 | let h: i32 = cursor.height(); 225 | 226 | let (hot_x, hot_y) = match kind { 227 | CursorKind::None => (0, 0), 228 | CursorKind::LeftPtr => (0, 0), 229 | CursorKind::BottomLeftCorner => (0, h), 230 | CursorKind::BottomRightCorner => (w, h), 231 | CursorKind::BottomSide => (w / 2, h), 232 | CursorKind::LeftSide => (0, h / 2), 233 | CursorKind::RightSide => (w, h / 2), 234 | }; 235 | 236 | self.compositor 237 | .update_cursor(self.cursor_x, self.cursor_y, hot_x, hot_y, cursor); 238 | } 239 | } 240 | 241 | impl OrbitalScheme { 242 | /// Return true if a packet should be delayed until a display event 243 | pub fn should_delay(&mut self, id: usize) -> bool { 244 | self.windows 245 | .get(&id) 246 | .map(|window| !window.asynchronous) 247 | .unwrap_or(true) 248 | } 249 | 250 | /// Callback to handle events over the input handle 251 | pub fn handle_input(&mut self, events: &mut [Event]) { 252 | for &mut event in events { 253 | self.event(event); 254 | } 255 | } 256 | 257 | /// Called after a batch of any events have been handled 258 | pub fn handle_after(&mut self, orb: &mut Orbital) -> io::Result<()> { 259 | for (id, window) in self.windows.iter_mut() { 260 | if !window.events.is_empty() { 261 | if !window.notified_read || window.asynchronous { 262 | window.notified_read = true; 263 | orb.scheme_write(Response::post_fevent(*id, EVENT_READ.bits()))?; 264 | } 265 | } else { 266 | window.notified_read = false; 267 | } 268 | } 269 | 270 | self.redraw(); 271 | Ok(()) 272 | } 273 | 274 | /// Called when a new window is requested by the scheme. 275 | /// Return a window ID that will be used to identify it later. 276 | #[allow(clippy::too_many_arguments)] 277 | pub fn handle_window_new( 278 | &mut self, 279 | x: i32, 280 | y: i32, 281 | width: i32, 282 | height: i32, 283 | parts: &str, 284 | title: String, 285 | ) -> Result { 286 | self.window_new(x, y, width, height, parts, title) 287 | } 288 | 289 | /// Called when the scheme is read for events 290 | pub fn handle_window_read(&mut self, id: usize, buf: &mut [Event]) -> Result { 291 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 292 | Ok(window.read(buf)) 293 | } 294 | 295 | /// Called when the window asks to set async 296 | pub fn handle_window_async(&mut self, id: usize, is_async: bool) -> Result<()> { 297 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 298 | window.asynchronous = is_async; 299 | Ok(()) 300 | } 301 | 302 | /// Called when the window asks to be dragged 303 | pub fn handle_window_drag(&mut self, id: usize /*TODO: resize sides */) -> Result<()> { 304 | let _window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 305 | if self.cursor_left { 306 | self.dragging = DragMode::Title(id, self.cursor_x, self.cursor_y); 307 | } 308 | Ok(()) 309 | } 310 | 311 | /// Called when the window asks to set mouse cursor visibility 312 | pub fn handle_window_mouse_cursor(&mut self, id: usize, visible: bool) -> Result<()> { 313 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 314 | window.mouse_cursor = visible; 315 | Ok(()) 316 | } 317 | 318 | /// Called when the window asks to set mouse grabbing 319 | pub fn handle_window_mouse_grab(&mut self, id: usize, grab: bool) -> Result<()> { 320 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 321 | window.mouse_grab = grab; 322 | Ok(()) 323 | } 324 | 325 | /// Called when the window asks to set mouse relative mode 326 | pub fn handle_window_mouse_relative(&mut self, id: usize, relative: bool) -> Result<()> { 327 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 328 | window.mouse_relative = relative; 329 | Ok(()) 330 | } 331 | 332 | /// Called when the window asks to be repositioned 333 | pub fn handle_window_position( 334 | &mut self, 335 | id: usize, 336 | x: Option, 337 | y: Option, 338 | ) -> Result<()> { 339 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 340 | Self::update_window(&mut self.compositor, window, |_compositor, window| { 341 | window.x = x.unwrap_or(window.x); 342 | window.y = y.unwrap_or(window.y); 343 | }); 344 | 345 | Ok(()) 346 | } 347 | 348 | /// Called when the window asks to be resized 349 | pub fn handle_window_resize( 350 | &mut self, 351 | id: usize, 352 | w: Option, 353 | h: Option, 354 | ) -> Result<()> { 355 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 356 | Self::update_window(&mut self.compositor, window, |_compositor, window| { 357 | let w = w.unwrap_or(window.width()); 358 | let h = h.unwrap_or(window.height()); 359 | window.set_size(w, h); 360 | }); 361 | 362 | Ok(()) 363 | } 364 | 365 | /// Called when the window wants to set a flag 366 | pub fn handle_window_set_flag(&mut self, id: usize, flag: char, value: bool) -> Result<()> { 367 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 368 | 369 | // Handle maximized flag custom 370 | if flag == window::ORBITAL_FLAG_MAXIMIZED || flag == window::ORBITAL_FLAG_FULLSCREEN { 371 | let toggle_tile = if value { 372 | window.restore = None; 373 | true 374 | } else { 375 | window.restore.is_some() 376 | }; 377 | if toggle_tile { 378 | Self::tile_window( 379 | &mut self.compositor, 380 | &mut self.windows, 381 | id, 382 | if flag == window::ORBITAL_FLAG_FULLSCREEN { 383 | TilePosition::FullScreen 384 | } else { 385 | TilePosition::Maximized 386 | }, 387 | ); 388 | } 389 | } else { 390 | // Setting flag may change visibility, make sure to queue redraws both before and after 391 | Self::update_window(&mut self.compositor, window, |_compositor, window| { 392 | window.set_flag(flag, value); 393 | }); 394 | } 395 | 396 | Ok(()) 397 | } 398 | 399 | /// Called when the window asks to change title 400 | pub fn handle_window_title(&mut self, id: usize, title: String) -> Result<()> { 401 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 402 | window.title = title; 403 | window.render_title(&self.font); 404 | 405 | self.compositor.schedule(window.title_rect()); 406 | 407 | Ok(()) 408 | } 409 | 410 | /// Called by fevent to clear notified status, assuming you're sending edge-triggered notifications 411 | /// TODO: Abstract event system away completely. 412 | pub fn handle_window_clear_notified(&mut self, id: usize) -> Result<()> { 413 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 414 | window.notified_read = false; 415 | Ok(()) 416 | } 417 | 418 | /// Return a reference the window's image that will be mapped in the scheme's fmap function 419 | pub fn handle_window_map(&mut self, id: usize, create_new: bool) -> Result<&mut [Color]> { 420 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 421 | if create_new { 422 | window.maps += 1; 423 | } 424 | Ok(window.map()) 425 | } 426 | 427 | /// Free a reference to the window's image, for use by funmap 428 | pub fn handle_window_unmap(&mut self, id: usize) -> Result<()> { 429 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 430 | if window.maps > 0 { 431 | window.maps -= 1; 432 | } else { 433 | warn!("attempted unmap when there are no mappings"); 434 | } 435 | Ok(()) 436 | } 437 | 438 | /// Called to get window properties 439 | pub fn handle_window_properties(&mut self, id: usize) -> Result> { 440 | let window = self.windows.get(&id).ok_or(Error::new(EBADF))?; 441 | Ok(window.properties()) 442 | } 443 | 444 | /// Called to flush a window. It's usually a good idea to redraw here. 445 | pub fn handle_window_sync(&mut self, id: usize) -> Result<()> { 446 | let window = self.windows.get(&id).ok_or(Error::new(EBADF))?; 447 | self.compositor.schedule(window.rect()); 448 | Ok(()) 449 | } 450 | 451 | /// Called when a window should be closed 452 | pub fn handle_window_close(&mut self, id: usize) { 453 | // Unfocus current front window 454 | if let Some(id) = self.order.focused() { 455 | self.focus(id, false); 456 | } 457 | 458 | self.order.remove_window(id); 459 | 460 | if let Some(window) = self.windows.remove(&id) { 461 | self.compositor.schedule(window.title_rect()); 462 | self.compositor.schedule(window.rect()); 463 | } 464 | 465 | // Focus current front window 466 | if let Some(id) = self.order.focused() { 467 | self.focus(id, true); 468 | } 469 | 470 | // Ensure mouse cursor is correct 471 | let event = MouseEvent { 472 | x: self.cursor_x, 473 | y: self.cursor_y, 474 | }; 475 | self.mouse_event(event); 476 | } 477 | /// Create a clipboard from a window 478 | pub fn handle_clipboard_new(&mut self, id: usize) -> Result { 479 | //TODO: implement better clipboard mechanism 480 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 481 | window.clipboard_seek = 0; 482 | Ok(id) 483 | } 484 | 485 | /// Read window clipboard 486 | pub fn handle_clipboard_read(&mut self, id: usize, buf: &mut [u8]) -> Result { 487 | //TODO: implement better clipboard mechanism 488 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 489 | let mut i = 0; 490 | while i < buf.len() && window.clipboard_seek < self.clipboard.len() { 491 | buf[i] = self.clipboard[i]; 492 | i += 1; 493 | window.clipboard_seek += 1; 494 | } 495 | Ok(i) 496 | } 497 | 498 | /// Write window clipboard 499 | pub fn handle_clipboard_write(&mut self, id: usize, buf: &[u8]) -> Result { 500 | //TODO: implement better clipboard mechanism 501 | let window = self.windows.get_mut(&id).ok_or(Error::new(EBADF))?; 502 | let mut i = 0; 503 | self.clipboard.truncate(window.clipboard_seek); 504 | while i < buf.len() { 505 | self.clipboard.push(buf[i]); 506 | i += 1; 507 | window.clipboard_seek += 1; 508 | } 509 | Ok(i) 510 | } 511 | 512 | /// Close the window's clipboard access 513 | pub fn handle_clipboard_close(&mut self, _id: usize) { 514 | //TODO: implement better clipboard mechanism 515 | } 516 | 517 | fn redraw(&mut self) { 518 | self.order 519 | .rezbuffer(&|id| self.windows.get(&id).unwrap().zorder); 520 | 521 | let popup = if self.shortcuts_osd { 522 | Some(self.draw_shortcuts_osd()) 523 | } else if self.volume_osd { 524 | Some(self.draw_volume_osd()) 525 | } else if self.win_tabbing { 526 | self.draw_window_list_osd() 527 | } else { 528 | None 529 | }; 530 | if let Some(last_popup_rect) = self.last_popup_rect.take() { 531 | self.compositor.schedule(last_popup_rect); 532 | } 533 | let popup_rect = if let Some(popup) = &popup { 534 | let rect = Rect::new( 535 | self.compositor.screen_rect().width() / 2 - popup.width() / 2, 536 | self.compositor.screen_rect().height() / 2 - popup.height() / 2, 537 | popup.width(), 538 | popup.height(), 539 | ); 540 | self.last_popup_rect = Some(rect); 541 | self.compositor.schedule(rect); 542 | Some(rect) 543 | } else { 544 | None 545 | }; 546 | 547 | let mut total_redraw_opt: Option = None; 548 | 549 | self.compositor 550 | .redraw_windows(&mut total_redraw_opt, |display, rect| { 551 | display.rect(&rect, self.config.background_color.into()); 552 | 553 | for (id, focused) in self.order.iter_back_to_front() { 554 | if let Some(window) = self.windows.get(&id) { 555 | window.draw_title( 556 | display, 557 | &rect, 558 | focused, 559 | if focused { 560 | &self.window_max 561 | } else { 562 | &self.window_max_unfocused 563 | }, 564 | if focused { 565 | &self.window_close 566 | } else { 567 | &self.window_close_unfocused 568 | }, 569 | ); 570 | window.draw(display, &rect); 571 | } 572 | } 573 | 574 | if let Some(popup) = &popup { 575 | display 576 | .roi_mut(popup_rect.as_ref().unwrap()) 577 | .blend(&popup.roi(&Rect::new(0, 0, popup.width(), popup.height()))); 578 | } 579 | }); 580 | 581 | self.compositor.redraw_cursor(total_redraw_opt); 582 | 583 | // Sync any parts of displays that changed 584 | if let Some(total_redraw) = total_redraw_opt { 585 | self.compositor.sync_rect(total_redraw); 586 | } 587 | } 588 | 589 | fn volume(&mut self, volume: Volume) { 590 | let value = match fs::read_to_string("/scheme/audio/volume") { 591 | Ok(string) => match string.parse::() { 592 | Ok(value) => value, 593 | Err(err) => { 594 | error!("failed to parse volume '{}': {}", string, err); 595 | return; 596 | } 597 | }, 598 | Err(err) => { 599 | error!("failed to read volume: {}", err); 600 | return; 601 | } 602 | }; 603 | 604 | self.volume_value = match volume { 605 | Volume::Down => cmp::max(0, value - 5), 606 | Volume::Up => cmp::min(100, value + 5), 607 | Volume::Toggle => { 608 | if value == 0 { 609 | self.volume_toggle 610 | } else { 611 | self.volume_toggle = value; 612 | 0 613 | } 614 | } 615 | }; 616 | 617 | match fs::write("/scheme/audio/volume", format!("{}", self.volume_value)) { 618 | Ok(()) => (), 619 | Err(err) => { 620 | error!("failed to write volume: {}", err); 621 | return; 622 | } 623 | } 624 | 625 | self.volume_osd = true; 626 | } 627 | 628 | // Tab through the list of selectable windows, changing window order and focus to bring 629 | // the next one to the front and push the previous one to the back. 630 | // Note that the selectable windows maybe interlaced in the stack with non-selectable windows, 631 | // the first selectable window may not be the first in the stack and the bottom selectable 632 | // window may not be the last in the stack 633 | fn super_tab(&mut self) { 634 | // Enter win_tabbing mode 635 | self.win_tabbing = true; 636 | 637 | let mut selectable_windows: Vec = vec![]; 638 | for id in self.order.focus_order() { 639 | if let Some(window) = self.windows.get(&id) { 640 | if !window.title.is_empty() { 641 | selectable_windows.push(id); 642 | } 643 | } 644 | } 645 | 646 | if selectable_windows.len() > 1 { 647 | // Disable dragging 648 | self.dragging = DragMode::None; 649 | 650 | // remove focus from the first selectable window in the window stack and make it 651 | // the last selectable window in the stack. Indexes are the indexes of windows 652 | // in self.order 653 | if let Some(id) = self.order.focused() { 654 | self.focus(id, false); 655 | } 656 | 657 | self.order 658 | .move_focused_after(selectable_windows[selectable_windows.len() - 1]); 659 | 660 | if let Some(id) = self.order.focused() { 661 | self.focus(id, true); 662 | } 663 | } 664 | } 665 | 666 | // Called by redraw() to draw the list of currently open windows in the middle of the screen. 667 | // Filter out app windows with no title. 668 | // If there are no windows to select, nothing is drawn. 669 | fn draw_window_list_osd(&mut self) -> Option { 670 | const SELECT_POPUP_TOP_BOTTOM_MARGIN: u32 = 2; 671 | const SELECT_POPUP_SIDE_MARGIN: i32 = 4; 672 | const SELECT_ROW_HEIGHT: u32 = 20; 673 | const SELECT_ROW_WIDTH: i32 = 400; 674 | const FONT_HEIGHT: f32 = 16.0; 675 | 676 | //TODO: HiDPI 677 | 678 | let selectable_window_ids: Vec = self 679 | .order 680 | .focus_order() 681 | .filter(|id| { 682 | if let Some(window) = self.windows.get(id) { 683 | !window.title.is_empty() 684 | } else { 685 | false 686 | } 687 | }) 688 | .collect(); 689 | 690 | if selectable_window_ids.len() <= 1 { 691 | return None; 692 | } 693 | 694 | // follow the look of the current config - in terms of colors 695 | let Config { 696 | bar_color, 697 | bar_highlight_color, 698 | text_color, 699 | text_highlight_color, 700 | .. 701 | } = *self.config; 702 | 703 | let list_h = (selectable_window_ids.len() as u32 * SELECT_ROW_HEIGHT 704 | + (SELECT_POPUP_TOP_BOTTOM_MARGIN * 2)) as i32; 705 | let list_w = SELECT_ROW_WIDTH; 706 | let mut image = Image::from_color(list_w, list_h, bar_color.into()); 707 | 708 | for (selectable_index, window_id) in selectable_window_ids.iter().enumerate() { 709 | if let Some(window) = self.windows.get(window_id) { 710 | let vertical_offset = selectable_index as i32 * SELECT_ROW_HEIGHT as i32 711 | + SELECT_POPUP_TOP_BOTTOM_MARGIN as i32; 712 | let text = self.font.render(&window.title, FONT_HEIGHT); 713 | if selectable_index == 0 { 714 | image.rect( 715 | 0, 716 | vertical_offset, 717 | list_w as u32, 718 | SELECT_ROW_HEIGHT, 719 | bar_highlight_color.into(), 720 | ); 721 | text.draw( 722 | &mut image, 723 | SELECT_POPUP_SIDE_MARGIN, 724 | vertical_offset + SELECT_POPUP_TOP_BOTTOM_MARGIN as i32, 725 | text_highlight_color.into(), 726 | ); 727 | } else { 728 | text.draw( 729 | &mut image, 730 | SELECT_POPUP_SIDE_MARGIN, 731 | vertical_offset + SELECT_POPUP_TOP_BOTTOM_MARGIN as i32, 732 | text_color.into(), 733 | ); 734 | } 735 | } 736 | } 737 | 738 | Some(image) 739 | } 740 | 741 | // Draw an on screen display (overlay) for volume control 742 | fn draw_volume_osd(&mut self) -> Image { 743 | let Config { 744 | bar_color, 745 | bar_highlight_color, 746 | .. 747 | } = *self.config; 748 | 749 | const BAR_HEIGHT: i32 = 20; 750 | const BAR_WIDTH: i32 = 100; 751 | const POPUP_MARGIN: i32 = 2; 752 | 753 | //TODO: HiDPI 754 | let list_h = BAR_HEIGHT + (2 * POPUP_MARGIN); 755 | let list_w = BAR_WIDTH + (2 * POPUP_MARGIN); 756 | // Color copied over from orbtk's window background 757 | let mut image = Image::from_color(list_w, list_h, bar_color.into()); 758 | image.rect( 759 | POPUP_MARGIN, 760 | POPUP_MARGIN, 761 | self.volume_value as u32, 762 | BAR_HEIGHT as u32, 763 | bar_highlight_color.into(), 764 | ); 765 | 766 | image 767 | } 768 | 769 | const SHORTCUTS_LIST: &'static [&'static str] = &[ 770 | "Super-Q: Quit current window", 771 | "Super-TAB: Cycle through active windows bringing to the front of the stack", 772 | "Super-{: Volume down", 773 | "Super-}: Volume up", 774 | "Super-\\: Volume toggle (mute / unmute)", 775 | "Super-Shift-left: Tile window to left", 776 | "Super-Shift-right: Tile window to right", 777 | "Super-Shift-up: Tile window to top", 778 | "Super-Shift-down: Tile window to bottom", 779 | "Super-left_arrow: Move window left", 780 | "Super-right_arrow: Move window right", 781 | "Super-up_arrow: Move window up", 782 | "Super-down_arrow: Move window down", 783 | "Super-C: Copy to copy buffer", 784 | "Super-X: Cut to copy buffer", 785 | "Super-V: Paste from the copy buffer", 786 | "Super-M: Toggle window max (maximize or restore)", 787 | "Super-ENTER: Toggle window max (maximize or restore)", 788 | "Super-Numpad-0: Enable mouse accessibility keys using numpad", 789 | ]; 790 | 791 | // Draw an on screen display (overlay) of available SUPER keyboard shortcuts 792 | fn draw_shortcuts_osd(&mut self) -> Image { 793 | const ROW_HEIGHT: u32 = 20; 794 | const ROW_WIDTH: i32 = 400; 795 | const POPUP_BORDER: u32 = 2; 796 | const FONT_HEIGHT: f32 = 16.0; 797 | 798 | // follow the look of the current config - in terms of colors 799 | let Config { 800 | bar_color, 801 | bar_highlight_color, 802 | text_highlight_color, 803 | .. 804 | } = *self.config; 805 | 806 | let list_h = (Self::SHORTCUTS_LIST.len() as u32 * ROW_HEIGHT + (POPUP_BORDER * 2)) as i32; 807 | let list_w = ROW_WIDTH; 808 | let mut image = Image::from_color(list_w, list_h, bar_color.into()); 809 | 810 | for (index, shortcut) in Self::SHORTCUTS_LIST.iter().enumerate() { 811 | let vertical_offset = index as i32 * ROW_HEIGHT as i32 + POPUP_BORDER as i32; 812 | let text = self.font.render(shortcut, FONT_HEIGHT); 813 | image.rect( 814 | 0, 815 | vertical_offset, 816 | list_w as u32, 817 | ROW_HEIGHT, 818 | bar_highlight_color.into(), 819 | ); 820 | text.draw( 821 | &mut image, 822 | POPUP_BORDER as i32, 823 | vertical_offset + POPUP_BORDER as i32, 824 | text_highlight_color.into(), 825 | ); 826 | } 827 | 828 | image 829 | } 830 | 831 | // Keep track of the modifier keys state based on past keydown/keyup events 832 | fn track_modifier_state(&mut self, scancode: u8, pressed: bool) { 833 | match (scancode, pressed) { 834 | (orbclient::K_SUPER, true) => self.modifier_state |= SUPER_MODIFIER, 835 | (orbclient::K_SUPER, false) => self.modifier_state &= !SUPER_MODIFIER, 836 | (orbclient::K_LEFT_SHIFT, true) => self.modifier_state |= SHIFT_LEFT_MODIFIER, 837 | (orbclient::K_LEFT_SHIFT, false) => self.modifier_state &= !SHIFT_LEFT_MODIFIER, 838 | (orbclient::K_RIGHT_SHIFT, true) => self.modifier_state |= SHIFT_RIGHT_MODIFIER, 839 | (orbclient::K_RIGHT_SHIFT, false) => self.modifier_state &= !SHIFT_RIGHT_MODIFIER, 840 | (orbclient::K_CTRL, true) => self.modifier_state |= CONTROL_MODIFIER, 841 | (orbclient::K_CTRL, false) => self.modifier_state &= !CONTROL_MODIFIER, 842 | (orbclient::K_ALT, true) => self.modifier_state |= ALT_MODIFIER, 843 | (orbclient::K_ALT, false) => self.modifier_state &= !ALT_MODIFIER, 844 | (orbclient::K_ALT_GR, true) => self.modifier_state |= ALT_GR_MODIFIER, 845 | (orbclient::K_ALT_GR, false) => self.modifier_state &= !ALT_GR_MODIFIER, 846 | _ => {} 847 | } 848 | 849 | if self.modifier_state & SHIFT_LEFT_MODIFIER != 0 850 | || self.modifier_state & SHIFT_RIGHT_MODIFIER != 0 851 | { 852 | self.modifier_state |= SHIFT_ANY_MODIFIER; 853 | } else { 854 | self.modifier_state &= !SHIFT_ANY_MODIFIER; 855 | } 856 | 857 | if self.modifier_state & ALT_MODIFIER != 0 || self.modifier_state & ALT_GR_MODIFIER != 0 { 858 | self.modifier_state |= ALT_ANY_MODIFIER; 859 | } else { 860 | self.modifier_state &= !ALT_ANY_MODIFIER; 861 | } 862 | } 863 | 864 | // Move the front-most window horizontally and vertically by the number of pixels passed 865 | fn move_front_window(&mut self, h_movement: i32, v_movement: i32) { 866 | if let Some(id) = self.order.focused() { 867 | if let Some(window) = self.windows.get_mut(&id) { 868 | let display_width = self.compositor.screen_rect().width(); 869 | let display_height = self.compositor.screen_rect().height(); 870 | Self::update_window(&mut self.compositor, window, |_compositor, window| { 871 | // Align location to grid 872 | window.x -= window.x % GRID_SIZE; 873 | window.y -= window.y % GRID_SIZE; 874 | 875 | window.x += h_movement; 876 | window.y += v_movement; 877 | 878 | // Ensure window remains visible 879 | window.x = cmp::max( 880 | -window.width() + GRID_SIZE, 881 | cmp::min(display_width - GRID_SIZE, window.x), 882 | ); 883 | window.y = cmp::max( 884 | -window.height() + GRID_SIZE, 885 | cmp::min(display_height - GRID_SIZE, window.y), 886 | ); 887 | 888 | let move_event = MoveEvent { 889 | x: window.x, 890 | y: window.y, 891 | } 892 | .to_event(); 893 | window.event(move_event); 894 | }); 895 | } 896 | } 897 | } 898 | 899 | fn clipboard_event(&mut self, kind: u8) { 900 | if let Some(id) = self.order.focused() { 901 | if let Some(window) = self.windows.get_mut(&id) { 902 | //TODO: set window's clipboard to primary 903 | let clipboard_event = ClipboardEvent { kind, size: 0 }.to_event(); 904 | window.event(clipboard_event); 905 | } 906 | } 907 | } 908 | 909 | fn quit_front_window(&mut self) { 910 | if let Some(id) = self.order.focused() { 911 | if let Some(window) = self.windows.get_mut(&id) { 912 | window.event(QuitEvent.to_event()); 913 | } 914 | } 915 | } 916 | 917 | /// Tile the focused window to a defined position. 918 | fn tile_focused_window(&mut self, position: TilePosition) { 919 | if let Some(id) = self.order.focused() { 920 | Self::tile_window(&mut self.compositor, &mut self.windows, id, position); 921 | } 922 | } 923 | 924 | fn tile_window( 925 | compositor: &mut Compositor, 926 | windows: &mut BTreeMap, 927 | window_id: usize, 928 | position: TilePosition, 929 | ) { 930 | if let Some(window) = windows.get_mut(&window_id) { 931 | Self::update_window(compositor, window, |compositor, window| { 932 | let (x, y, width, height) = match window.restore.take() { 933 | None => { 934 | // we are about to maximize window, so store current size for restore later 935 | window.restore = Some((window.rect(), position)); 936 | 937 | let screen_rect = compositor.get_screen_rect_for_window(&window.rect()); 938 | let window_rect = if matches!(position, TilePosition::FullScreen) { 939 | screen_rect 940 | } else { 941 | compositor.get_window_rect_from_screen_rect(&screen_rect) 942 | }; 943 | let top = window_rect.top() + window.title_rect().height(); 944 | let left = window_rect.left(); 945 | let max_height = window_rect.height() - window.title_rect().height(); 946 | let max_width = window_rect.width(); 947 | let half_width = (max_width / 2) as u32; 948 | let half_height = (max_height / 2) as u32; 949 | 950 | match position { 951 | TilePosition::LeftHalf => (left, top, half_width, max_height as u32), 952 | TilePosition::RightHalf => { 953 | (left + half_width as i32, top, half_width, max_height as u32) 954 | } 955 | TilePosition::TopHalf => (left, top, max_width as u32, half_height), 956 | TilePosition::BottomHalf => ( 957 | left, 958 | top + half_height as i32, 959 | max_width as u32, 960 | half_height, 961 | ), 962 | TilePosition::Maximized | TilePosition::FullScreen => { 963 | (left, top, max_width as u32, max_height as u32) 964 | } 965 | } 966 | } 967 | Some((restore, _)) => ( 968 | restore.left(), 969 | restore.top(), 970 | restore.width() as u32, 971 | restore.height() as u32, 972 | ), 973 | }; 974 | 975 | // TODO understand why this is needed and why handle_window_position isn't enough 976 | window.x = x; 977 | window.y = y; 978 | window.event(MoveEvent { x, y }.to_event()); 979 | 980 | window.event(ResizeEvent { width, height }.to_event()); 981 | }); 982 | } 983 | } 984 | 985 | // undraw any overlay that was being displayed and exit the mode causing it to be displayed 986 | fn close_overlays(&mut self) { 987 | // disable drawing of the win-tab or volume popup or shortcuts overlay on redraw 988 | self.win_tabbing = false; 989 | self.volume_osd = false; 990 | self.shortcuts_osd = false; 991 | } 992 | 993 | // Process incoming key events 994 | fn key_event(&mut self, event: KeyEvent) { 995 | self.track_modifier_state(event.scancode, event.pressed); 996 | 997 | match (event.scancode, event.pressed) { 998 | (orbclient::K_SUPER, true) => self.shortcuts_osd = true, 999 | (orbclient::K_SUPER, false) => self.close_overlays(), 1000 | (orbclient::K_VOLUME_TOGGLE, true) => self.volume(Volume::Toggle), 1001 | (orbclient::K_VOLUME_DOWN, true) => self.volume(Volume::Down), 1002 | (orbclient::K_VOLUME_UP, true) => self.volume(Volume::Up), 1003 | ( 1004 | orbclient::K_VOLUME_TOGGLE | orbclient::K_VOLUME_DOWN | orbclient::K_VOLUME_UP, 1005 | false, 1006 | ) => self.volume_osd = false, 1007 | _ => {} 1008 | } 1009 | 1010 | // process SUPER- key combinations 1011 | if self.modifier_state & SUPER_MODIFIER == SUPER_MODIFIER 1012 | && event.pressed 1013 | && event.scancode != orbclient::K_SUPER 1014 | { 1015 | self.close_overlays(); 1016 | 1017 | let shift = self.modifier_state & SHIFT_ANY_MODIFIER != 0; 1018 | match event.scancode { 1019 | orbclient::K_Q => self.quit_front_window(), 1020 | orbclient::K_TAB => self.super_tab(), 1021 | orbclient::K_NUM_0 => self.cursor_simulate_enabled = !self.cursor_simulate_enabled, 1022 | orbclient::K_BRACE_OPEN => self.volume(Volume::Down), 1023 | orbclient::K_BRACE_CLOSE => self.volume(Volume::Up), 1024 | orbclient::K_BACKSLASH => self.volume(Volume::Toggle), 1025 | orbclient::K_M => self.tile_focused_window(TilePosition::Maximized), 1026 | orbclient::K_ENTER => self.tile_focused_window(TilePosition::Maximized), 1027 | orbclient::K_UP if shift => self.tile_focused_window(TilePosition::TopHalf), 1028 | orbclient::K_DOWN if shift => self.tile_focused_window(TilePosition::BottomHalf), 1029 | orbclient::K_LEFT if shift => self.tile_focused_window(TilePosition::LeftHalf), 1030 | orbclient::K_RIGHT if shift => self.tile_focused_window(TilePosition::RightHalf), 1031 | orbclient::K_UP => self.move_front_window(0, -GRID_SIZE), 1032 | orbclient::K_DOWN => self.move_front_window(0, GRID_SIZE), 1033 | orbclient::K_LEFT => self.move_front_window(-GRID_SIZE, 0), 1034 | orbclient::K_RIGHT => self.move_front_window(GRID_SIZE, 0), 1035 | orbclient::K_C => self.clipboard_event(orbclient::CLIPBOARD_COPY), 1036 | orbclient::K_X => self.clipboard_event(orbclient::CLIPBOARD_CUT), 1037 | orbclient::K_V => self.clipboard_event(orbclient::CLIPBOARD_PASTE), 1038 | _ => { 1039 | //TODO: remove hack for sending super events to lowest numbered window 1040 | // ADM is this related to Launcher or Background or something? 1041 | if let Some((id, window)) = self.windows.iter_mut().next() { 1042 | info!("sending super {:?} to {}, {}", event, id, window.title); 1043 | let mut super_event = event.to_event(); 1044 | super_event.code += 0x1000_0000; 1045 | window.event(super_event); 1046 | } 1047 | } 1048 | } 1049 | } 1050 | 1051 | if self.cursor_simulate_enabled && self.simulate_mouse_event(&event) { 1052 | return; 1053 | } 1054 | 1055 | // send non-Super key events to the front window 1056 | if self.modifier_state & SUPER_MODIFIER == 0 { 1057 | if let Some(id) = self.order.focused() { 1058 | if let Some(window) = self.windows.get_mut(&id) { 1059 | if event.pressed && event.character != '\0' { 1060 | let text_input_event = TextInputEvent { 1061 | character: event.character, 1062 | } 1063 | .to_event(); 1064 | window.event(text_input_event); 1065 | } 1066 | window.event(event.to_event()); 1067 | } 1068 | } 1069 | } 1070 | } 1071 | 1072 | fn simulate_mouse_event(&mut self, event: &KeyEvent) -> bool { 1073 | match (event.scancode, event.pressed) { 1074 | (orbclient::K_NUM_4, true) => self.mouse_event(MouseEvent { 1075 | x: self.cursor_x - self.cursor_simulate_speed, 1076 | y: self.cursor_y, 1077 | }), 1078 | (orbclient::K_NUM_2, true) => self.mouse_event(MouseEvent { 1079 | x: self.cursor_x, 1080 | y: self.cursor_y + self.cursor_simulate_speed, 1081 | }), 1082 | (orbclient::K_NUM_8, true) => self.mouse_event(MouseEvent { 1083 | x: self.cursor_x, 1084 | y: self.cursor_y - self.cursor_simulate_speed, 1085 | }), 1086 | (orbclient::K_NUM_6, true) => self.mouse_event(MouseEvent { 1087 | x: self.cursor_x + self.cursor_simulate_speed, 1088 | y: self.cursor_y, 1089 | }), 1090 | (orbclient::K_NUM_3, true) => { 1091 | if self.cursor_simulate_speed > 2 { 1092 | self.cursor_simulate_speed /= 2; 1093 | } 1094 | } 1095 | (orbclient::K_NUM_9, true) => { 1096 | if self.cursor_simulate_speed <= 128 { 1097 | self.cursor_simulate_speed *= 2; 1098 | } 1099 | } 1100 | (orbclient::K_NUM_5, _) => self.button_event(ButtonEvent { 1101 | left: event.pressed, 1102 | middle: false, 1103 | right: false, 1104 | }), 1105 | (orbclient::K_NUM_7, _) => self.button_event(ButtonEvent { 1106 | left: false, 1107 | middle: event.pressed, 1108 | right: false, 1109 | }), 1110 | (orbclient::K_NUM_1, _) => self.button_event(ButtonEvent { 1111 | left: false, 1112 | middle: false, 1113 | right: event.pressed, 1114 | }), 1115 | _ => return false, 1116 | } 1117 | true 1118 | } 1119 | 1120 | fn mouse_event(&mut self, event: MouseEvent) { 1121 | let mut new_cursor = CursorKind::LeftPtr; 1122 | let mut new_hover = None; 1123 | 1124 | // Check for focus switch, dragging, and forward mouse events to applications 1125 | match self.dragging { 1126 | DragMode::None => { 1127 | for id in self.order.iter_front_to_back() { 1128 | if let Some(window) = self.windows.get_mut(&id) { 1129 | if window.rect().contains(event.x, event.y) { 1130 | if !window.mouse_cursor { 1131 | new_cursor = CursorKind::None; 1132 | } 1133 | 1134 | new_hover = Some(id); 1135 | if new_hover != self.hover { 1136 | let hover_event = HoverEvent { entered: true }.to_event(); 1137 | window.event(hover_event); 1138 | } 1139 | 1140 | if self.modifier_state & SUPER_MODIFIER == 0 { 1141 | let mut window_event = event.to_event(); 1142 | window_event.a -= window.x as i64; 1143 | window_event.b -= window.y as i64; 1144 | window.event(window_event); 1145 | } 1146 | break; 1147 | } else if window.title_rect().contains(event.x, event.y) { 1148 | break; 1149 | } else if window.left_border_rect().contains(event.x, event.y) { 1150 | new_cursor = CursorKind::LeftSide; 1151 | break; 1152 | } else if window.right_border_rect().contains(event.x, event.y) { 1153 | new_cursor = CursorKind::RightSide; 1154 | break; 1155 | } else if window.bottom_border_rect().contains(event.x, event.y) { 1156 | new_cursor = CursorKind::BottomSide; 1157 | break; 1158 | } else if window.bottom_left_border_rect().contains(event.x, event.y) { 1159 | new_cursor = CursorKind::BottomLeftCorner; 1160 | break; 1161 | } else if window.bottom_right_border_rect().contains(event.x, event.y) { 1162 | new_cursor = CursorKind::BottomRightCorner; 1163 | break; 1164 | } 1165 | } 1166 | } 1167 | } 1168 | DragMode::Title(window_id, drag_x, drag_y) => { 1169 | if let Some(window) = self.windows.get_mut(&window_id) { 1170 | if drag_x != event.x || drag_y != event.y { 1171 | Self::update_window(&mut self.compositor, window, |_compositor, window| { 1172 | //TODO: Min and max 1173 | window.x += event.x - drag_x; 1174 | window.y += event.y - drag_y; 1175 | 1176 | let move_event = MoveEvent { 1177 | x: window.x, 1178 | y: window.y, 1179 | } 1180 | .to_event(); 1181 | window.event(move_event); 1182 | 1183 | self.dragging = DragMode::Title(window_id, event.x, event.y); 1184 | }); 1185 | } 1186 | } else { 1187 | self.dragging = DragMode::None; 1188 | } 1189 | } 1190 | DragMode::LeftBorder(window_id, off_x, right_x) => { 1191 | if let Some(window) = self.windows.get_mut(&window_id) { 1192 | new_cursor = CursorKind::LeftSide; 1193 | 1194 | let x = event.x - off_x; 1195 | let w = right_x - x; 1196 | 1197 | if w > 0 { 1198 | if x != window.x { 1199 | Self::update_window( 1200 | &mut self.compositor, 1201 | window, 1202 | |_compositor, window| { 1203 | window.x = x; 1204 | window.event(MoveEvent { x, y: window.y }.to_event()); 1205 | }, 1206 | ); 1207 | } 1208 | 1209 | if w != window.width() { 1210 | let resize_event = ResizeEvent { 1211 | width: w as u32, 1212 | height: window.height() as u32, 1213 | } 1214 | .to_event(); 1215 | window.event(resize_event); 1216 | } 1217 | } 1218 | } else { 1219 | self.dragging = DragMode::None; 1220 | } 1221 | } 1222 | DragMode::RightBorder(window_id, off_x) => { 1223 | if let Some(window) = self.windows.get_mut(&window_id) { 1224 | new_cursor = CursorKind::RightSide; 1225 | let w = event.x - off_x - window.x; 1226 | if w > 0 && w != window.width() { 1227 | let resize_event = ResizeEvent { 1228 | width: w as u32, 1229 | height: window.height() as u32, 1230 | } 1231 | .to_event(); 1232 | window.event(resize_event); 1233 | } 1234 | } else { 1235 | self.dragging = DragMode::None; 1236 | } 1237 | } 1238 | DragMode::BottomBorder(window_id, off_y) => { 1239 | if let Some(window) = self.windows.get_mut(&window_id) { 1240 | new_cursor = CursorKind::BottomSide; 1241 | let h = event.y - off_y - window.y; 1242 | if h > 0 && h != window.height() { 1243 | let resize_event = ResizeEvent { 1244 | width: window.width() as u32, 1245 | height: h as u32, 1246 | } 1247 | .to_event(); 1248 | window.event(resize_event); 1249 | } 1250 | } else { 1251 | self.dragging = DragMode::None; 1252 | } 1253 | } 1254 | DragMode::BottomLeftBorder(window_id, off_x, off_y, right_x) => { 1255 | if let Some(window) = self.windows.get_mut(&window_id) { 1256 | new_cursor = CursorKind::BottomLeftCorner; 1257 | 1258 | let x = event.x - off_x; 1259 | let h = event.y - off_y - window.y; 1260 | let w = right_x - x; 1261 | 1262 | if w > 0 && h > 0 { 1263 | if x != window.x { 1264 | Self::update_window( 1265 | &mut self.compositor, 1266 | window, 1267 | |_compositor, window| { 1268 | window.x = x; 1269 | window.event(MoveEvent { x, y: window.y }.to_event()); 1270 | }, 1271 | ); 1272 | } 1273 | 1274 | if w != window.width() || h != window.height() { 1275 | let resize_event = ResizeEvent { 1276 | width: w as u32, 1277 | height: h as u32, 1278 | } 1279 | .to_event(); 1280 | window.event(resize_event); 1281 | } 1282 | } 1283 | } else { 1284 | self.dragging = DragMode::None; 1285 | } 1286 | } 1287 | DragMode::BottomRightBorder(window_id, off_x, off_y) => { 1288 | if let Some(window) = self.windows.get_mut(&window_id) { 1289 | new_cursor = CursorKind::BottomRightCorner; 1290 | let w = event.x - off_x - window.x; 1291 | let h = event.y - off_y - window.y; 1292 | if w > 0 && h > 0 && (w != window.width() || h != window.height()) { 1293 | let resize_event = ResizeEvent { 1294 | width: w as u32, 1295 | height: h as u32, 1296 | } 1297 | .to_event(); 1298 | window.event(resize_event); 1299 | } 1300 | } else { 1301 | self.dragging = DragMode::None; 1302 | } 1303 | } 1304 | } 1305 | 1306 | if new_hover != self.hover { 1307 | if let Some(id) = self.hover { 1308 | if let Some(window) = self.windows.get_mut(&id) { 1309 | let hover_event = HoverEvent { entered: false }.to_event(); 1310 | window.event(hover_event); 1311 | } 1312 | } 1313 | 1314 | self.hover = new_hover; 1315 | } 1316 | 1317 | self.update_cursor(event.x, event.y, new_cursor); 1318 | } 1319 | 1320 | fn mouse_relative_event(&mut self, event: MouseRelativeEvent) { 1321 | let mut relative_cursor_opt = None; 1322 | if let Some(id) = self.order.focused() { 1323 | if let Some(window) = self.windows.get_mut(&id) { 1324 | //TODO: handle grab? 1325 | if window.mouse_relative { 1326 | // Send relative event 1327 | window.event(event.to_event()); 1328 | 1329 | // Update cursor to center of this window 1330 | relative_cursor_opt = Some(( 1331 | window.x + window.width() / 2, 1332 | window.y + window.height() / 2, 1333 | //TODO: allow cursors on relative windows? 1334 | CursorKind::None, 1335 | )); 1336 | } 1337 | } 1338 | } 1339 | 1340 | // Handle relative window cursor 1341 | if let Some((x, y, kind)) = relative_cursor_opt { 1342 | self.update_cursor(x, y, kind); 1343 | return; 1344 | } 1345 | 1346 | //TODO: more advanced logic for keeping mouse on screen. 1347 | // This logic assumes horizontal and touching, but not overlapping, screens 1348 | let mut max_x = 0; 1349 | let mut max_y = 0; 1350 | for display in self.compositor.displays() { 1351 | let rect = display.screen_rect(); 1352 | max_x = cmp::max(max_x, rect.right() - 1); 1353 | max_y = cmp::max(max_y, rect.bottom() - 1); 1354 | } 1355 | 1356 | let x = cmp::max(0, cmp::min(max_x, self.cursor_x + event.dx)); 1357 | let mut y = cmp::max(0, cmp::min(max_y, self.cursor_y + event.dy)); 1358 | for display in self.compositor.displays() { 1359 | let rect = display.screen_rect(); 1360 | if x >= rect.left() && x < rect.right() { 1361 | y = cmp::max(rect.top(), cmp::min(rect.bottom() - 1, y)); 1362 | } 1363 | } 1364 | 1365 | self.mouse_event(MouseEvent { x, y }); 1366 | } 1367 | 1368 | fn button_event(&mut self, event: ButtonEvent) { 1369 | // Check for focus switch, dragging, and forward mouse events to applications 1370 | match self.dragging { 1371 | DragMode::None => { 1372 | let mut focus = 0; 1373 | for id in self.order.iter_front_to_back() { 1374 | if let Some(window) = self.windows.get(&id) { 1375 | if window.rect().contains(self.cursor_x, self.cursor_y) { 1376 | if self.modifier_state & SUPER_MODIFIER == SUPER_MODIFIER { 1377 | if event.left && !self.cursor_left { 1378 | focus = id; 1379 | self.dragging = 1380 | DragMode::Title(id, self.cursor_x, self.cursor_y); 1381 | } 1382 | } else if let Some(window) = self.windows.get_mut(&id) { 1383 | window.event(event.to_event()); 1384 | if event.left && !self.cursor_left 1385 | || event.middle && !self.cursor_middle 1386 | || event.right && !self.cursor_right 1387 | { 1388 | focus = id; 1389 | } 1390 | } 1391 | break; 1392 | } else if window.title_rect().contains(self.cursor_x, self.cursor_y) { 1393 | //TODO: Trigger max and exit on release 1394 | if event.left && !self.cursor_left { 1395 | focus = id; 1396 | if (window.max_contains(self.cursor_x, self.cursor_y)) 1397 | && (window.resizable) 1398 | { 1399 | Self::tile_window( 1400 | &mut self.compositor, 1401 | &mut self.windows, 1402 | id, 1403 | TilePosition::Maximized, 1404 | ); 1405 | } else if (window.close_contains(self.cursor_x, self.cursor_y)) 1406 | && (!window.unclosable) 1407 | { 1408 | if let Some(window) = self.windows.get_mut(&id) { 1409 | window.event(QuitEvent.to_event()); 1410 | } 1411 | } else { 1412 | self.dragging = 1413 | DragMode::Title(id, self.cursor_x, self.cursor_y); 1414 | } 1415 | } 1416 | break; 1417 | } else if window 1418 | .left_border_rect() 1419 | .contains(self.cursor_x, self.cursor_y) 1420 | { 1421 | if event.left && !self.cursor_left { 1422 | focus = id; 1423 | self.dragging = DragMode::LeftBorder( 1424 | id, 1425 | self.cursor_x - window.x, 1426 | window.x + window.width(), 1427 | ); 1428 | } 1429 | break; 1430 | } else if window 1431 | .right_border_rect() 1432 | .contains(self.cursor_x, self.cursor_y) 1433 | { 1434 | if event.left && !self.cursor_left { 1435 | focus = id; 1436 | self.dragging = DragMode::RightBorder( 1437 | id, 1438 | self.cursor_x - (window.x + window.width()), 1439 | ); 1440 | } 1441 | break; 1442 | } else if window 1443 | .bottom_border_rect() 1444 | .contains(self.cursor_x, self.cursor_y) 1445 | { 1446 | if event.left && !self.cursor_left { 1447 | focus = id; 1448 | self.dragging = DragMode::BottomBorder( 1449 | id, 1450 | self.cursor_y - (window.y + window.height()), 1451 | ); 1452 | } 1453 | break; 1454 | } else if window 1455 | .bottom_left_border_rect() 1456 | .contains(self.cursor_x, self.cursor_y) 1457 | { 1458 | if event.left && !self.cursor_left { 1459 | focus = id; 1460 | self.dragging = DragMode::BottomLeftBorder( 1461 | id, 1462 | self.cursor_x - window.x, 1463 | self.cursor_y - (window.y + window.height()), 1464 | window.x + window.width(), 1465 | ); 1466 | } 1467 | break; 1468 | } else if window 1469 | .bottom_right_border_rect() 1470 | .contains(self.cursor_x, self.cursor_y) 1471 | { 1472 | if event.left && !self.cursor_left { 1473 | focus = id; 1474 | self.dragging = DragMode::BottomRightBorder( 1475 | id, 1476 | self.cursor_x - (window.x + window.width()), 1477 | self.cursor_y - (window.y + window.height()), 1478 | ); 1479 | } 1480 | break; 1481 | } 1482 | } 1483 | } 1484 | 1485 | if focus > 0 { 1486 | // Redraw old focused window 1487 | if let Some(id) = self.order.focused() { 1488 | self.focus(id, false); 1489 | } 1490 | 1491 | // Reorder windows 1492 | if self.windows.get(&focus).unwrap().zorder != WindowZOrder::Back { 1493 | // Transfer focus if a front or normal window 1494 | self.order.make_focused(focus); 1495 | } 1496 | 1497 | // Redraw new focused window 1498 | if let Some(id) = self.order.focused() { 1499 | self.focus(id, true); 1500 | } 1501 | } 1502 | } 1503 | _ => { 1504 | if !event.left { 1505 | self.dragging = DragMode::None; 1506 | } 1507 | } 1508 | } 1509 | 1510 | self.cursor_left = event.left; 1511 | self.cursor_middle = event.middle; 1512 | self.cursor_right = event.right; 1513 | } 1514 | 1515 | fn resize_event(&mut self, event: ResizeEvent) { 1516 | self.compositor 1517 | .resize(event.width as i32, event.height as i32); 1518 | 1519 | let screen_event = ScreenEvent { 1520 | width: self.compositor.screen_rect().width() as u32, 1521 | height: self.compositor.screen_rect().height() as u32, 1522 | } 1523 | .to_event(); 1524 | for (_window_id, window) in self.windows.iter_mut() { 1525 | window.event(screen_event); 1526 | } 1527 | } 1528 | 1529 | fn event(&mut self, event_union: Event) { 1530 | self.order 1531 | .rezbuffer(&|id| self.windows.get(&id).unwrap().zorder); 1532 | 1533 | match event_union.to_option() { 1534 | EventOption::Key(event) => self.key_event(event), 1535 | EventOption::Mouse(MouseEvent { x, y }) => { 1536 | // ps2d gives us absolute mouse events with x and y in the range 0..65535. 1537 | // We need to translate this back to screen coordinates. We are using the 1538 | // size of the first display here as the only multi-display system supported 1539 | // by qemu doesn't produce absolute mouse events using vmmouse at all. 1540 | // FIXME once we have usb tablet support add a new event like MouseEvent 1541 | // which indicates the input device from which the event originated to use 1542 | // the correct display for getting the size. 1543 | self.mouse_event(MouseEvent { 1544 | x: x * self.compositor.screen_rect().width() / 65536, 1545 | y: y * self.compositor.screen_rect().height() / 65536, 1546 | }); 1547 | } 1548 | EventOption::MouseRelative(event) => self.mouse_relative_event(event), 1549 | EventOption::Button(event) => self.button_event(event), 1550 | EventOption::Scroll(_) => { 1551 | if let Some(id) = self.order.iter_front_to_back().next() { 1552 | if let Some(window) = self.windows.get_mut(&id) { 1553 | window.event(event_union); 1554 | } 1555 | } 1556 | } 1557 | EventOption::Resize(event) => self.resize_event(event), 1558 | event => error!("unexpected event: {:?}", event), 1559 | } 1560 | } 1561 | 1562 | fn window_new( 1563 | &mut self, 1564 | x: i32, 1565 | y: i32, 1566 | mut width: i32, 1567 | mut height: i32, 1568 | flags: &str, 1569 | title: String, 1570 | ) -> Result { 1571 | let id = self.next_id as usize; 1572 | self.next_id += 1; 1573 | if self.next_id < 0 { 1574 | //TODO: should this be an error? 1575 | self.next_id = 1; 1576 | } 1577 | 1578 | // Unfocus previous top window 1579 | if let Some(id) = self.order.focused() { 1580 | self.focus(id, false); 1581 | } 1582 | 1583 | // Resize to fit allowed screen area 1584 | let screen_rect = self.compositor.screen_rect(); 1585 | let allow_rect = self 1586 | .compositor 1587 | .get_window_rect_from_screen_rect(&screen_rect); 1588 | if flags.contains(window::ORBITAL_FLAG_RESIZABLE) { 1589 | width = width.min(allow_rect.width()); 1590 | height = height.min(allow_rect.height()); 1591 | } 1592 | 1593 | let mut window = Window::new(x, y, width, height, self.scale, Rc::clone(&self.config)); 1594 | 1595 | for flag in flags.chars() { 1596 | window.set_flag(flag, true); 1597 | } 1598 | 1599 | window.title = title; 1600 | window.render_title(&self.font); 1601 | 1602 | // Automatic placement 1603 | if x < 0 && y < 0 { 1604 | // Center by default in allowed area 1605 | let center_x = cmp::max(0, (allow_rect.width() - width) / 2); 1606 | let center_y = cmp::max( 1607 | window.title_rect().height(), 1608 | (allow_rect.height() - height) / 2, 1609 | ); 1610 | window.x = center_x; 1611 | window.y = center_y; 1612 | 1613 | // Process overlaps 1614 | let mut overlap = true; 1615 | let mut attempts = 0; 1616 | while overlap { 1617 | overlap = false; 1618 | let cascade_rect = window.cascade_rect(); 1619 | for other_id in self.order.focus_order() { 1620 | let Some(other) = self.windows.get(&other_id) else { 1621 | continue; 1622 | }; 1623 | 1624 | // Ignore windows not shown on the same level 1625 | if other.hidden || other.zorder != window.zorder { 1626 | continue; 1627 | } 1628 | 1629 | // Ignore windows not colliding in cascade region 1630 | if cascade_rect.intersection(&other.cascade_rect()).is_empty() { 1631 | continue; 1632 | } 1633 | 1634 | // Adjust position by cascading region size 1635 | overlap = true; 1636 | window.x += cascade_rect.width(); 1637 | window.y += cascade_rect.height(); 1638 | 1639 | // Reset X or Y if beyond the screen size 1640 | if window.x + window.width() > screen_rect.width() { 1641 | window.x = 0; 1642 | } 1643 | if window.y + window.height() > screen_rect.height() { 1644 | window.y = window.title_rect().height(); 1645 | } 1646 | 1647 | // Give up if we ran out of places to try 1648 | attempts += 1; 1649 | if attempts > 1000 { 1650 | window.x = center_x; 1651 | window.y = center_y; 1652 | overlap = false; 1653 | } 1654 | break; 1655 | } 1656 | } 1657 | } 1658 | 1659 | // Redraw new window 1660 | self.compositor.schedule(window.title_rect()); 1661 | self.compositor.schedule(window.rect()); 1662 | 1663 | // Add to zorder as appropriate 1664 | self.order.add_window(id, window.zorder); 1665 | 1666 | self.windows.insert(id, window); 1667 | 1668 | // Focus new top window 1669 | if let Some(id) = self.order.focused() { 1670 | self.focus(id, true); 1671 | } 1672 | 1673 | // Ensure mouse cursor is correct 1674 | let event = MouseEvent { 1675 | x: self.cursor_x, 1676 | y: self.cursor_y, 1677 | }; 1678 | self.mouse_event(event); 1679 | 1680 | Ok(id) 1681 | } 1682 | } 1683 | --------------------------------------------------------------------------------