wzrd

An ICCCM & EWMH compliant X11 reparenting, dynamic window manager, written in Rust
git clone git://git.deurzen.net/wzrd
Log | Files | Refs | LICENSE

commit 33718f2901632bc44bdf650ebfe45349476f7159
Author: deurzen <m.deurzen@tum.de>
Date:   Sat, 13 Feb 2021 01:31:35 +0100

initial commit

Diffstat:
A.gitignore | 3+++
ACargo.lock | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 47+++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE | 27+++++++++++++++++++++++++++
AMakefile | 24++++++++++++++++++++++++
Agit-hooks/pre-commit | 16++++++++++++++++
Alaunch | 4++++
Arustfmt.toml | 8++++++++
Asrc/bar/main.rs | 1+
Asrc/client/main.rs | 1+
Asrc/core/binding.rs | 16++++++++++++++++
Asrc/core/client.rs | 744+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/common.rs | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/config.rs | 1+
Asrc/core/consume.rs | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/context.rs | 1+
Asrc/core/contrib/actions.rs | 1+
Asrc/core/contrib/extensions/ipc.rs | 1+
Asrc/core/contrib/extensions/mod.rs | 1+
Asrc/core/contrib/extensions/scratchpad.rs | 1+
Asrc/core/contrib/hooks.rs | 1+
Asrc/core/contrib/layouts.rs | 1+
Asrc/core/contrib/mod.rs | 1+
Asrc/core/cycle.rs | 1140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/helpers.rs | 1+
Asrc/core/jump.rs | 24++++++++++++++++++++++++
Asrc/core/layout.rs | 1215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/macros.rs | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/main.rs | 531+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/model.rs | 3401+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/partition.rs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/profile.rs | 1+
Asrc/core/rule.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/stack.rs | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/util.rs | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/workspace.rs | 588+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main.rs | 1+
Asrc/winsys/common.rs | 1082+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/connection.rs | 350+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/event.rs | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/input.rs | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/mod.rs | 12++++++++++++
Asrc/winsys/screen.rs | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/xdata/event.rs | 12++++++++++++
Asrc/winsys/xdata/input.rs | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/xdata/mod.rs | 3+++
Asrc/winsys/xdata/xconnection.rs | 2980+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axinitrc | 5+++++
48 files changed, 13989 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +/target +/tags +/TODO diff --git a/Cargo.lock b/Cargo.lock @@ -0,0 +1,273 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "gethostname" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "nix" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85db2feff6bf70ebc3a4793191517d5f0331100a2f10f9bf93b5e5214f32b7b7" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "simplelog" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2736f58087298a448859961d3f4a0850b832e72619d75adc69da7993c2cd3c" +dependencies = [ + "chrono", + "log", + "termcolor", +] + +[[package]] +name = "strum" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89a286a7e3b5720b9a477b23253bc50debac207c8d21505f8e70b36792f11b5" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e61bb0be289045cb80bfce000512e32d09f8337e54c186725da381377ad1f8d5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wzrd" +version = "0.1.0" +dependencies = [ + "anyhow", + "log", + "nix", + "simplelog", + "strum", + "strum_macros", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4122702a684f01cf35cf8e00d0ba513f7c4c7d46f9881d9a699f3de787e61e4" +dependencies = [ + "gethostname", + "nix", + "winapi", + "winapi-wsapoll", +] diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "wzrd" +version = "0.1.0" +authors = ["deurzen <m.deurzen@tum.de>"] +edition = "2018" +license = "BSD3" +repository = "https://github.com/deurzen/wzrd" +documentation = "https://docs.rs/wzrd" +readme = "README.md" +default-run = "wzrd" +description = """ +An ICCCM & EWMH compliant X11 reparenting, tiling window manager, written in Rust +""" + +[profile.release] +lto = true + +[lib] +name = "winsys" +path = "src/winsys/mod.rs" + +[[bin]] +name = "wzrd" +path = "src/core/main.rs" + +[[bin]] +name = "wzrdbar" +path = "src/bar/main.rs" +required-features = ["bar"] + +[[bin]] +name = "wzrdclient" +path = "src/client/main.rs" +required-features = ["client"] + +[features] +bar = [] +client = [] + +[dependencies] +x11rb = { version = "0.8.0", features = ["cursor", "xinerama", "randr", "res"] } +anyhow = "1.0.33" +log = "0.4" +simplelog = "0.8.0" +nix = "0.19.0" +strum = { version = "0.19", features = ["derive"] } +strum_macros = "0.19" diff --git a/LICENSE b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2020, Max van Deurzen <max@vandeurzen.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile @@ -0,0 +1,24 @@ +all: build + +build: tags + cargo build + +test: + cargo test + +debug: build + ./launch + +release: + cargo build --release + +install: + install ./target/release/wzrd /usr/local/bin/wzrd + +.PHONY: tags +tags: + ctags -R --exclude=.git --exclude=target --fields=+iaS --extras=+q . + +.PHONY: format +format: + @cargo +nightly fmt diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit @@ -0,0 +1,16 @@ +#!/bin/sh + +remote="$1" +url="$2" + +for file in $(git diff --name-only); do + if [[ "$file" == *.rs ]]; then + if ! rustfmt +nightly --unstable-features --quiet --check --error-on-unformatted $file 2>&1 >/dev/null; then + echo -e "${RED}Format check failed${NC}, cannot continue." + echo -e "Please format using \`make format\`." + exit 1 + fi + fi +done + +exit 0 diff --git a/launch b/launch @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +XEPHYR=$(which Xephyr) +xinit ./xinitrc -- "$XEPHYR" :100 -ac -screen 800x600 -host-cursor diff --git a/rustfmt.toml b/rustfmt.toml @@ -0,0 +1,8 @@ +max_width = 80 +fn_args_layout = "vertical" +merge_derives = false +use_field_init_shorthand = true +condense_wildcard_suffixes = true +match_block_trailing_comma = true +overflow_delimited_expr = true +struct_lit_single_line = false diff --git a/src/bar/main.rs b/src/bar/main.rs @@ -0,0 +1 @@ +pub fn main() {} diff --git a/src/client/main.rs b/src/client/main.rs @@ -0,0 +1 @@ +pub fn main() {} diff --git a/src/core/binding.rs b/src/core/binding.rs @@ -0,0 +1,16 @@ +use crate::model::Model; + +use winsys::common::Window; +use winsys::input::KeyCode; +use winsys::input::MouseEvent; +use winsys::input::MouseEventKey; +use winsys::input::MouseShortcut; + +use std::collections::HashMap; + +pub type Action = Box<dyn FnMut(&mut Model)>; +pub type MouseEvents = Box<dyn FnMut(&mut Model, &MouseEvent, Option<Window>)>; +pub type KeyEvents = Box<dyn FnMut(&mut Model)>; +pub type KeyBindings = HashMap<KeyCode, KeyEvents>; +pub type MouseBindings = + HashMap<(MouseEventKey, MouseShortcut), (MouseEvents, bool)>; diff --git a/src/core/client.rs b/src/core/client.rs @@ -0,0 +1,744 @@ +use crate::common::Ident; +use crate::common::Identify; +use crate::common::NO_EXTENTS; +use winsys::common::Extents; +use winsys::common::Hex32; +use winsys::common::Pid; +use winsys::common::Pos; +use winsys::common::Region; +use winsys::common::SizeHints; +use winsys::common::Window; +use winsys::common::WindowType; + +use std::time::SystemTime; + +pub struct Client { + window: Window, + frame: Window, + name: String, + class: String, + instance: String, + context: usize, + workspace: usize, + window_type: WindowType, + active_region: Region, + previous_region: Region, + inner_region: Region, + free_region: Region, + tile_region: Region, + tree_region: Region, + frame_extents: Option<Extents>, + size_hints: Option<SizeHints>, + warp_pos: Option<Pos>, + parent: Option<Window>, + children: Vec<Window>, + leader: Option<Window>, + producer: Option<Window>, + consumers: Vec<Window>, + focused: bool, + mapped: bool, + managed: bool, + in_window: bool, + floating: bool, + fullscreen: bool, + iconified: bool, + disowned: bool, + sticky: bool, + invincible: bool, + urgent: bool, + consuming: bool, + producing: bool, + pid: Option<Pid>, + ppid: Option<Pid>, + last_focused: SystemTime, + managed_since: SystemTime, + expected_unmap_count: u8, +} + +impl Identify for Client { + fn id(&self) -> Ident { + self.window as Ident + } +} + +impl Client { + pub fn new( + window: Window, + frame: Window, + name: String, + class: String, + instance: String, + window_type: WindowType, + pid: Option<Pid>, + ppid: Option<Pid>, + ) -> Self { + Self { + window, + frame, + name, + class, + instance, + context: 0, + workspace: 0, + window_type, + active_region: Region::default(), + previous_region: Region::default(), + inner_region: Region::default(), + free_region: Region::default(), + tile_region: Region::default(), + tree_region: Region::default(), + frame_extents: None, + size_hints: None, + warp_pos: None, + parent: None, + children: Vec::new(), + leader: None, + producer: None, + consumers: Vec::new(), + focused: false, + mapped: false, + managed: true, + in_window: false, + floating: false, + fullscreen: false, + iconified: false, + disowned: false, + sticky: false, + invincible: false, + urgent: false, + consuming: false, + producing: true, + pid, + ppid, + last_focused: SystemTime::now(), + managed_since: SystemTime::now(), + expected_unmap_count: 0, + } + } + + #[inline] + pub fn windows(&self) -> (Window, Window) { + (self.window, self.frame) + } + + #[inline] + pub fn window(&self) -> Window { + self.window + } + + #[inline] + pub fn frame(&self) -> Window { + self.frame + } + + #[inline] + pub fn name(&self) -> &str { + &self.name + } + + #[inline] + pub fn set_name( + &mut self, + name: impl Into<String>, + ) { + self.name = name.into(); + } + + #[inline] + pub fn class(&self) -> &str { + &self.class + } + + #[inline] + pub fn set_class( + &mut self, + class: impl Into<String>, + ) { + self.class = class.into(); + } + + #[inline] + pub fn instance(&self) -> &str { + &self.instance + } + + #[inline] + pub fn set_instance( + &mut self, + instance: impl Into<String>, + ) { + self.instance = instance.into(); + } + + #[inline] + pub fn context(&self) -> usize { + self.context + } + + #[inline] + pub fn set_context( + &mut self, + context: usize, + ) { + self.context = context; + } + + #[inline] + pub fn workspace(&self) -> usize { + self.workspace + } + + #[inline] + pub fn set_workspace( + &mut self, + workspace: usize, + ) { + self.workspace = workspace; + } + + #[inline] + pub fn window_type(&self) -> WindowType { + self.window_type + } + + #[inline] + pub fn active_region(&self) -> &Region { + &self.active_region + } + + #[inline] + fn set_active_region( + &mut self, + active_region: &Region, + ) { + self.previous_region = self.active_region; + self.active_region = *active_region; + self.set_inner_region(active_region); + } + + #[inline] + pub fn previous_region(&self) -> &Region { + &self.previous_region + } + + #[inline] + pub fn inner_region(&self) -> &Region { + &self.inner_region + } + + #[inline] + fn set_inner_region( + &mut self, + active_region: &Region, + ) { + self.inner_region = if let Some(frame_extents) = self.frame_extents { + let mut inner_region = *active_region - frame_extents; + + inner_region.pos.x = frame_extents.left as i32; + inner_region.pos.y = frame_extents.top as i32; + + inner_region.dim.w = + active_region.dim.w - frame_extents.left - frame_extents.right; + inner_region.dim.h = + active_region.dim.h - frame_extents.top - frame_extents.bottom; + + inner_region + } else { + let mut inner_region = *active_region; + + inner_region.pos.x = 0; + inner_region.pos.y = 0; + + inner_region + }; + } + + #[inline] + pub fn free_region(&self) -> &Region { + &self.free_region + } + + #[inline] + pub fn set_free_region( + &mut self, + free_region: &Region, + ) { + if let Some(warp_pos) = self.warp_pos { + if !free_region.encompasses(self.active_region.pos + warp_pos) { + self.unset_warp_pos(); + } + } + + self.free_region = *free_region; + self.set_active_region(free_region); + } + + #[inline] + pub fn tile_region(&self) -> &Region { + &self.tile_region + } + + #[inline] + pub fn set_tile_region( + &mut self, + tile_region: &Region, + ) { + if let Some(warp_pos) = self.warp_pos { + if !tile_region.encompasses(self.active_region.pos + warp_pos) { + self.unset_warp_pos(); + } + } + + self.tile_region = *tile_region; + self.set_active_region(tile_region); + } + + #[inline] + pub fn tree_region(&self) -> &Region { + &self.tree_region + } + + #[inline] + pub fn set_tree_region( + &mut self, + tree_region: &Region, + ) { + if let Some(warp_pos) = self.warp_pos { + if !tree_region.encompasses(self.active_region.pos + warp_pos) { + self.unset_warp_pos(); + } + } + + self.tree_region = *tree_region; + self.set_active_region(tree_region); + } + + #[inline] + pub fn frame_extents_unchecked(&self) -> Extents { + if let Some(frame_extents) = self.frame_extents { + frame_extents + } else { + NO_EXTENTS + } + } + + #[inline] + pub fn frame_extents(&self) -> &Option<Extents> { + &self.frame_extents + } + + #[inline] + pub fn set_frame_extents( + &mut self, + frame_extents: Option<Extents>, + ) { + self.frame_extents = frame_extents; + } + + #[inline] + pub fn size_hints(&self) -> &Option<SizeHints> { + &self.size_hints + } + + #[inline] + pub fn set_size_hints( + &mut self, + size_hints: Option<SizeHints>, + ) { + self.size_hints = size_hints; + } + + #[inline] + pub fn warp_pos(&self) -> &Option<Pos> { + &self.warp_pos + } + + #[inline] + pub fn set_warp_pos( + &mut self, + pointer_pos: Pos, + ) { + let pointer_rpos = pointer_pos.relative_to(self.active_region.pos); + + self.warp_pos = if self.active_region.encompasses(pointer_pos) { + Some(pointer_rpos) + } else { + None + }; + } + + #[inline] + pub fn unset_warp_pos(&mut self) { + self.warp_pos = None; + } + + #[inline] + pub fn set_parent( + &mut self, + parent: Window, + ) { + self.parent = Some(parent); + } + + #[inline] + pub fn parent(&self) -> Option<Window> { + self.parent + } + + #[inline] + pub fn add_child( + &mut self, + child: Window, + ) { + self.children.push(child); + } + + #[inline] + pub fn remove_child( + &mut self, + child: Window, + ) { + if let Some(index) = self.children.iter().rposition(|c| *c == child) { + self.children.remove(index); + } + } + + #[inline] + pub fn set_leader( + &mut self, + leader: Window, + ) { + self.leader = Some(leader); + } + + #[inline] + pub fn leader(&self) -> Option<Window> { + self.leader + } + + #[inline] + pub fn set_producer( + &mut self, + producer: Window, + ) { + self.producer = Some(producer); + } + + #[inline] + pub fn unset_producer(&mut self) { + self.producer = None; + } + + #[inline] + pub fn producer(&self) -> Option<Window> { + self.producer + } + + #[inline] + pub fn consumer_len(&self) -> usize { + self.consumers.len() + } + + #[inline] + pub fn add_consumer( + &mut self, + consumer: Window, + ) { + self.consumers.push(consumer); + } + + #[inline] + pub fn remove_consumer( + &mut self, + consumer: Window, + ) { + if let Some(index) = self.consumers.iter().rposition(|c| *c == consumer) + { + self.consumers.remove(index); + } + } + + #[inline] + pub fn is_free(&self) -> bool { + self.floating || self.disowned || !self.managed + } + + #[inline] + pub fn is_focused(&self) -> bool { + self.focused + } + + #[inline] + pub fn set_focused( + &mut self, + focused: bool, + ) { + if focused { + self.last_focused = SystemTime::now(); + } + + self.focused = focused; + } + + #[inline] + pub fn is_mapped(&self) -> bool { + self.mapped + } + + #[inline] + pub fn set_mapped( + &mut self, + mapped: bool, + ) { + self.mapped = mapped; + } + + #[inline] + pub fn is_managed(&self) -> bool { + self.managed + } + + #[inline] + pub fn set_managed( + &mut self, + managed: bool, + ) { + self.managed = managed; + } + + #[inline] + pub fn is_in_window(&self) -> bool { + self.in_window + } + + #[inline] + pub fn set_in_window( + &mut self, + in_window: bool, + ) { + self.in_window = in_window; + } + + #[inline] + pub fn is_floating(&self) -> bool { + self.floating + } + + #[inline] + pub fn set_floating( + &mut self, + floating: bool, + ) { + self.floating = floating; + } + + #[inline] + pub fn is_fullscreen(&self) -> bool { + self.fullscreen + } + + #[inline] + pub fn set_fullscreen( + &mut self, + fullscreen: bool, + ) { + self.fullscreen = fullscreen; + } + + #[inline] + pub fn is_iconified(&self) -> bool { + self.iconified + } + + #[inline] + pub fn set_iconified( + &mut self, + iconified: bool, + ) { + self.iconified = iconified; + } + + #[inline] + pub fn is_disowned(&self) -> bool { + self.disowned + } + + #[inline] + pub fn set_disowned( + &mut self, + disowned: bool, + ) { + self.disowned = disowned; + } + + #[inline] + pub fn is_sticky(&self) -> bool { + self.sticky + } + + #[inline] + pub fn set_sticky( + &mut self, + sticky: bool, + ) { + self.sticky = sticky; + } + + #[inline] + pub fn is_invincible(&self) -> bool { + self.invincible + } + + #[inline] + pub fn set_invincible( + &mut self, + invincible: bool, + ) { + self.invincible = invincible; + } + + #[inline] + pub fn is_urgent(&self) -> bool { + self.urgent + } + + #[inline] + pub fn set_urgent( + &mut self, + urgent: bool, + ) { + self.urgent = urgent; + } + + #[inline] + pub fn is_consuming(&self) -> bool { + self.consuming + } + + #[inline] + pub fn set_consuming( + &mut self, + consuming: bool, + ) { + self.consuming = consuming; + } + + #[inline] + pub fn is_producing(&self) -> bool { + self.producing + } + + #[inline] + pub fn set_producing( + &mut self, + producing: bool, + ) { + self.producing = producing; + } + + #[inline] + pub fn pid(&self) -> Option<Pid> { + self.pid + } + + #[inline] + pub fn ppid(&self) -> Option<Pid> { + self.ppid + } + + #[inline] + pub fn last_focused(&self) -> SystemTime { + self.last_focused + } + + #[inline] + pub fn managed_since(&self) -> SystemTime { + self.managed_since + } + + #[inline] + pub fn expect_unmap(&mut self) { + self.expected_unmap_count += 1; + } + + #[inline] + pub fn is_expecting_unmap(&self) -> bool { + self.expected_unmap_count > 0 + } + + #[inline] + pub fn consume_unmap_if_expecting(&mut self) -> bool { + let expecting = self.expected_unmap_count > 0; + + if expecting { + self.expected_unmap_count -= 1; + } + + expecting + } +} + +impl PartialEq for Client { + fn eq( + &self, + other: &Self, + ) -> bool { + self.window == other.window + } +} + +impl std::fmt::Debug for Client { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + f.debug_struct("Client") + .field("window", &Hex32(self.window)) + .field("frame", &Hex32(self.frame)) + .field("name", &self.name) + .field("class", &self.class) + .field("instance", &self.instance) + .field("context", &self.context) + .field("workspace", &self.workspace) + .field("window_type", &self.window_type) + .field("active_region", &self.active_region) + .field("previous_region", &self.previous_region) + .field("inner_region", &self.inner_region) + .field("free_region", &self.free_region) + .field("tile_region", &self.tile_region) + .field("tree_region", &self.tree_region) + .field("frame_extents", &self.frame_extents) + .field("size_hints", &self.size_hints) + .field("warp_pos", &self.warp_pos) + .field("parent", &self.parent.map(|parent| Hex32(parent))) + .field( + "children", + &self + .children + .iter() + .map(|&child| Hex32(child)) + .collect::<Vec<Hex32>>(), + ) + .field("leader", &self.leader) + .field("producer", &self.producer) + .field("consumers", &self.consumers) + .field("focused", &self.focused) + .field("mapped", &self.mapped) + .field("managed", &self.managed) + .field("in_window", &self.in_window) + .field("floating", &self.floating) + .field("fullscreen", &self.fullscreen) + .field("iconified", &self.iconified) + .field("disowned", &self.disowned) + .field("sticky", &self.sticky) + .field("invincible", &self.invincible) + .field("urgent", &self.urgent) + .field("consuming", &self.consuming) + .field("pid", &self.pid) + .field("ppid", &self.ppid) + .field("last_focused", &self.last_focused) + .field("managed_since", &self.managed_since) + .field("expected_unmap_count", &self.expected_unmap_count) + .finish() + } +} diff --git a/src/core/common.rs b/src/core/common.rs @@ -0,0 +1,139 @@ +use winsys::common::Dim; +use winsys::common::Extents; +use winsys::common::Pos; +use winsys::common::Region; +use winsys::common::Window; + +pub type Color = u32; + +#[macro_export] +macro_rules! WM_NAME ( + () => { "wzrd" }; +); + +pub const FOCUSED_FRAME_COLOR: Color = 0xD7005F; +pub const URGENT_FRAME_COLOR: Color = 0xD08928; +pub const REGULAR_DISOWNED_FRAME_COLOR: Color = 0x707070; +pub const FOCUSED_DISOWNED_FRAME_COLOR: Color = 0x00AA80; +pub const REGULAR_STICKY_FRAME_COLOR: Color = 0x6C9EF8; +pub const FOCUSED_STICKY_FRAME_COLOR: Color = 0xB77FDB; +pub const REGULAR_FRAME_COLOR: Color = 0x191A2A; + +pub const MIN_WINDOW_DIM: Dim = Dim { + w: 75, + h: 50, +}; +pub const BORDER_SIZE: u32 = 3; + +pub const FREE_EXTENTS: Extents = Extents { + left: 3, + right: 1, + top: 1, + bottom: 1, +}; + +pub const NO_EXTENTS: Extents = Extents { + left: 0, + right: 0, + top: 0, + bottom: 0, +}; + +pub type Ident = u32; +pub type Index = usize; + +pub trait Identify: PartialEq { + fn id(&self) -> Ident; +} + +impl Identify for Window { + fn id(&self) -> Ident { + *self as Ident + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Direction { + Forward, + Backward, +} + +impl Direction { + pub fn rev(&self) -> Self { + match self { + Self::Forward => Self::Backward, + Self::Backward => Self::Forward, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Change { + Inc, + Dec, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BorderState { + Urgent, + Focused, + Unfocused, + Disowned, + Sticky, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BorderSize { + pub left: u32, + pub right: u32, + pub top: u32, + pub bottom: u32, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Placement { + pub window: Window, + pub region: Option<Region>, + pub extents: Option<Extents>, +} + +impl Placement { + pub fn new( + window: Window, + region: Option<Region>, + extents: Option<Extents>, + ) -> Self { + Self { + window, + region, + extents, + } + } + + pub fn inner_region(&self) -> Option<Region> { + if let Some(region) = self.region { + if let Some(extents) = self.extents { + return Some(Region { + pos: Pos { + x: extents.left as i32, + y: extents.top as i32, + }, + dim: Dim { + w: region.dim.w - extents.left - extents.right, + h: region.dim.h - extents.top - extents.bottom, + }, + }); + } else { + return Some(Region { + pos: Pos { + x: 0, + y: 0, + }, + dim: region.dim, + }); + } + } + + None + } +} diff --git a/src/core/config.rs b/src/core/config.rs @@ -0,0 +1 @@ + diff --git a/src/core/consume.rs b/src/core/consume.rs @@ -0,0 +1,49 @@ +use crate::client::Client; +use winsys::common::Pid; +use winsys::common::Window; + +use std::collections::HashMap; +use std::fs; + +fn get_parent_pid(pid: Pid) -> Option<Pid> { + if let Ok(stat) = fs::read_to_string(format!("/proc/{}/stat", pid)) { + let stat = stat.split(" ").collect::<Vec<&str>>(); + return stat.get(3).and_then(|ppid| ppid.parse::<Pid>().ok()); + } + + None +} + +pub fn get_spawner_pid( + pid: Pid, + wm_pid: Pid, + pid_map: &HashMap<Pid, Window>, + client_map: &HashMap<Window, Client>, +) -> Option<Pid> { + let mut ppid = get_parent_pid(pid); + + while ppid.is_some() { + let ppid_new = get_parent_pid(ppid.unwrap()); + + let mut is_consumer = false; + if let Some(ppid_new) = ppid_new { + if let Some(window) = pid_map.get(&ppid_new) { + if let Some(client) = client_map.get(&window) { + is_consumer = client.is_consuming(); + } + } + }; + + if is_consumer { + return ppid_new; + } + + if ppid_new == Some(wm_pid) { + return if ppid == Some(pid) { None } else { ppid }; + } + + ppid = ppid_new; + } + + None +} diff --git a/src/core/context.rs b/src/core/context.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/actions.rs b/src/core/contrib/actions.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/extensions/ipc.rs b/src/core/contrib/extensions/ipc.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/extensions/mod.rs b/src/core/contrib/extensions/mod.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/extensions/scratchpad.rs b/src/core/contrib/extensions/scratchpad.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/hooks.rs b/src/core/contrib/hooks.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/layouts.rs b/src/core/contrib/layouts.rs @@ -0,0 +1 @@ + diff --git a/src/core/contrib/mod.rs b/src/core/contrib/mod.rs @@ -0,0 +1 @@ + diff --git a/src/core/cycle.rs b/src/core/cycle.rs @@ -0,0 +1,1140 @@ +use crate::common::Direction; +use crate::common::Ident; +use crate::common::Identify; +use crate::common::Index; +use crate::util::BuildIdHasher; +use crate::util::Util; + +use std::cmp::Ordering; +use std::collections::HashMap; +use std::collections::VecDeque; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum InsertPos { + BeforeActive, + AfterActive, + BeforeIndex(Index), + AfterIndex(Index), + BeforeIdent(Ident), + AfterIdent(Ident), + Front, + Back, +} + +#[derive(Clone, Copy)] +pub enum Selector<'a, T> { + AtActive, + AtIndex(Index), + AtIdent(Ident), + First, + Last, + ForCond(&'a dyn Fn(&T) -> bool), +} + +#[derive(Clone, Copy, PartialEq)] +enum StackAction { + Insert, + Remove, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Cycle<T> +where + T: Identify + std::fmt::Debug, +{ + index: Index, + + elements: VecDeque<T>, + indices: HashMap<Ident, Index, BuildIdHasher>, + + unwindable: bool, + stack: VecDeque<Ident>, +} + +impl<T> Cycle<T> +where + T: Identify + std::fmt::Debug, +{ + const MAX_STACK_LEN: usize = 0x10; + + pub fn new( + elements: Vec<T>, + unwindable: bool, + ) -> Self { + Self { + indices: elements + .iter() + .enumerate() + .map(|(i, e)| (e.id(), i)) + .collect(), + + index: Util::last_index(elements.iter()), + elements: elements.into(), + + unwindable, + stack: VecDeque::with_capacity(Self::MAX_STACK_LEN), + } + } + + #[inline] + fn index(&self) -> Option<Index> { + if self.index < self.elements.len() { + Some(self.index) + } else { + None + } + } + + pub fn clear(&mut self) { + self.index = 0; + self.elements.clear(); + self.indices.clear(); + self.stack.clear(); + } + + #[inline] + pub fn next_will_wrap( + &self, + dir: Direction, + ) -> bool { + self.index == Util::last_index(self.elements.iter()) + && dir == Direction::Forward + || self.index == 0 && dir == Direction::Backward + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } + + #[inline] + pub fn contains( + &self, + element: &T, + ) -> bool { + self.elements.contains(element) + } + + #[inline] + pub fn active_index(&self) -> Index { + self.index + } + + #[inline] + pub fn next_index( + &self, + dir: Direction, + ) -> Index { + self.next_index_from(self.index, dir) + } + + #[inline] + pub fn next_element( + &self, + dir: Direction, + ) -> Option<&T> { + let next_index = self.next_index(dir); + self.get_for(&Selector::AtIndex(next_index)) + } + + pub fn cycle_active( + &mut self, + dir: Direction, + ) -> Option<&T> { + self.push_active_to_stack(); + self.index = self.next_index(dir); + self.active_element() + } + + pub fn index_for( + &self, + sel: &Selector<T>, + ) -> Option<Index> { + match sel { + Selector::AtActive => Some(self.index), + Selector::AtIndex(index) => { + if *index < self.len() { + return Some(*index); + } + + None + }, + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.index_for(&Selector::AtIndex(index)); + } + + None + }, + Selector::First => Some(0), + Selector::Last => Some(self.elements.len() - 1), + Selector::ForCond(f) => self.by(f).map(|(i, _)| i), + } + } + + #[inline] + pub fn active_element(&self) -> Option<&T> { + self.elements.get(self.index) + } + + #[inline] + pub fn active_element_mut(&mut self) -> Option<&mut T> { + self.elements.get_mut(self.index) + } + + pub fn rotate( + &mut self, + dir: Direction, + ) { + if !self.elements.is_empty() { + match dir { + Direction::Forward => self.elements.rotate_right(1), + Direction::Backward => self.elements.rotate_left(1), + }; + + self.indices.clear(); + for (i, id) in + self.elements.iter().enumerate().map(|(i, e)| (i, e.id())) + { + self.indices.insert(id as Ident, i as Index); + } + } + } + + pub fn drag_active( + &mut self, + dir: Direction, + ) -> Option<&T> { + match (self.index, self.next_index(dir), dir) { + (0, _, Direction::Backward) => self.rotate(dir), + (_, 0, Direction::Forward) => self.rotate(dir), + (active, next, _) => { + let active_id = self.elements.get(active).unwrap().id(); + let next_id = self.elements.get(next).unwrap().id(); + + self.elements.swap(active, next); + + *self.indices.get_mut(&active_id).unwrap() = next; + *self.indices.get_mut(&next_id).unwrap() = active; + }, + }; + + self.cycle_active(dir) + } + + #[inline] + pub fn len(&self) -> usize { + self.elements.len() + } + + pub fn insert_at( + &mut self, + insert_pos: &InsertPos, + element: T, + ) { + match insert_pos { + InsertPos::BeforeActive => self.insert(self.index, element), + InsertPos::AfterActive => { + self.insert_at(&InsertPos::AfterIndex(self.index), element) + }, + InsertPos::BeforeIndex(index) => self.insert(*index, element), + InsertPos::Front => self.push_front(element), + InsertPos::Back => self.push_back(element), + InsertPos::AfterIndex(index) => { + let next_index = index + 1; + + if next_index > self.elements.len() { + self.push_back(element) + } else { + self.insert(next_index, element) + } + }, + InsertPos::BeforeIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + self.insert_at(&InsertPos::BeforeIndex(index), element); + } + }, + InsertPos::AfterIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + self.insert_at(&InsertPos::AfterIndex(index), element); + } + }, + } + } + + pub fn insert( + &mut self, + index: Index, + element: T, + ) { + self.push_active_to_stack(); + self.sync_indices(index, StackAction::Insert); + self.indices.insert((&element).id(), index); + self.elements.insert(index, element); + self.index = index; + } + + pub fn push_front( + &mut self, + element: T, + ) { + self.push_active_to_stack(); + self.sync_indices(0, StackAction::Insert); + self.indices.insert((&element).id(), 0); + self.elements.push_front(element); + self.index = 0; + } + + pub fn push_back( + &mut self, + element: T, + ) { + let end = self.elements.len(); + + self.push_active_to_stack(); + self.indices.insert((&element).id(), end); + self.elements.push_back(element); + self.index = end; + } + + #[inline] + pub fn iter(&self) -> std::collections::vec_deque::Iter<T> { + self.elements.iter() + } + + #[inline] + pub fn iter_mut(&mut self) -> std::collections::vec_deque::IterMut<T> { + self.elements.iter_mut() + } + + #[inline] + pub fn get( + &self, + index: Index, + ) -> Option<&T> { + self.elements.get(index) + } + + #[inline] + pub fn get_mut( + &mut self, + index: Index, + ) -> Option<&mut T> { + self.elements.get_mut(index) + } + + pub fn get_for( + &self, + sel: &Selector<T>, + ) -> Option<&T> { + match sel { + Selector::AtActive => self.active_element(), + Selector::AtIndex(index) => self.elements.get(*index), + Selector::First => self.elements.get(0), + Selector::Last => { + self.elements.get(Util::last_index(self.elements.iter())) + }, + Selector::ForCond(f) => self.by(f).map(|(_, e)| e), + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.get_for(&Selector::AtIndex(index)); + } + + None + }, + } + } + + pub fn get_for_mut( + &mut self, + sel: &Selector<T>, + ) -> Option<&mut T> { + match sel { + Selector::AtActive => self.active_element_mut(), + Selector::AtIndex(index) => self.elements.get_mut(*index), + Selector::First => self.elements.get_mut(0), + Selector::Last => self + .elements + .get_mut(Util::last_index(self.elements.iter())), + Selector::ForCond(f) => self.by_mut(f).map(|(_, e)| e), + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.get_for_mut(&Selector::AtIndex(index)); + } + + None + }, + } + } + + pub fn get_all_for( + &self, + sel: &Selector<T>, + ) -> Vec<&T> { + match sel { + Selector::AtActive => self.active_element().into_iter().collect(), + Selector::AtIndex(index) => { + self.elements.get(*index).into_iter().collect() + }, + Selector::First => self.elements.get(0).into_iter().collect(), + Selector::Last => self + .elements + .get(Util::last_index(self.elements.iter())) + .into_iter() + .collect(), + Selector::ForCond(f) => { + self.elements.iter().filter(|e| f(*e)).collect() + }, + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.get_all_for(&Selector::AtIndex(index)); + } + + vec![] + }, + } + } + + pub fn get_all_for_mut( + &mut self, + sel: &Selector<T>, + ) -> Vec<&mut T> { + match sel { + Selector::AtActive => { + self.active_element_mut().into_iter().collect() + }, + Selector::AtIndex(index) => { + self.elements.get_mut(*index).into_iter().collect() + }, + Selector::First => self.elements.get_mut(0).into_iter().collect(), + Selector::Last => self + .elements + .get_mut(Util::last_index(self.elements.iter())) + .into_iter() + .collect(), + Selector::ForCond(f) => { + self.elements.iter_mut().filter(|e| f(*e)).collect() + }, + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.get_all_for_mut(&Selector::AtIndex(index)); + } + + vec![] + }, + } + } + + pub fn on_active<F: Fn(&T)>( + &self, + f: F, + ) { + if let Some(element) = self.active_element() { + f(element); + } + } + + pub fn on_active_mut<F: FnMut(&mut T)>( + &mut self, + mut f: F, + ) { + if let Some(element) = self.active_element_mut() { + f(element); + } + } + + pub fn on_all<F: Fn(&T)>( + &self, + f: F, + ) { + for element in self.elements.iter() { + f(element); + } + } + + pub fn on_all_mut<F: FnMut(&mut T)>( + &mut self, + mut f: F, + ) { + for element in self.elements.iter_mut() { + f(element); + } + } + + pub fn on_all_for<F: Fn(&T)>( + &self, + f: F, + sel: &Selector<T>, + ) { + for element in self.get_all_for(sel) { + f(element); + } + } + + pub fn on_all_for_mut<F: FnMut(&mut T)>( + &mut self, + mut f: F, + sel: &Selector<T>, + ) { + for element in self.get_all_for_mut(sel) { + f(element); + } + } + + pub fn activate_for( + &mut self, + sel: &Selector<T>, + ) -> Option<&T> { + match sel { + Selector::AtActive => self.active_element(), + Selector::AtIndex(index) => { + self.push_active_to_stack(); + self.index = *index; + self.active_element() + }, + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.activate_for(&Selector::AtIndex(index)); + } + + None + }, + Selector::First => { + self.push_active_to_stack(); + self.index = 0; + self.active_element() + }, + Selector::Last => { + self.push_active_to_stack(); + self.index = Util::last_index(self.elements.iter()); + self.active_element() + }, + Selector::ForCond(f) => { + if let Some((index, _)) = self.by(f) { + self.push_active_to_stack(); + self.index = index; + Some(&self.elements[self.index]) + } else { + None + } + }, + } + } + + pub fn remove_for( + &mut self, + sel: &Selector<T>, + ) -> Option<T> { + let (index, element) = match sel { + Selector::AtActive => { + (self.index, self.elements.remove(self.index)) + }, + Selector::AtIndex(index) => (*index, self.elements.remove(*index)), + Selector::AtIdent(id) => { + if let Some(index) = self.id_to_index(*id) { + return self.remove_for(&Selector::AtIndex(index)); + } + + return None; + }, + Selector::First => (0, self.elements.remove(0)), + Selector::Last => { + let end = Util::last_index(self.elements.iter()); + (end, self.elements.remove(end)) + }, + Selector::ForCond(f) => { + if let Some((index, _)) = self.by(f) { + (index, self.elements.remove(index)) + } else { + return None; + } + }, + }; + + self.remove_element(index, &element); + element + } + + pub fn swap( + &mut self, + sel1: &Selector<T>, + sel2: &Selector<T>, + ) { + let index1 = self.index_for(sel1); + + if let Some(index1) = index1 { + let index2 = self.index_for(sel2); + + if let Some(index2) = index2 { + self.elements.swap(index1, index2); + } + } + } + + fn next_index_from( + &self, + index: Index, + dir: Direction, + ) -> Index { + let end = Util::last_index(self.elements.iter()); + + match dir { + Direction::Forward => { + if index == end { + 0 + } else { + index + 1 + } + }, + Direction::Backward => { + if index == 0 { + end + } else { + index - 1 + } + }, + } + } + + fn sync_indices( + &mut self, + pivot_index: Index, + action: StackAction, + ) { + for index in pivot_index..self.elements.len() { + let id = self.elements.get(index).unwrap().id(); + + match action { + StackAction::Remove => *self.indices.get_mut(&id).unwrap() -= 1, + StackAction::Insert => *self.indices.get_mut(&id).unwrap() += 1, + } + } + + if action == StackAction::Remove { + match pivot_index.cmp(&self.index) { + Ordering::Equal => { + if let Some(id) = self.pop_from_stack() { + if let Some(index) = self.id_to_index(id) { + self.index = index; + return; + } + } + + self.index = Util::last_index(self.elements.iter()); + }, + Ordering::Less => { + if self.index > 0 { + self.index -= 1; + } + }, + Ordering::Greater => {}, + } + } + } + + fn by( + &self, + cond: impl Fn(&T) -> bool, + ) -> Option<(Index, &T)> { + self.elements.iter().enumerate().find(|(_, e)| cond(*e)) + } + + fn by_mut( + &mut self, + cond: impl Fn(&T) -> bool, + ) -> Option<(Index, &mut T)> { + self.elements.iter_mut().enumerate().find(|(_, e)| cond(*e)) + } + + fn index_of( + &self, + element: T, + ) -> Option<Index> { + self.id_to_index(element.id()) + } + + fn index_to_id( + &self, + index: Index, + ) -> Option<Ident> { + if let Some(element) = self.elements.get(index) { + return Some(element.id()); + } + + None + } + + fn id_to_index( + &self, + id: Ident, + ) -> Option<Index> { + if let Some(index) = self.indices.get(&id) { + return Some(*index); + } + + None + } + + pub fn stack(&self) -> &VecDeque<Ident> { + &self.stack + } + + pub fn stack_after_focus(&self) -> Vec<Ident> { + let mut stack: Vec<Ident> = self.stack.iter().cloned().collect(); + + if let Some(index) = self.index() { + if let Some(id) = self.index_to_id(index) { + if let Some(found_index) = stack.iter().rposition(|i| *i == id) + { + stack.remove(found_index); + } + + stack.push(id); + } + } + + stack + } + + fn push_index_to_stack( + &mut self, + index: Option<Index>, + ) { + if !self.unwindable { + return; + } + + if let Some(index) = index { + if let Some(id) = self.index_to_id(index) { + self.remove_from_stack(id); + self.stack.push_back(id); + } + } + } + + #[inline] + fn push_active_to_stack(&mut self) { + if !self.unwindable { + return; + } + + self.push_index_to_stack(self.index()); + } + + fn remove_element( + &mut self, + index: Index, + element: &Option<T>, + ) { + if let Some(element) = element { + let id = element.id(); + + self.indices.remove(&id); + self.remove_from_stack(id); + self.sync_indices(index, StackAction::Remove); + } + } + + fn remove_from_stack( + &mut self, + id: Ident, + ) { + if !self.unwindable { + return; + } + + if let Some(found_index) = self.stack.iter().rposition(|i| *i == id) { + self.stack.remove(found_index); + } + } + + #[inline] + fn pop_from_stack(&mut self) -> Option<Ident> { + if !self.unwindable { + return None; + } + + self.stack.pop_back() + } + + #[inline] + fn stack_as_vec(&self) -> Vec<Ident> { + if !self.unwindable { + return Vec::new(); + } + + self.stack.iter().cloned().collect() + } +} + +impl<T: PartialEq + Identify + std::fmt::Debug> Cycle<T> { + pub fn equivalent_selectors( + &self, + sel1: &Selector<T>, + sel2: &Selector<T>, + ) -> bool { + match (self.index_for(&sel1), self.index_for(&sel2)) { + (Some(e), Some(f)) => e == f, + _ => false, + } + } +} + +impl<T: Clone + Identify + std::fmt::Debug> Cycle<T> { + #[allow(dead_code)] + pub fn as_vec(&self) -> Vec<T> { + self.iter().cloned().collect() + } +} + +impl<T: Identify + std::fmt::Debug> std::ops::Index<Index> for Cycle<T> { + type Output = T; + + fn index( + &self, + index: Index, + ) -> &Self::Output { + &self.elements[index] + } +} + +impl<T: Identify + std::fmt::Debug> std::ops::IndexMut<Index> for Cycle<T> { + fn index_mut( + &mut self, + index: Index, + ) -> &mut Self::Output { + &mut self.elements[index] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod i32 { + impl super::Identify for i32 { + fn id(&self) -> super::Ident { + *self as super::Ident + } + } + } + + #[test] + fn removing_element_before_focus() { + let mut cycle = Cycle::new(vec![0, 10, 20, 30, 40, 50, 60], false); + + assert_eq!(cycle.index, 6); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 5); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), Some(&2)); + assert_eq!(cycle.indices.get(&40), Some(&3)); + assert_eq!(cycle.indices.get(&50), Some(&4)); + assert_eq!(cycle.indices.get(&60), Some(&5)); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 4); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), Some(&2)); + assert_eq!(cycle.indices.get(&50), Some(&3)); + assert_eq!(cycle.indices.get(&60), Some(&4)); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 3); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), Some(&2)); + assert_eq!(cycle.indices.get(&60), Some(&3)); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 2); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), Some(&2)); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 1); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 1); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(1)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + } + + #[test] + fn removing_last_element_at_focus() { + let mut cycle = Cycle::new(vec![0, 10, 20, 30, 40, 50, 60], false); + + assert_eq!(cycle.index, 6); + + cycle.remove_for(&Selector::AtIndex(6)); + + assert_eq!(cycle.index, 5); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), Some(&2)); + assert_eq!(cycle.indices.get(&30), Some(&3)); + assert_eq!(cycle.indices.get(&40), Some(&4)); + assert_eq!(cycle.indices.get(&50), Some(&5)); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(6)); + + assert_eq!(cycle.index, 5); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), Some(&2)); + assert_eq!(cycle.indices.get(&30), Some(&3)); + assert_eq!(cycle.indices.get(&40), Some(&4)); + assert_eq!(cycle.indices.get(&50), Some(&5)); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(5)); + + assert_eq!(cycle.index, 4); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), Some(&2)); + assert_eq!(cycle.indices.get(&30), Some(&3)); + assert_eq!(cycle.indices.get(&40), Some(&4)); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(4)); + + assert_eq!(cycle.index, 3); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), Some(&2)); + assert_eq!(cycle.indices.get(&30), Some(&3)); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(3)); + + assert_eq!(cycle.index, 2); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), Some(&2)); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(2)); + + assert_eq!(cycle.index, 1); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), Some(&1)); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(1)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), Some(&0)); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + } + + #[test] + fn removing_first_element_at_focus() { + let mut cycle = Cycle::new(vec![0, 10, 20, 30, 40, 50, 60], false); + + assert_eq!(cycle.index, 6); + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 5); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), Some(&0)); + assert_eq!(cycle.indices.get(&20), Some(&1)); + assert_eq!(cycle.indices.get(&30), Some(&2)); + assert_eq!(cycle.indices.get(&40), Some(&3)); + assert_eq!(cycle.indices.get(&50), Some(&4)); + assert_eq!(cycle.indices.get(&60), Some(&5)); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 4); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), Some(&0)); + assert_eq!(cycle.indices.get(&30), Some(&1)); + assert_eq!(cycle.indices.get(&40), Some(&2)); + assert_eq!(cycle.indices.get(&50), Some(&3)); + assert_eq!(cycle.indices.get(&60), Some(&4)); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 3); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), Some(&0)); + assert_eq!(cycle.indices.get(&40), Some(&1)); + assert_eq!(cycle.indices.get(&50), Some(&2)); + assert_eq!(cycle.indices.get(&60), Some(&3)); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 2); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), Some(&0)); + assert_eq!(cycle.indices.get(&50), Some(&1)); + assert_eq!(cycle.indices.get(&60), Some(&2)); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 1); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), Some(&0)); + assert_eq!(cycle.indices.get(&60), Some(&1)); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), Some(&0)); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + + cycle.activate_for(&Selector::AtIndex(0)); + assert_eq!(cycle.index, 0); + + cycle.remove_for(&Selector::AtIndex(0)); + + assert_eq!(cycle.index, 0); + assert_eq!(cycle.indices.get(&0), None); + assert_eq!(cycle.indices.get(&10), None); + assert_eq!(cycle.indices.get(&20), None); + assert_eq!(cycle.indices.get(&30), None); + assert_eq!(cycle.indices.get(&40), None); + assert_eq!(cycle.indices.get(&50), None); + assert_eq!(cycle.indices.get(&60), None); + } +} diff --git a/src/core/helpers.rs b/src/core/helpers.rs @@ -0,0 +1 @@ + diff --git a/src/core/jump.rs b/src/core/jump.rs @@ -0,0 +1,24 @@ +use crate::client::Client; +use crate::common::Index; +use crate::workspace::ClientSelector; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Order { + Ascending, + Descending, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MatchMethod { + Equals, + Contains, +} + +#[derive(Clone, Copy)] +pub enum JumpCriterium { + OnWorkspaceBySelector(Index, &'static ClientSelector), + ByName(&'static str, MatchMethod), + ByClass(&'static str, MatchMethod), + ByInstance(&'static str, MatchMethod), + ForCond(&'static dyn Fn(&Client) -> bool), +} diff --git a/src/core/layout.rs b/src/core/layout.rs @@ -0,0 +1,1215 @@ +use crate::client::Client; +use crate::common::Change; +use crate::common::Ident; +use crate::common::Identify; +use crate::common::Placement; +use crate::common::FREE_EXTENTS; +use crate::common::MIN_WINDOW_DIM; +use crate::util::Util; + +use winsys::common::Dim; +use winsys::common::Edge; +use winsys::common::Extents; +use winsys::common::Padding; +use winsys::common::Pos; +use winsys::common::Region; +use winsys::common::Window; + +use std::default::Default; + +pub type LayoutFn = + fn(&[&Client], Option<Window>, &Region, &LayoutData) -> Vec<Placement>; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum LayoutMethod { + /// Does not inhibit free placement of clients + Free, + /// Arranges clients along a predefined layout + Tile, + /// Semi-adjustable tree-based layout + Tree, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum LayoutKind { + Center, + Float, + Monocle, + Paper, + PaperCenter, + SStack, + SingleFloat, + Stack, +} + +pub struct LayoutFactory {} + +impl LayoutFactory { + pub fn create_layout(kind: LayoutKind) -> Layout { + match kind { + LayoutKind::Center => CenterLayout::build(), + LayoutKind::Float => FloatLayout::build(), + LayoutKind::Monocle => MonocleLayout::build(), + LayoutKind::Paper => PaperLayout::build(), + LayoutKind::PaperCenter => PaperCenterLayout::build(), + LayoutKind::SStack => SStackLayout::build(), + LayoutKind::SingleFloat => SingleFloatLayout::build(), + LayoutKind::Stack => StackLayout::build(), + } + } +} + +pub trait LayoutApply { + fn arrange( + &self, + clients: &[&Client], + focus: Option<Window>, + screen: &Region, + ) -> Vec<Placement>; +} + +#[derive(Clone)] +pub struct Layout { + pub kind: LayoutKind, + pub symbol: char, + pub name: String, + pub config: LayoutConfig, + data: LayoutData, + default_data: LayoutData, + logic: LayoutFn, +} + +impl Layout { + const MAX_MAIN_COUNT: u32 = 15; + const MAX_GAP_SIZE: u32 = 300; + const MAX_MARGIN: Padding = Padding { + left: 350, + right: 350, + top: 200, + bottom: 200, + }; + + pub fn new( + kind: LayoutKind, + symbol: char, + name: impl Into<String>, + config: LayoutConfig, + data: LayoutData, + logic: LayoutFn, + ) -> Self { + Self { + kind, + symbol, + name: name.into(), + config, + data, + default_data: data, + logic, + } + } + + pub fn change_gap_size( + &mut self, + change: Change, + delta: u32, + ) { + if let LayoutData::Tile(ref mut data) = self.data { + data.gap_size = Util::change_within_range( + 0, + Self::MAX_GAP_SIZE, + data.gap_size, + change, + delta, + ); + } + } + + pub fn reset_gap_size(&mut self) { + match self.data { + LayoutData::Tile(ref mut data) => { + if let LayoutData::Tile(default_data) = self.default_data { + data.gap_size = default_data.gap_size; + } + }, + LayoutData::Tree(ref mut data) => { + if let LayoutData::Tree(default_data) = self.default_data { + data.gap_size = default_data.gap_size; + } + }, + _ => {}, + }; + } + + pub fn main_count(&self) -> Option<u32> { + if let LayoutData::Tile(data) = self.data { + Some(data.main_count) + } else { + None + } + } + + pub fn change_main_count( + &mut self, + change: Change, + ) { + if let LayoutData::Tile(ref mut data) = self.data { + data.main_count = Util::change_within_range( + 0, + Self::MAX_MAIN_COUNT, + data.main_count, + change, + 1, + ); + } + } + + pub fn change_main_factor( + &mut self, + change: Change, + delta: f32, + ) { + if let LayoutData::Tile(ref mut data) = self.data { + match change { + Change::Inc => data.main_factor += delta, + Change::Dec => data.main_factor -= delta, + } + + if data.main_factor < 0.05f32 { + data.main_factor = 0.05f32; + } else if data.main_factor > 0.95f32 { + data.main_factor = 0.95f32; + } + } + } + + pub fn change_margin( + &mut self, + edge: Edge, + change: Change, + delta: u32, + ) { + match self.data { + LayoutData::Tile(ref mut data) => { + if let LayoutData::Tile(default_data) = self.default_data { + if let Some(ref mut margin) = data.margin { + let default_margin = + if let Some(margin) = default_data.margin { + margin + } else { + Padding::default() + }; + + match edge { + Edge::Left => { + margin.left = Util::change_within_range( + default_margin.left, + Self::MAX_MARGIN.left, + margin.left, + change, + delta, + ); + }, + Edge::Right => { + margin.right = Util::change_within_range( + default_margin.right, + Self::MAX_MARGIN.right, + margin.right, + change, + delta, + ); + }, + Edge::Top => { + margin.top = Util::change_within_range( + default_margin.top, + Self::MAX_MARGIN.top, + margin.top, + change, + delta, + ); + }, + Edge::Bottom => { + margin.bottom = Util::change_within_range( + default_margin.bottom, + Self::MAX_MARGIN.bottom, + margin.bottom, + change, + delta, + ); + }, + } + } + } + }, + LayoutData::Tree(ref mut data) => { + if let LayoutData::Tree(default_data) = self.default_data { + if let Some(ref mut margin) = data.margin { + let default_margin = + if let Some(margin) = default_data.margin { + margin + } else { + Padding::default() + }; + + match edge { + Edge::Left => { + margin.left = Util::change_within_range( + default_margin.left, + Self::MAX_MARGIN.left, + margin.left, + change, + delta, + ); + }, + Edge::Right => { + margin.right = Util::change_within_range( + default_margin.right, + Self::MAX_MARGIN.right, + margin.right, + change, + delta, + ); + }, + Edge::Top => { + margin.top = Util::change_within_range( + default_margin.top, + Self::MAX_MARGIN.top, + margin.top, + change, + delta, + ); + }, + Edge::Bottom => { + margin.bottom = Util::change_within_range( + default_margin.bottom, + Self::MAX_MARGIN.bottom, + margin.bottom, + change, + delta, + ); + }, + } + } + } + }, + _ => {}, + }; + } + + pub fn reset_margin(&mut self) { + match self.data { + LayoutData::Tile(ref mut data) => { + if let LayoutData::Tile(default_data) = self.default_data { + data.margin = default_data.margin; + } + }, + LayoutData::Tree(ref mut data) => { + if let LayoutData::Tree(default_data) = self.default_data { + data.margin = default_data.margin; + } + }, + _ => {}, + }; + } + pub fn reset(&mut self) { + self.data = self.default_data; + } + + fn adjust_for_margin( + region: Region, + extents: &Extents, + ) -> Region { + Region { + pos: Pos { + x: region.pos.x + extents.left as i32, + y: region.pos.y + extents.top as i32, + }, + dim: Dim { + w: region.dim.w - extents.left - extents.right, + h: region.dim.h - extents.top - extents.bottom, + }, + } + } + + pub fn adjust_for_padding( + mut placement: Placement, + gap_size: u32, + ) -> Placement { + if let Some(ref mut region) = placement.region { + let padding = 2 * gap_size; + + region.pos.x += gap_size as i32; + region.pos.y += gap_size as i32; + + if region.dim.w >= padding + MIN_WINDOW_DIM.w { + region.dim.w -= padding; + } else { + region.dim.w = MIN_WINDOW_DIM.w; + } + + if region.dim.h >= padding + MIN_WINDOW_DIM.h { + region.dim.h -= padding; + } else { + region.dim.h = MIN_WINDOW_DIM.h; + } + } + + placement + } +} + +impl LayoutApply for Layout { + fn arrange( + &self, + clients: &[&Client], + focus: Option<Window>, + screen: &Region, + ) -> Vec<Placement> { + let screen = Layout::adjust_for_margin( + *screen, + &LayoutData::screen_margin(&self.data), + ); + + (self.logic)(clients, focus, &screen, &self.data) + .iter_mut() + .map(|p| { + Layout::adjust_for_padding( + *p, + if self.config.gap { + LayoutData::gap_size(&self.data) + } else { + 0 + }, + ) + }) + .collect() + } +} + +impl std::cmp::PartialEq<Self> for Layout { + fn eq( + &self, + other: &Self, + ) -> bool { + self.kind == other.kind + && self.symbol == other.symbol + && self.name == other.name + && self.config == other.config + && self.data == other.data + } +} + +impl Identify for Layout { + fn id(&self) -> Ident { + self.kind as Ident + } +} + +#[non_exhaustive] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct LayoutConfig { + pub method: LayoutMethod, + pub mirrorable: bool, + pub gap: bool, + pub persistent: bool, + pub single: bool, + pub wraps: bool, +} + +impl Default for LayoutConfig { + fn default() -> Self { + Self { + method: LayoutMethod::Free, + mirrorable: false, + gap: false, + persistent: false, + single: false, + wraps: true, + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum LayoutData { + Free(FreeData), + Tile(TileData), + Tree(TreeData), +} + +impl LayoutData { + fn screen_margin(data: &LayoutData) -> Extents { + let no_margin = Extents { + left: 0, + right: 0, + top: 0, + bottom: 0, + }; + + match data { + LayoutData::Free(_) => no_margin, + LayoutData::Tile(data) => { + if let Some(margin) = data.margin { + margin + } else { + no_margin + } + }, + LayoutData::Tree(data) => { + if let Some(margin) = data.margin { + margin + } else { + no_margin + } + }, + } + } + + fn gap_size(data: &LayoutData) -> u32 { + match data { + LayoutData::Free(_) => 0, + LayoutData::Tile(data) => data.gap_size, + LayoutData::Tree(data) => data.gap_size, + } + } +} + +#[non_exhaustive] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct FreeData { + pub frame_extents: Option<Extents>, +} + +impl Default for FreeData { + fn default() -> Self { + Self { + frame_extents: Some(FREE_EXTENTS), + } + } +} + +#[non_exhaustive] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct TileData { + pub main_count: u32, + pub gap_size: u32, + pub main_factor: f32, + pub mirrored: bool, + pub margin: Option<Padding>, + pub frame_extents: Option<Extents>, +} + +impl Default for TileData { + fn default() -> Self { + Self { + main_count: 1, + gap_size: 15, + main_factor: 0.5, + mirrored: false, + margin: Some(Padding { + left: 0, + right: 0, + top: 0, + bottom: 0, + }), + frame_extents: Some(Extents { + left: 0, + right: 0, + top: 3, + bottom: 0, + }), + } + } +} + +impl TileData { + pub fn stack_split<T>( + clients: &[T], + n_main: u32, + ) -> (u32, u32) { + let n = clients.len() as u32; + if n <= n_main { + (n, 0) + } else { + (n_main, n - n_main) + } + } +} + +#[non_exhaustive] +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct TreeData { + pub gap_size: u32, + pub mirrored: bool, + pub margin: Option<Padding>, + pub frame_extents: Option<Extents>, +} + +impl Default for TreeData { + fn default() -> Self { + Self { + gap_size: 15, + mirrored: false, + margin: Some(Padding { + left: 15, + right: 15, + top: 15, + bottom: 15, + }), + frame_extents: Some(Extents { + left: 0, + right: 0, + top: 3, + bottom: 0, + }), + } + } +} + +trait LayoutBuilder { + fn build() -> Layout; +} + +trait LayoutFunc { + fn func( + clients: &[&Client], + focus: Option<Window>, + screen: &Region, + data: &LayoutData, + ) -> Vec<Placement>; +} + +struct FloatLayout {} + +impl LayoutBuilder for FloatLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Free, + mirrorable: false, + gap: false, + persistent: false, + single: false, + wraps: true, + }; + + let data = FreeData::default(); + + Layout { + kind: LayoutKind::Float, + symbol: 'F', + name: "float".into(), + config, + data: LayoutData::Free(data), + default_data: LayoutData::Free(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for FloatLayout { + fn func( + clients: &[&Client], + _focus: Option<Window>, + _screen: &Region, + data: &LayoutData, + ) -> Vec<Placement> { + if let LayoutData::Free(data) = data { + clients + .iter() + .map(|c| { + Placement::new( + c.window(), + Some(*c.free_region()), + data.frame_extents, + ) + }) + .collect() + } else { + Vec::with_capacity(0) + } + } +} + +struct SingleFloatLayout {} + +impl LayoutBuilder for SingleFloatLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Free, + mirrorable: false, + gap: false, + persistent: true, + single: true, + wraps: true, + }; + + let data = FreeData::default(); + + Layout { + kind: LayoutKind::SingleFloat, + symbol: 'Z', + name: "singlefloat".into(), + config, + data: LayoutData::Free(data), + default_data: LayoutData::Free(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for SingleFloatLayout { + fn func( + clients: &[&Client], + focus: Option<Window>, + _screen: &Region, + data: &LayoutData, + ) -> Vec<Placement> { + if let LayoutData::Free(data) = data { + if let Some(focus) = focus { + clients + .iter() + .map(|c| { + let window = c.window(); + + if window == focus { + Placement::new( + window, + Some(*c.free_region()), + data.frame_extents, + ) + } else { + Placement::new(window, None, None) + } + }) + .collect() + } else { + Vec::with_capacity(0) + } + } else { + Vec::with_capacity(0) + } + } +} + +struct StackLayout {} + +impl LayoutBuilder for StackLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Tile, + mirrorable: true, + gap: true, + persistent: false, + single: false, + wraps: true, + }; + + let data = TileData::default(); + + Layout { + kind: LayoutKind::Stack, + symbol: 'S', + name: "stack".into(), + config, + data: LayoutData::Tile(data), + default_data: LayoutData::Tile(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for StackLayout { + fn func( + clients: &[&Client], + _focus: Option<Window>, + screen: &Region, + data: &LayoutData, + ) -> Vec<Placement> { + if let LayoutData::Tile(data) = data { + let n = clients.len(); + let (screen_pos, screen_dim) = screen.values(); + + if n == 1 { + return vec![Placement::new( + clients[0].window(), + Some(*screen), + None, + )]; + } + + let (n_main, n_stack) = + TileData::stack_split(&clients, data.main_count); + + let h_stack = if n_stack > 0 { + screen_dim.h / n_stack + } else { + 0 + }; + + let h_main = if n_main > 0 { screen_dim.h / n_main } else { 0 }; + + let split = if data.main_count > 0 { + (screen_dim.w as f32 * data.main_factor) as i32 + } else { + 0 + }; + + clients + .iter() + .enumerate() + .map(|(i, c)| { + let i = i as u32; + + if i < data.main_count { + let w = if n_stack == 0 { + screen_dim.w + } else { + split as u32 + }; + + Placement::new( + c.window(), + Some(Region::new( + screen_pos.x, + screen_pos.y + (i * h_main) as i32, + w, + h_main, + )), + data.frame_extents, + ) + } else { + let sn = (i - data.main_count) as i32; + + let region = Region::new( + screen_pos.x + split, + screen_pos.y + sn * h_stack as i32, + screen_dim.w - split as u32, + h_stack, + ); + + Placement::new( + c.window(), + Some(region), + data.frame_extents, + ) + } + }) + .collect() + } else { + Vec::with_capacity(0) + } + } +} + +struct SStackLayout {} + +impl LayoutBuilder for SStackLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Tile, + mirrorable: true, + gap: false, + persistent: false, + single: false, + wraps: true, + }; + + let data = TileData::default(); + + Layout { + kind: LayoutKind::SStack, + symbol: 'T', + name: "sstack".into(), + config, + data: LayoutData::Tile(data), + default_data: LayoutData::Tile(data), + logic: StackLayout::func, + } + } +} + +struct MonocleLayout {} + +impl LayoutBuilder for MonocleLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Tile, + mirrorable: false, + gap: false, + persistent: false, + single: false, + wraps: true, + }; + + let data = TileData { + main_count: 0, + gap_size: 0, + main_factor: 0f32, + mirrored: false, + frame_extents: None, + ..Default::default() + }; + + Layout { + kind: LayoutKind::Monocle, + symbol: 'M', + name: "monocle".into(), + config, + data: LayoutData::Tile(data), + default_data: LayoutData::Tile(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for MonocleLayout { + fn func( + clients: &[&Client], + _focus: Option<Window>, + screen: &Region, + _data: &LayoutData, + ) -> Vec<Placement> { + let (screen_pos, screen_dim) = screen.values(); + + clients + .iter() + .map(|c| { + Placement::new( + c.window(), + Some(Region::new( + screen_pos.x, + screen_pos.y, + screen_dim.w, + screen_dim.h, + )), + None, + ) + }) + .collect() + } +} + +struct CenterLayout {} + +impl LayoutBuilder for CenterLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Tile, + mirrorable: false, + gap: true, + persistent: false, + single: false, + wraps: true, + }; + + let data = TileData { + main_count: 5, + gap_size: 0, + main_factor: 0.40f32, + mirrored: false, + frame_extents: None, + ..Default::default() + }; + + Layout { + kind: LayoutKind::Center, + symbol: '|', + name: "center".into(), + config, + data: LayoutData::Tile(data), + default_data: LayoutData::Tile(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for CenterLayout { + fn func( + clients: &[&Client], + _focus: Option<Window>, + screen: &Region, + data: &LayoutData, + ) -> Vec<Placement> { + if let LayoutData::Tile(data) = data { + let (screen_pos, screen_dim) = screen.values(); + + clients + .iter() + .map(|c| { + let window = c.window(); + + let w_ratio: f32 = data.main_factor / 0.95; + let h_ratio: f32 = + ((Layout::MAX_MAIN_COUNT + 1) - data.main_count) as f32 + / (Layout::MAX_MAIN_COUNT + 1) as f32; + + Placement::new( + window, + Some( + Region::new( + screen_pos.x, + screen_pos.y, + screen_dim.w, + screen_dim.h, + ) + .from_absolute_inner_center(&Dim { + w: (screen_dim.w as f32 * w_ratio) as u32, + h: (screen_dim.h as f32 * h_ratio) as u32, + }), + ), + None, + ) + }) + .collect() + } else { + Vec::with_capacity(0) + } + } +} + +struct PaperLayout {} + +impl LayoutBuilder for PaperLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Tile, + mirrorable: true, + gap: false, + persistent: true, + single: false, + wraps: false, + }; + + let data = TileData::default(); + + Layout { + kind: LayoutKind::Paper, + symbol: ';', + name: "paper".into(), + config, + data: LayoutData::Tile(data), + default_data: LayoutData::Tile(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for PaperLayout { + fn func( + clients: &[&Client], + focus: Option<Window>, + screen: &Region, + data: &LayoutData, + ) -> Vec<Placement> { + if let LayoutData::Tile(data) = data { + let n = clients.len(); + + if n == 1 { + return vec![Placement::new( + clients[0].window(), + Some(*screen), + None, + )]; + } + + let (screen_pos, screen_dim) = screen.values(); + let min_w = 0.5; + + let cw = (screen_dim.w as f32 + * if data.main_factor > min_w { + data.main_factor + } else { + min_w + }) as u32; + + let step = ((screen_dim.w - cw) as usize / (n - 1)) as i32; + let focus = focus.unwrap(); + let mut after_focus = false; + + clients + .iter() + .enumerate() + .map(|(i, c)| { + let window = c.window(); + if window == focus { + after_focus = true; + + Placement::new( + window, + Some(Region::new( + screen_pos.x + i as i32 * step, + screen_pos.y, + cw, + screen_dim.h, + )), + data.frame_extents, + ) + } else { + let mut x = screen_pos.x + i as i32 * step; + + if after_focus { + x += cw as i32 - step + }; + + Placement::new( + window, + Some(Region::new( + x, + screen_pos.y, + step as u32, + screen_dim.h, + )), + data.frame_extents, + ) + } + }) + .collect() + } else { + Vec::with_capacity(0) + } + } +} + +struct PaperCenterLayout {} + +impl LayoutBuilder for PaperCenterLayout { + fn build() -> Layout { + let config = LayoutConfig { + method: LayoutMethod::Tile, + mirrorable: false, + gap: false, + persistent: true, + single: false, + wraps: false, + }; + + let data = TileData { + main_count: 0, + main_factor: 0.95f32, + gap_size: (Layout::MAX_GAP_SIZE as f32 / 2f32) as u32, + ..Default::default() + }; + + Layout { + kind: LayoutKind::PaperCenter, + symbol: ';', + name: "papercenter".into(), + config, + data: LayoutData::Tile(data), + default_data: LayoutData::Tile(data), + logic: Self::func, + } + } +} + +impl LayoutFunc for PaperCenterLayout { + fn func( + clients: &[&Client], + focus: Option<Window>, + screen: &Region, + data: &LayoutData, + ) -> Vec<Placement> { + if let LayoutData::Tile(data) = data { + let n = clients.len(); + + if n == 1 { + return vec![Placement::new( + clients[0].window(), + Some(*screen), + None, + )]; + } + + let min_w = 0.5; + let max_w = 0.95; + let w_ratio: f32 = data.main_factor / 0.95; + let h_ratio: f32 = ((Layout::MAX_MAIN_COUNT + 1) - data.main_count) + as f32 + / (Layout::MAX_MAIN_COUNT + 1) as f32; + + let screen = Region::new( + screen.pos.x, + screen.pos.y, + screen.dim.w, + screen.dim.h, + ) + .from_absolute_inner_center(&Dim { + w: (screen.dim.w as f32 * w_ratio) as u32, + h: (screen.dim.h as f32 * h_ratio) as u32, + }); + + let (screen_pos, screen_dim) = screen.values(); + + let cw = data.gap_size as f32 / Layout::MAX_GAP_SIZE as f32; + let cw = (screen_dim.w as f32 + * if cw > min_w { + if cw <= max_w { + data.gap_size as f32 / Layout::MAX_GAP_SIZE as f32 + } else { + max_w + } + } else { + min_w + }) as u32; + + let step = (screen_dim.w - cw) / (n - 1) as u32; + let focus = focus.unwrap(); + let mut after_focus = false; + + clients + .iter() + .enumerate() + .map(|(i, c)| { + let window = c.window(); + if window == focus { + after_focus = true; + + Placement::new( + window, + Some(Region::new( + screen_pos.x + i as i32 * step as i32, + screen_pos.y, + cw, + screen_dim.h, + )), + data.frame_extents, + ) + } else { + let mut x = screen_pos.x + (i as i32) * (step as i32); + + if after_focus { + x += cw as i32 - step as i32 + }; + + Placement::new( + window, + Some(Region::new( + x, + screen_pos.y, + step, + screen_dim.h, + )), + data.frame_extents, + ) + } + }) + .collect() + } else { + Vec::with_capacity(0) + } + } +} + +impl std::fmt::Debug for Layout { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + f.debug_struct("Layout") + .field("kind", &self.kind) + .field("symbol", &self.symbol) + .field("name", &self.name) + .field("config", &self.config) + .field("data", &self.data) + .field("logic", &stringify!(&self.logic)) + .finish() + } +} diff --git a/src/core/macros.rs b/src/core/macros.rs @@ -0,0 +1,78 @@ +#[macro_export] +macro_rules! do_internal( + ($func:ident) => { + Box::new(|model: &mut $crate::model::Model| { + model.$func(); + }) as $crate::binding::KeyEvents + }; + + ($func:ident, $($arg:expr),+) => { + Box::new(move |model: &mut $crate::model::Model| { + model.$func($($arg),+); + }) as $crate::binding::KeyEvents + }; +); + +#[macro_export] +macro_rules! do_internal_block( + ($model:ident, $body:block) => { + Box::new(|$model: &mut $crate::model::Model| { + $body + }) as $crate::binding::KeyEvents + }; +); + +#[macro_export] +macro_rules! spawn_external( + ($cmd:expr) => { + { + Box::new(move |_: &mut $crate::model::Model| { + $crate::util::Util::spawn($cmd); + }) as $crate::binding::KeyEvents + } + }; +); + +#[macro_export] +macro_rules! spawn_from_shell( + ($cmd:expr) => { + { + Box::new(move |_: &mut $crate::model::Model| { + $crate::util::Util::spawn_shell($cmd); + }) as $crate::binding::KeyEvents + } + }; +); + +#[macro_export] +macro_rules! build_key_bindings( + { @start $key_bindings:expr, $keycodes:expr, + $( $binding:expr ),+ => $action:expr, + $($tail:tt)* + } => { + $( + match $crate::util::Util::parse_key_binding($binding, &$keycodes) { + None => panic!("could not parse key binding: {}", $binding), + Some(keycode) => $key_bindings.insert(keycode, $action), + }; + )+ + build_key_bindings!(@start $key_bindings, $keycodes, $($tail)*); + }; + + { @start $key_bindings:expr, $keycodes:expr, + $($tail:tt)* + } => { + $(compile_error!( + stringify!(incorrect syntax in build_key_bindings: $tail) + );)* + }; + + { $($tokens:tt)+ } => { + { + let mut key_bindings = std::collections::HashMap::new(); + let keycodes = $crate::util::Util::system_keycodes(); + build_key_bindings!(@start key_bindings, keycodes, $($tokens)+); + key_bindings + } + }; +); diff --git a/src/core/main.rs b/src/core/main.rs @@ -0,0 +1,531 @@ +#![deny(clippy::all)] +#![allow(dead_code)] + +#[macro_use] +extern crate log; + +#[allow(unused_imports)] +use simplelog::LevelFilter; +#[allow(unused_imports)] +use simplelog::SimpleLogger; + +pub use winsys::Result; + +use winsys::common::Edge; +use winsys::input::Button; +use winsys::input::EventTarget; +use winsys::input::Modifier; +use winsys::input::MouseEventKey; +use winsys::input::MouseEventKind; +use winsys::input::MouseShortcut; +use winsys::xdata::xconnection::XConnection; + +#[macro_use] +mod macros; + +#[macro_use] +mod common; + +mod binding; +mod client; +mod consume; +mod cycle; +mod jump; +mod layout; +mod model; +mod partition; +mod rule; +mod stack; +mod util; +mod workspace; + +use binding::KeyBindings; +use binding::MouseBindings; +use common::Change; +use common::Direction; +use jump::JumpCriterium; +use jump::MatchMethod; +use layout::LayoutKind; +use model::Model; +use workspace::ClientSelector; + +pub fn main() -> Result<()> { + #[cfg(debug_assertions)] + SimpleLogger::init(LevelFilter::Debug, simplelog::Config::default())?; + + let (conn, screen_num) = x11rb::connect(None)?; + let mut xconn = XConnection::new(&conn, screen_num)?; + + let (mouse_bindings, key_bindings) = init_bindings(); + + Model::new(&mut xconn, &key_bindings, &mouse_bindings) + .run(key_bindings, mouse_bindings); + + Ok(()) +} + +fn init_bindings() -> (MouseBindings, KeyBindings) { + let mut mouse_bindings = MouseBindings::new(); + + let modkey = if cfg!(debug_assertions) { + Modifier::Alt + } else { + Modifier::Meta + }; + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Left, vec![modkey]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.start_moving(w); + } + }), + true, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Right, vec![modkey]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.start_resizing(w); + } + }), + true, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Middle, vec![modkey]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.center_client(w); + } + }), + true, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Right, vec![modkey, Modifier::Ctrl]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.toggle_float_client(w); + } + }), + true, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Middle, vec![ + modkey, + Modifier::Ctrl, + Modifier::Shift, + ]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.toggle_fullscreen_client(w); + } + }), + true, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::ScrollDown, vec![ + modkey, + Modifier::Ctrl, + Modifier::Shift, + ]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.grow_ratio_client(w, -15); + } + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::ScrollUp, vec![ + modkey, + Modifier::Ctrl, + Modifier::Shift, + ]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.grow_ratio_client(w, 15); + } + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Forward, vec![modkey]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.move_client_to_next_workspace(w); + } + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Client, + }, + MouseShortcut::new(Button::Backward, vec![modkey]), + ), + ( + Box::new(|m, _, w| { + if let Some(w) = w { + m.move_client_to_prev_workspace(w); + } + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Global, + }, + MouseShortcut::new(Button::ScrollDown, vec![modkey]), + ), + ( + Box::new(|m, _, _| { + m.cycle_focus(Direction::Forward); + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Global, + }, + MouseShortcut::new(Button::ScrollUp, vec![modkey]), + ), + ( + Box::new(|m, _, _| { + m.cycle_focus(Direction::Backward); + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Global, + }, + MouseShortcut::new(Button::ScrollDown, vec![ + modkey, + Modifier::Shift, + ]), + ), + ( + Box::new(|m, _, _| { + m.activate_next_workspace(); + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Press, + target: EventTarget::Global, + }, + MouseShortcut::new(Button::ScrollUp, vec![modkey, Modifier::Shift]), + ), + ( + Box::new(|m, _, _| { + m.activate_prev_workspace(); + }), + false, + ), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Release, + target: EventTarget::Global, + }, + MouseShortcut::new(Button::ScrollDown, vec![modkey]), + ), + (Box::new(|_, _, _| {}), false), + ); + + mouse_bindings.insert( + ( + MouseEventKey { + kind: MouseEventKind::Release, + target: EventTarget::Global, + }, + MouseShortcut::new(Button::ScrollUp, vec![modkey]), + ), + (Box::new(|_, _, _| {}), false), + ); + + let key_bindings = build_key_bindings!( + "1-C-S-q" => do_internal!(exit), + + // client manipulators + "1-c" => do_internal!(kill_focus), + "1-C-space" => do_internal!(center_focus), + + // client state modifiers + "1-S-space" => do_internal!(toggle_float_focus), + "1-f" => do_internal!(toggle_fullscreen_focus), + "1-x" => do_internal!(toggle_stick_focus), + "1-2-C-f" => do_internal!(toggle_in_window_focus), + "1-2-C-i" => do_internal!(toggle_invincible_focus), + "1-2-C-p" => do_internal!(toggle_producing_focus), + "1-y" => do_internal!(iconify_focus), + "1-u" => do_internal!(pop_deiconify), + "1-2-u" => do_internal_block!(model, { + let workspace_index = model.active_workspace(); + model.deiconify_all(workspace_index); + }), + + // free client arrangers + "1-C-h" => do_internal!(nudge_focus, Edge::Left, 15), + "1-C-j" => do_internal!(nudge_focus, Edge::Bottom, 15), + "1-C-k" => do_internal!(nudge_focus, Edge::Top, 15), + "1-C-l" => do_internal!(nudge_focus, Edge::Right, 15), + "1-C-S-h" => do_internal!(stretch_focus, Edge::Left, 15), + "1-C-S-j" => do_internal!(stretch_focus, Edge::Bottom, 15), + "1-C-S-k" => do_internal!(stretch_focus, Edge::Top, 15), + "1-C-S-l" => do_internal!(stretch_focus, Edge::Right, 15), + "1-C-S-y" => do_internal!(stretch_focus, Edge::Left, -15), + "1-C-S-u" => do_internal!(stretch_focus, Edge::Bottom, -15), + "1-C-S-i" => do_internal!(stretch_focus, Edge::Top, -15), + "1-C-S-o" => do_internal!(stretch_focus, Edge::Right, -15), + "1-C-Left" => do_internal!(snap_focus, Edge::Left), + "1-C-Down" => do_internal!(snap_focus, Edge::Bottom), + "1-C-Up" => do_internal!(snap_focus, Edge::Top), + "1-C-Right" => do_internal!(snap_focus, Edge::Right), + + // client order modifiers + "1-j" => do_internal!(cycle_focus, Direction::Forward), + "1-k" => do_internal!(cycle_focus, Direction::Backward), + "1-S-j" => do_internal!(drag_focus, Direction::Forward), + "1-S-k" => do_internal!(drag_focus, Direction::Backward), + "1-S-semicolon" => do_internal!(rotate_clients, Direction::Forward), + "1-S-comma" => do_internal!(rotate_clients, Direction::Backward), + + // active workspace layout setters + "1-m" => do_internal!(set_layout, LayoutKind::Monocle), + "1-t" => do_internal!(set_layout, LayoutKind::Stack), + "1-g" => do_internal!(set_layout, LayoutKind::Center), + "1-z" => do_internal!(set_layout, LayoutKind::SingleFloat), + "1-S-f" => do_internal!(set_layout, LayoutKind::Float), + "1-C-S-f" => do_internal!(apply_float_retain_region), + "1-S-t" => do_internal!(set_layout, LayoutKind::SStack), + "1-C-S-p" => do_internal!(set_layout, LayoutKind::Paper), + "1-space" => do_internal!(toggle_layout), + + // active workspace layout-data modifiers + "1-plus" => do_internal!(change_gap_size, Change::Inc), + "1-minus" => do_internal!(change_gap_size, Change::Dec), + "1-S-equal" => do_internal!(reset_gap_size), + "1-i" => do_internal!(change_main_count, Change::Inc), + "1-d" => do_internal!(change_main_count, Change::Dec), + "1-l" => do_internal!(change_main_factor, Change::Inc), + "1-h" => do_internal!(change_main_factor, Change::Dec), + "1-S-Left" => do_internal!(change_margin, Edge::Left, Change::Inc), + "1-C-S-Left" => do_internal!(change_margin, Edge::Left, Change::Dec), + "1-S-Up" => do_internal!(change_margin, Edge::Top, Change::Inc), + "1-C-S-Up" => do_internal!(change_margin, Edge::Top, Change::Dec), + "1-S-Down" => do_internal!(change_margin, Edge::Bottom, Change::Inc), + "1-C-S-Down" => do_internal!(change_margin, Edge::Bottom, Change::Dec), + "1-S-Right" => do_internal!(change_margin, Edge::Right, Change::Inc), + "1-C-S-Right" => do_internal!(change_margin, Edge::Right, Change::Dec), + "1-C-S-equal" => do_internal!(reset_margin), + "1-2-C-S-equal" => do_internal!(reset_layout), + + // workspace activators + "1-Escape" => do_internal!(toggle_workspace), + "1-bracketleft" => do_internal!(activate_prev_workspace), + "1-bracketright" => do_internal!(activate_next_workspace), + "1-1" => do_internal!(activate_workspace, 0), + "1-2" => do_internal!(activate_workspace, 1), + "1-3" => do_internal!(activate_workspace, 2), + "1-4" => do_internal!(activate_workspace, 3), + "1-5" => do_internal!(activate_workspace, 4), + "1-6" => do_internal!(activate_workspace, 5), + "1-7" => do_internal!(activate_workspace, 6), + "1-8" => do_internal!(activate_workspace, 7), + "1-9" => do_internal!(activate_workspace, 8), + "1-0" => do_internal!(activate_workspace, 9), + + // workspace client movement + "1-S-bracketleft" => do_internal!(move_focus_to_prev_workspace), + "1-S-bracketright" => do_internal!(move_focus_to_next_workspace), + "1-S-1" => do_internal!(move_focus_to_workspace, 0), + "1-S-2" => do_internal!(move_focus_to_workspace, 1), + "1-S-3" => do_internal!(move_focus_to_workspace, 2), + "1-S-4" => do_internal!(move_focus_to_workspace, 3), + "1-S-5" => do_internal!(move_focus_to_workspace, 4), + "1-S-6" => do_internal!(move_focus_to_workspace, 5), + "1-S-7" => do_internal!(move_focus_to_workspace, 6), + "1-S-8" => do_internal!(move_focus_to_workspace, 7), + "1-S-9" => do_internal!(move_focus_to_workspace, 8), + "1-S-0" => do_internal!(move_focus_to_workspace, 9), + + // placeable region modifiers + "1-v" => do_internal!(toggle_screen_struts), + + // client jump criteria + "1-b" => do_internal!(jump_client, + &JumpCriterium::ByClass("qutebrowser", MatchMethod::Equals) + ), + "1-S-b" => do_internal!(jump_client, + &JumpCriterium::ByClass("Firefox", MatchMethod::Equals) + ), + "1-C-b" => do_internal!(jump_client, + &JumpCriterium::ByClass("Chromium", MatchMethod::Equals) + ), + "1-2-space" => do_internal!(jump_client, + &JumpCriterium::ByClass("Spotify", MatchMethod::Equals) + ), + "1-e" => do_internal_block!(model, { + model.jump_client(&JumpCriterium::ByName( + "[vim]", + MatchMethod::Contains, + )); + }), + "1-slash" => do_internal_block!(model, { + let workspace_index = model.active_workspace(); + + model.jump_client(&JumpCriterium::OnWorkspaceBySelector( + workspace_index, + &ClientSelector::Last, + )); + }), + "1-period" => do_internal_block!(model, { + let workspace_index = model.active_workspace(); + + model.jump_client(&JumpCriterium::OnWorkspaceBySelector( + workspace_index, + &ClientSelector::AtMaster, + )); + }), + "1-comma" => do_internal_block!(model, { + let workspace_index = model.active_workspace(); + + model.jump_client(&JumpCriterium::OnWorkspaceBySelector( + workspace_index, + &ClientSelector::First, + )); + }), + + // external spawn commands + "XF86AudioPlay", "1-2-p" => spawn_external!("playerctl play-pause"), + "XF86AudioPrev", "1-2-k" => spawn_external!("playerctl previous"), + "XF86AudioNext", "1-2-j" => spawn_external!("playerctl next"), + "1-2-BackSpace" => spawn_external!("playerctl stop"), + "XF86AudioMute" => spawn_external!("amixer -D pulse sset Master toggle"), + "XF86AudioLowerVolume" => spawn_external!("amixer -D pulse sset Master 5%-"), + "XF86AudioRaiseVolume" => spawn_external!("amixer -D pulse sset Master 5%+"), + + "1-Return" => spawn_external!("st"), + "1-S-Return" => spawn_external!(concat!("st -n ", WM_NAME!(), ":cf")), + + "1-p" => spawn_external!("dmenu_run"), + "1-q" => spawn_external!("qutebrowser"), + "1-S-q" => spawn_external!("firefox"), + "1-C-q" => spawn_external!("chromium"), + + "1-C-e" => spawn_external!("st -g 140x42 -e zsh -i -c neomutt"), + "1-C-s" => spawn_external!("st -g 80x42 -e zsh -i -c sncli"), + "1-C-i" => spawn_external!("st -g 80x42 -e zsh -i -c irssi"), + + "S-XF86AudioMute" => spawn_external!("amixer -D pulse sset Capture toggle"), + "XF86AudioMicMute" => spawn_external!("amixer -D pulse sset Capture toggle"), + + // external shell commands + "1-S-p" => spawn_from_shell!("$HOME/bin/dmenupass"), + "1-C-p" => spawn_from_shell!("$HOME/bin/dmenupass --copy"), + "1-S-o" => spawn_from_shell!("$HOME/bin/dmenunotify"), + "Print", "1-2-slash" => spawn_from_shell!( + "maim -u -m 1 -s \ + $(date +$HOME/screenshots/scrots/SS_%Y-%h-%d_%H-%M-%S.png)" + ), + "S-Print", "1-2-S-slash" => spawn_from_shell!( + "maim -u -m 1 \ + $(date +$HOME/screenshots/scrots/SS_%Y-%h-%d_%H-%M-%S.png)" + ), + ); + + (mouse_bindings, key_bindings) +} diff --git a/src/core/model.rs b/src/core/model.rs @@ -0,0 +1,3401 @@ +use crate::binding::KeyBindings; +use crate::binding::MouseBindings; +use crate::client::Client; +use crate::common::Change; +use crate::common::Direction; +use crate::common::Index; +use crate::common::Placement; +use crate::common::FOCUSED_FRAME_COLOR; +use crate::common::FOCUSED_STICKY_FRAME_COLOR; +use crate::common::FREE_EXTENTS; +use crate::common::MIN_WINDOW_DIM; +use crate::common::REGULAR_FRAME_COLOR; +use crate::common::REGULAR_STICKY_FRAME_COLOR; +use crate::common::URGENT_FRAME_COLOR; +use crate::consume::get_spawner_pid; +use crate::cycle::Cycle; +use crate::cycle::InsertPos; +use crate::cycle::Selector; +use crate::jump::JumpCriterium; +use crate::jump::MatchMethod; +use crate::layout::LayoutKind; +use crate::layout::LayoutMethod; +use crate::partition::Partition; +use crate::rule::Rules; +use crate::stack::StackLayer; +use crate::stack::StackManager; +use crate::workspace::Buffer; +use crate::workspace::BufferKind; +use crate::workspace::Workspace; + +#[allow(unused_imports)] +use crate::util::Util; + +use winsys::common::Corner; +use winsys::common::Dim; +use winsys::common::Edge; +use winsys::common::Extents; +use winsys::common::Grip; +use winsys::common::Hints; +use winsys::common::IcccmWindowState; +use winsys::common::Pid; +use winsys::common::Pos; +use winsys::common::Region; +use winsys::common::Window; +use winsys::common::WindowState; +use winsys::common::WindowType; +use winsys::connection::Connection; +use winsys::event::Event; +use winsys::event::PropertyKind; +use winsys::event::StackMode; +use winsys::event::ToggleAction; +use winsys::input::EventTarget; +use winsys::input::KeyCode; +use winsys::input::MouseEvent; +use winsys::input::MouseEventKey; +use winsys::input::MouseEventKind; +use winsys::screen::Screen; + +use std::collections::HashMap; + +pub struct Model<'a> { + conn: &'a mut dyn Connection, + stack: StackManager, + stacking_order: Vec<Window>, + pid_map: HashMap<Pid, Window>, + client_map: HashMap<Window, Client>, + window_map: HashMap<Window, Window>, + frame_map: HashMap<Window, Window>, + sticky_clients: Vec<Window>, + unmanaged_windows: Vec<Window>, + fullscreen_regions: HashMap<Window, Region>, + partitions: Cycle<Partition>, + workspaces: Cycle<Workspace>, + move_buffer: Buffer, + resize_buffer: Buffer, + prev_partition: Index, + prev_workspace: Index, + running: bool, + focus: Option<Window>, + jumped_from: Option<Window>, +} + +impl<'a> Model<'a> { + pub fn new( + conn: &'a mut dyn Connection, + key_bindings: &KeyBindings, + mouse_bindings: &MouseBindings, + ) -> Self { + let move_handle = conn.create_handle(); + let resize_handle = conn.create_handle(); + + Self::init( + Self { + conn, + stack: StackManager::new(), + stacking_order: Vec::new(), + pid_map: HashMap::new(), + client_map: HashMap::new(), + window_map: HashMap::new(), + frame_map: HashMap::new(), + sticky_clients: Vec::new(), + unmanaged_windows: Vec::new(), + fullscreen_regions: HashMap::new(), + partitions: Cycle::new(Vec::new(), false), + workspaces: Cycle::new(Vec::with_capacity(10), false), + move_buffer: Buffer::new(BufferKind::Move, move_handle), + resize_buffer: Buffer::new(BufferKind::Resize, resize_handle), + prev_partition: 0, + prev_workspace: 0, + running: true, + focus: None, + jumped_from: None, + }, + key_bindings, + mouse_bindings, + ) + } + + fn init( + mut model: Self, + key_bindings: &KeyBindings, + mouse_bindings: &MouseBindings, + ) -> Self { + info!("initializing window manager"); + + let workspaces = + ["main", "web", "term", "4", "5", "6", "7", "8", "9", "10"]; + + for (i, &workspace_name) in workspaces.iter().enumerate() { + model + .workspaces + .push_back(Workspace::new(workspace_name, i as u32)); + } + + model.workspaces.activate_for(&Selector::AtIndex(0)); + model.acquire_partitions(); + + model.conn.init_wm_properties(WM_NAME!(), &workspaces); + model.conn.set_current_desktop(0); + + model.conn.grab_bindings( + &key_bindings + .keys() + .cloned() + .collect::<Vec<winsys::input::KeyCode>>(), + &mouse_bindings.keys().into_iter().collect::<Vec<&( + winsys::input::MouseEventKey, + winsys::input::MouseShortcut, + )>>(), + ); + + model.conn.top_level_windows().iter().for_each(|&window| { + if model.conn.must_manage_window(window) { + model.manage(window, false); + } + }); + + if cfg!(not(debug_assertions)) { + info!("executing startup scripts"); + Util::spawn_shell(concat!( + "$HOME/.config/", + WM_NAME!(), + "/blocking_autostart" + )); + Util::spawn_shell(concat!( + "$HOME/.config/", + WM_NAME!(), + "/nonblocking_autostart &" + )); + } + + model + } + + pub fn run( + &mut self, + mut key_bindings: KeyBindings, + mut mouse_bindings: MouseBindings, + ) { + while self.running { + if let Some(event) = self.conn.step() { + trace!("received event: {:?}", event); + + match event { + Event::Mouse { + event, + } => self.handle_mouse(event, &mut mouse_bindings), + Event::Key { + key_code, + } => self.handle_key(key_code, &mut key_bindings), + Event::MapRequest { + window, + ignore, + } => self.handle_map_request(window, ignore), + Event::Map { + window, + ignore, + } => self.handle_map(window, ignore), + Event::Enter { + window, + root_rpos, + window_rpos, + } => self.handle_enter(window, root_rpos, window_rpos), + Event::Leave { + window, + root_rpos, + window_rpos, + } => self.handle_leave(window, root_rpos, window_rpos), + Event::Destroy { + window, + } => self.handle_destroy(window), + Event::Expose { + window, + } => self.handle_expose(window), + Event::Unmap { + window, + ignore, + } => self.handle_unmap(window, ignore), + Event::Configure { + window, + region, + on_root, + } => self.handle_configure(window, region, on_root), + Event::StateRequest { + window, + state, + action, + on_root, + } => self + .handle_state_request(window, state, action, on_root), + Event::FocusRequest { + window, + on_root, + } => self.handle_focus_request(window, on_root), + Event::CloseRequest { + window, + on_root, + } => self.handle_close_request(window, on_root), + Event::WorkspaceRequest { + window, + index, + on_root, + } => self.handle_workspace_request(window, index, on_root), + Event::PlacementRequest { + window, + pos, + dim, + on_root, + } => { + self.handle_placement_request(window, pos, dim, on_root) + }, + Event::GripRequest { + window, + pos, + grip, + on_root, + } => self.handle_grip_request(window, pos, grip, on_root), + Event::RestackRequest { + window, + sibling, + mode, + on_root, + } => self + .handle_restack_request(window, sibling, mode, on_root), + Event::Property { + window, + kind, + on_root, + } => self.handle_property(window, kind, on_root), + Event::FrameExtentsRequest { + window, + on_root, + } => self.handle_frame_extents_request(window, on_root), + Event::Mapping { + request, + } => self.handle_mapping(request), + Event::ScreenChange => self.handle_screen_change(), + Event::Randr => self.handle_randr(), + } + } + + self.conn.flush(); + } + } + + fn acquire_partitions(&mut self) { + info!("acquiring partitions"); + + let partitions: Vec<Partition> = self + .conn + .connected_outputs() + .into_iter() + .enumerate() + .map(|(i, mut s)| { + s.compute_placeable_region(); + Partition::new(s, i) + }) + .collect(); + + if partitions == self.partitions.as_vec() { + return; + } + + self.partitions = Cycle::new(partitions, false); + } + + fn apply_layout( + &mut self, + index: Index, + must_apply_stack: bool, + ) { + info!("applying layout on workspace {}", index); + + if index != self.active_workspace() { + return; + } + + let workspace = match self.workspaces.get(index) { + Some(workspace) => workspace, + None => return, + }; + + let layout_config = workspace.layout_config(); + let region = self.active_screen().placeable_region(); + + for placement in + workspace.arrange_with_filter(region, &self.client_map, |client| { + Self::is_applyable(client, layout_config.method) + }) + { + let frame = self.frame(placement.window).unwrap(); + + if placement.region.is_some() { + self.update_client_placement(&placement, layout_config.method); + self.place_client(placement.window, layout_config.method); + + self.map_client(frame); + } else { + self.unmap_client(frame); + } + } + + if must_apply_stack { + self.apply_stack(index); + } + } + + fn apply_stack( + &mut self, + index: Index, + ) { + info!("applying stack on workspace {}", index); + + let workspace = match self.workspaces.get(index) { + Some(workspace) => workspace, + None => return, + }; + + let desktop = self.stack.layer_windows(StackLayer::Desktop); + let below = self.stack.layer_windows(StackLayer::Below); + let dock = self.stack.layer_windows(StackLayer::Dock); + + let stack: Vec<Window> = workspace + .stack_after_focus() + .iter() + .map(|&window| self.frame(window).unwrap()) + .collect(); + + let (regular, fullscreen): (Vec<Window>, Vec<Window>) = + stack.iter().partition(|&window| { + self.client(*window).map_or(false, |client| { + !client.is_fullscreen() || client.is_in_window() + }) + }); + + let (regular, free): (Vec<Window>, Vec<Window>) = + if workspace.layout_config().method == LayoutMethod::Free { + (Vec::new(), regular) + } else { + regular.iter().partition(|&window| { + self.client(*window) + .map_or(false, |client| !client.is_free()) + }) + }; + + let above = self.stack.layer_windows(StackLayer::Above); + let notification = self.stack.layer_windows(StackLayer::Notification); + + let mut windows: Vec<Window> = desktop + .into_iter() + .chain(below.into_iter()) + .chain(dock.into_iter()) + .chain(regular.into_iter()) + .chain(fullscreen.into_iter()) + .chain(free.into_iter()) + .chain(above.into_iter()) + .chain(notification) + .into_iter() + .collect(); + + { + // handle above-other relationships + for &window in self.stack.above_other().keys() { + let index = windows.iter().position(|&w| w == window); + + if let Some(index) = index { + windows.remove(index); + } + } + + for (&window, &sibling) in self.stack.above_other() { + let index = windows.iter().position(|&w| w == sibling); + + if let Some(index) = index { + if index < windows.len() { + windows.insert(index + 1, window); + } + } + } + } + + { + // handle below-other relationships + for &window in self.stack.below_other().keys() { + let index = windows.iter().position(|&w| w == window); + + if let Some(index) = index { + windows.remove(index); + } + } + + for (&window, &sibling) in self.stack.below_other() { + let index = windows.iter().position(|&w| w == sibling); + + if let Some(index) = index { + windows.insert(index, window); + } + } + } + + let mut stack_walk = windows.iter(); + let mut order_changed = false; + let mut prev_window = stack_walk.next().map(|&w| w); + + for (i, &window) in stack_walk.enumerate() { + order_changed |= self.stacking_order.get(i + 1) != Some(&window); + + if order_changed { + self.conn.stack_window_above(window, prev_window); + } + + prev_window = Some(window); + } + + self.stacking_order = windows; + + if !order_changed { + return; + } + + let mut client_list: Vec<&Client> = + self.client_map.values().collect::<Vec<&Client>>(); + + client_list.sort_by_key(|&a| a.managed_since()); + + let client_list: Vec<Window> = + client_list.iter().map(|client| client.window()).collect(); + + self.conn.update_client_list(&client_list); + + let mut client_list_stacking = client_list; + + let stack_windows: Vec<Window> = stack + .iter() + .map(|&window| self.window(window).unwrap()) + .collect(); + + client_list_stacking.retain(|&window| !stack_windows.contains(&window)); + + client_list_stacking = client_list_stacking + .iter() + .chain(stack_windows.iter()) + .copied() + .collect(); + + self.conn.update_client_list_stacking(&client_list_stacking); + } + + fn window( + &self, + window: Window, + ) -> Option<Window> { + if self.window_map.contains_key(&window) { + return Some(window); + } + + Some(*self.frame_map.get(&window)?) + } + + fn frame( + &self, + window: Window, + ) -> Option<Window> { + if self.frame_map.contains_key(&window) { + return Some(window); + } + + Some(*self.window_map.get(&window)?) + } + + fn client_any( + &self, + mut window: Window, + ) -> Option<&Client> { + if let Some(inside) = self.frame_map.get(&window) { + window = *inside; + } + + self.client_map.get(&window) + } + + fn client( + &self, + window: Window, + ) -> Option<&Client> { + self.client_any(window).and_then(|client| { + if client.is_managed() { + Some(client) + } else { + None + } + }) + } + + fn client_any_mut( + &mut self, + mut window: Window, + ) -> Option<&mut Client> { + if let Some(inside) = self.frame_map.get(&window) { + window = *inside; + } + + self.client_map.get_mut(&window) + } + + fn client_mut( + &mut self, + window: Window, + ) -> Option<&mut Client> { + self.client_any_mut(window).and_then(|client| { + if client.is_managed() { + Some(client) + } else { + None + } + }) + } + + fn workspace( + &self, + index: Index, + ) -> &Workspace { + self.workspaces.get(index).unwrap() + } + + fn workspace_mut( + &mut self, + index: Index, + ) -> &mut Workspace { + self.workspaces.get_mut(index).unwrap() + } + + fn detect_rules( + &self, + instance: &str, + ) -> Rules { + const PREFIX: &str = &concat!(WM_NAME!(), ":"); + let mut rules: Rules = Default::default(); + + match (instance.get(..4), instance.get(4..)) { + (Some(PREFIX), flags) => { + if let Some(flags) = flags { + let mut invert = false; + + for i in 0..flags.len() { + let flag = &flags[i..=i]; + + match flag { + "!" => { + invert = true; + continue; + }, + "f" => rules.float = Some(!invert), + "c" => rules.center = Some(!invert), + _ => {}, + } + + invert = false; + } + } + }, + _ => {}, + } + + rules + } + + fn manage( + &mut self, + window: Window, + ignore: bool, + ) { + if ignore { + if self.conn.window_is_mappable(window) { + self.conn.map_window(window); + } + + self.conn.init_unmanaged(window); + self.unmanaged_windows.push(window); + + return; + } + + let pid = self.conn.get_window_pid(window); + + let ppid = pid.and_then(|pid| { + get_spawner_pid( + pid, + std::process::id() as Pid, + &self.pid_map, + &self.client_map, + ) + }); + + let name = self.conn.get_icccm_window_name(window); + let class = self.conn.get_icccm_window_class(window); + let instance = self.conn.get_icccm_window_instance(window); + + let preferred_state = self.conn.get_window_preferred_state(window); + let preferred_type = self.conn.get_window_preferred_type(window); + + let geometry = self.conn.get_window_geometry(window); + + if geometry.is_err() { + return; + } + + self.stop_moving(); + self.stop_resizing(); + + let original_geometry = geometry.unwrap(); + let mut geometry = original_geometry; + + let frame = self.conn.create_frame(geometry); + let rules = self.detect_rules(&instance); + let hints = self.conn.get_icccm_window_hints(window); + let (_, size_hints) = self.conn.get_icccm_window_size_hints( + window, + Some(MIN_WINDOW_DIM), + &None, + ); + + geometry = if size_hints.is_some() { + geometry + .with_size_hints(&size_hints) + .with_extents(&FREE_EXTENTS) + } else { + geometry + .with_minimum_dim(&MIN_WINDOW_DIM) + .with_extents(&FREE_EXTENTS) + }; + + let parent = self.conn.get_icccm_window_transient_for(window); + + let leader = self + .conn + .get_icccm_window_client_leader(window) + .and_then(|leader| self.client_any(leader)); + + // TODO: startup sequence/notification + // TODO: MOTIF decorations for old-style applications + + let context = 0; + let workspace = self.conn.get_window_desktop(window).map_or( + self.active_workspace(), + |d| { + if d < self.workspaces.len() { + d + } else { + self.active_workspace() + } + }, + ); + + let mut center = false; + + // TODO: retrieve screen of new client's workspace + let screen = self.active_screen(); + + if rules.center(&mut center) + || (size_hints.is_none() || !size_hints.unwrap().by_user) + && original_geometry.pos + == (Pos { + x: 0, + y: 0, + }) + { + geometry = screen + .full_region() + .from_absolute_inner_center(&geometry.dim); + } + + let mut client = Client::new( + window, + frame, + name, + class, + instance, + preferred_type, + pid, + ppid, + ); + + let fullscreen = self.conn.window_is_fullscreen(window); + let sticky = self.conn.window_is_sticky(window); + let mut floating = self.conn.must_free_window(window); + + if let Some(parent) = parent { + floating = true; + client.set_parent(parent); + } + + if let Some(leader) = leader { + let leader_window = leader.window(); + if leader_window != window { + floating = true; + client.set_leader(leader_window); + } + } + + if let Some(hints) = hints { + client.set_urgent(hints.urgent); + } + + rules.float(&mut floating); + + client.set_floating(floating); + client.set_free_region(&geometry); + client.set_size_hints(size_hints); + client.set_context(context); + client.set_workspace(workspace); + + self.conn.reparent_window(window, frame, Pos { + x: FREE_EXTENTS.left as i32, + y: FREE_EXTENTS.top as i32, + }); + + self.conn + .set_icccm_window_state(window, IcccmWindowState::Normal); + + if let Some(workspace) = self.workspaces.get_mut(workspace) { + workspace.add_client(window, &InsertPos::Back); + } + + if let Some(parent) = parent { + if let Some(parent) = self.client_any_mut(parent) { + let parent_frame = parent.frame(); + parent.add_child(window); + self.stack.add_above_other(frame, parent_frame); + } + } + + if let Some(pid) = pid { + self.pid_map.insert(pid, window); + } + + info!("managing client {:#?}", client); + + self.client_map.insert(window, client); + self.frame_map.insert(frame, window); + self.window_map.insert(window, frame); + + self.conn.insert_window_in_save_set(window); + self.conn.init_window(window, false); // TODO config.focus_follows_mouse + self.conn.init_frame(frame, false); // TODO: config.focus_follows_mouse + self.conn.set_window_border_width(window, 0); + self.conn.set_window_desktop(window, workspace); + + self.apply_layout(workspace, false); + self.focus(window); + + if let Some(ppid) = ppid { + if let Some(ppid_window) = self.pid_map.get(&ppid) { + let ppid_window = *ppid_window; + if let Some(ppid_client) = self.client(ppid_window) { + if ppid_client.is_producing() { + self.consume_client(window, ppid_window); + } + } + } + } + + if let Some(state) = preferred_state { + match state { + WindowState::DemandsAttention => self.handle_state_request( + window, + state, + ToggleAction::Add, + false, + ), + _ => {}, + } + } + + if sticky { + self.stick(window); + } + + if fullscreen { + self.fullscreen(window); + } + + let client = self.client_any(window).unwrap(); + let active_region = client.active_region(); + let current_pos = self.conn.get_pointer_position(); + + if let Some(warp_pos) = + active_region.quadrant_center_from_pos(current_pos) + { + self.conn.warp_pointer(warp_pos); + } + } + + fn remanage( + &mut self, + window: Window, + must_alter_workspace: bool, + ) { + if let Some(client) = self.client_any(window) { + if client.is_managed() { + return; + } + + info!("remanaging client with window {:#0x}", client.window()); + + let window = client.window(); + let active_workspace = self.active_workspace(); + let mut workspace = active_workspace; + + if must_alter_workspace { + let leader = client.leader(); + + if let Some(leader) = leader { + if let Some(leader) = self.client(leader) { + workspace = leader.workspace(); + } + } + + { + let workspace = self.workspace_mut(workspace); + + if !workspace.contains(window) { + workspace.add_client(window, &InsertPos::Back); + } + } + + let client = self.client_any_mut(window).unwrap(); + client.set_workspace(workspace); + } + + let client = self.client_any_mut(window).unwrap(); + client.set_managed(true); + + let client = self.client_any(window).unwrap(); + if client.is_sticky() { + let client = self.client_any_mut(window).unwrap(); + client.set_sticky(false); + + self.stick(window); + self.map_client(window); + } + } + } + + fn unmanage( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + info!("unmanaging client with window {:#0x}", client.window()); + + if client.is_sticky() { + self.unstick(window); + + let client = self.client_mut(window).unwrap(); + client.set_sticky(true); + } + + let client = self.client(window).unwrap(); + let window = client.window(); + let workspace = client.workspace(); + + self.unmap_client(window); + + { + let workspace = self.workspace_mut(workspace); + + if workspace.contains(window) { + workspace.remove_client(window); + } + } + + let client = self.client_mut(window).unwrap(); + client.set_managed(false); + } + } + + fn is_applyable( + client: &Client, + method: LayoutMethod, + ) -> bool { + method == LayoutMethod::Free + || !client.is_floating() + && !client.is_disowned() + && client.is_managed() + } + + fn is_free( + &self, + client: &Client, + ) -> bool { + (!client.is_fullscreen() || client.is_in_window()) + && (client.is_floating() + || client.is_disowned() + || !client.is_managed() + || self + .workspaces + .get(client.workspace()) + .unwrap() + .layout_config() + .method + == LayoutMethod::Free) + } + + fn is_focusable( + &self, + window: Window, + ) -> bool { + if let Some(client) = self.client(window) { + !client.is_disowned() && !client.is_iconified() + } else { + false + } + } + + fn remove_window( + &mut self, + window: Window, + ) { + let client = self.client(window); + + if client.is_none() { + return; + } + + let client = client.unwrap(); + let (window, frame) = client.windows(); + let parent = client.parent(); + let producer = client.producer(); + let workspace = client.workspace(); + + info!("removing client with window {:#0x}", window); + + if client.is_sticky() { + self.unstick(window); + } + + if Some(window) == self.jumped_from { + self.jumped_from = None; + } + + if producer.is_some() { + self.unconsume_client(window); + } + + if let Some(parent) = parent { + if let Some(parent) = self.client_mut(parent) { + parent.remove_child(window); + } + } + + self.workspaces.get_mut(workspace).map(|w| { + w.remove_client(window); + w.remove_icon(window); + }); + + self.stack.remove_window(window); + self.frame_map.remove(&frame); + self.window_map.remove(&window); + self.client_map.remove(&window); + self.pid_map.remove(&window); + self.fullscreen_regions.remove(&window); + + self.sync_focus(); + } + + fn refresh_client( + &self, + window: Window, + ) { + if let Some(client) = self.client(window) { + self.conn.set_window_background_color( + client.frame(), + if client.is_focused() { + if client.is_sticky() { + FOCUSED_STICKY_FRAME_COLOR + } else { + FOCUSED_FRAME_COLOR + } + } else if client.is_urgent() { + URGENT_FRAME_COLOR + } else if client.is_sticky() { + REGULAR_STICKY_FRAME_COLOR + } else { + REGULAR_FRAME_COLOR + }, + ); + } + } + + fn update_client_placement( + &mut self, + placement: &Placement, + method: LayoutMethod, + ) { + let client = self.client_mut(placement.window).unwrap(); + client.set_frame_extents(placement.extents); + + if let Some(region) = placement.region { + match method { + LayoutMethod::Free => client.set_free_region(&region), + LayoutMethod::Tile => client.set_tile_region(&region), + LayoutMethod::Tree => client.set_tree_region(&region), + }; + } + } + + fn place_client( + &self, + window: Window, + method: LayoutMethod, + ) { + let client = self.client(window).unwrap(); + + let (window, frame) = client.windows(); + let inner_region = client.inner_region(); + + self.conn.place_window(window, inner_region); + + self.conn.place_window(frame, match method { + LayoutMethod::Free => &client.free_region(), + LayoutMethod::Tile => &client.tile_region(), + LayoutMethod::Tree => &client.tree_region(), + }); + + self.refresh_client(window); + self.conn.update_window_offset(window, frame); + } + + fn map_client( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + if !client.is_mapped() { + let (window, frame) = client.windows(); + + info!("mapping client with window {:#0x}", window); + self.conn.map_window(window); + self.conn.map_window(frame); + self.refresh_client(window); + + let client = self.client_mut(window).unwrap(); + client.set_mapped(true); + } + } + } + + fn unmap_client( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + if client.is_mapped() { + let client = self.client_mut(window).unwrap(); + let (window, frame) = client.windows(); + + info!("unmapping client with window {:#0x}", window); + client.set_mapped(false); + client.expect_unmap(); + + self.conn.unmap_window(frame); + } + } + } + + fn consume_client( + &mut self, + consumer: Window, + producer: Window, + ) { + let consumer_window = consumer; + let producer_window = producer; + + let consumer = self.client_any(consumer_window); + let producer = self.client_any(producer_window); + + if consumer.is_none() || producer.is_none() { + return; + } + + info!( + "consuming client with window {:#0x} and producer window {:#0x}", + consumer_window, producer_window + ); + + let consumer = consumer.unwrap(); + let producer = producer.unwrap(); + let producer_workspace_index = producer.workspace(); + + if producer.is_iconified() || consumer.is_iconified() { + return; + } + + let consumer_len = producer.consumer_len(); + let consumer_workspace_index = consumer.workspace(); + let consumer = self.client_any_mut(consumer_window).unwrap(); + consumer.set_consuming(true); + consumer.set_producer(producer_window); + + if consumer_len == 0 { + let producer_workspace = + self.workspace_mut(producer_workspace_index); + + if producer_workspace_index == consumer_workspace_index { + producer_workspace + .replace_client(producer_window, consumer_window); + } else { + producer_workspace.remove_client(producer_window); + } + + self.apply_layout(consumer_workspace_index, true); + } + + let producer = self.client_any_mut(producer_window).unwrap(); + producer.add_consumer(consumer_window); + self.unmanage(producer_window); + } + + fn unconsume_client( + &mut self, + consumer: Window, + ) { + let consumer_window = consumer; + let consumer = self.client_any(consumer_window); + + if consumer.is_none() { + return; + } + + let consumer = consumer.unwrap(); + let producer_window = consumer.producer(); + let consumer_workspace = consumer.workspace(); + + if producer_window.is_none() { + return; + } + + let producer_window = producer_window.unwrap(); + + info!( + "unconsuming client with window {:#0x} and producer window {:#0x}", + consumer_window, producer_window + ); + + if self.client_map.contains_key(&producer_window) { + let producer = self.client_any_mut(producer_window).unwrap(); + producer.remove_consumer(consumer_window); + let consumer_len = producer.consumer_len(); + + if consumer_len == 0 { + producer.set_workspace(consumer_workspace); + + if let Some(workspace) = + self.workspaces.get_mut(consumer_workspace) + { + if workspace.contains(consumer_window) { + workspace + .replace_client(consumer_window, producer_window); + } else { + workspace.add_client(producer_window, &InsertPos::Back); + } + } + + self.remanage(producer_window, false); + + if consumer_workspace == self.active_workspace() { + self.map_client(producer_window); + } + + self.apply_layout(consumer_workspace, true); + } + } + + let consumer = self.client_any_mut(consumer_window).unwrap(); + consumer.unset_producer(); + consumer.set_consuming(false); + } + + pub fn kill_focus(&mut self) { + if let Some(focus) = self.focus { + self.kill_client(focus); + } + } + + pub fn kill_client( + &mut self, + mut window: Window, + ) { + if let Some(client) = self.client_any(window) { + window = client.window(); + + if client.is_invincible() { + return; + } + } else { + return; + } + + info!("killing client with window {:#0x}", window); + + self.conn.kill_window(window); + self.conn.flush(); + } + + pub fn cycle_focus( + &mut self, + dir: Direction, + ) { + let workspace = self.active_workspace(); + let windows = self + .workspaces + .get_mut(workspace) + .and_then(|ws| ws.cycle_focus(dir)); + + if let Some((_, window)) = windows { + self.focus(window); + self.sync_focus(); + } + } + + pub fn drag_focus( + &mut self, + dir: Direction, + ) { + if let Some(focus) = self.focus { + let workspace_index = self.active_workspace(); + self.workspaces + .get_mut(workspace_index) + .and_then(|ws| ws.drag_focus(dir)); + + self.apply_layout(workspace_index, false); + self.focus(focus); + } + } + + pub fn rotate_clients( + &mut self, + dir: Direction, + ) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + let next_window = workspace.next_client(dir.rev()); + + workspace.rotate_clients(dir); + self.apply_layout(workspace_index, false); + + if let Some(window) = next_window { + self.focus(window); + } + } + + pub fn center_client( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + if self.is_free(client) { + let screen = self.partitions.active_element().unwrap().screen(); + + let center = screen + .full_region() + .from_absolute_inner_center(&client.free_region().dim); + + let mut free_region = *client.free_region(); + free_region.pos = center.pos; + + info!("centering client with window {:#0x}", client.window()); + + self.conn.move_window(client.frame(), center.pos); + self.client_mut(window) + .unwrap() + .set_free_region(&free_region); + } + } + } + + pub fn center_focus(&mut self) { + if let Some(focus) = self.focus { + self.center_client(focus); + } + } + + pub fn apply_float_retain_region(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace(workspace_index); + let windows = workspace.iter().map(|&w| w).collect::<Vec<Window>>(); + + windows.iter().for_each(|&w| { + let client = self.client(w).unwrap(); + let active_region = *client.active_region(); + + let client = self.client_mut(w).unwrap(); + client.set_free_region(&active_region); + }); + + let workspace = self.workspace_mut(workspace_index); + workspace.set_layout(LayoutKind::Float); + self.apply_layout(workspace_index, false); + } + + pub fn move_focus_to_next_workspace(&mut self) { + if let Some(focus) = self.focus { + self.move_client_to_next_workspace(focus); + } + } + + pub fn move_focus_to_prev_workspace(&mut self) { + if let Some(focus) = self.focus { + self.move_client_to_prev_workspace(focus); + } + } + + pub fn move_focus_to_workspace( + &mut self, + index: Index, + ) { + if let Some(focus) = self.focus { + self.move_client_to_workspace(focus, index); + } + } + + pub fn move_client_to_next_workspace( + &mut self, + window: Window, + ) { + let index = self.active_workspace() + 1; + let index = index % self.workspaces.len(); + + self.move_client_to_workspace(window, index); + } + + pub fn move_client_to_prev_workspace( + &mut self, + window: Window, + ) { + let index = if self.active_workspace() == 0 { + self.workspaces.len() - 1 + } else { + self.active_workspace() - 1 + }; + + self.move_client_to_workspace(window, index); + } + + fn move_client_to_workspace( + &mut self, + window: Window, + index: Index, + ) { + if index == self.active_workspace() || index >= self.workspaces.len() { + return; + } + + let (window, current_index) = match self.client(window) { + Some(client) => { + if client.is_sticky() { + return; + } else { + (client.window(), client.workspace()) + } + }, + _ => return, + }; + + info!( + "moving client with window {:#0x} to workspace {}", + window, index + ); + + // add client to requested workspace + let workspace = self.workspace_mut(index); + workspace.add_client(window, &InsertPos::Back); + + // remove client from current_index workspace + let workspace = self.workspace_mut(current_index); + workspace.remove_client(window); + self.unmap_client(window); + self.apply_layout(current_index, true); + self.sync_focus(); + + let client = self.client_mut(window).unwrap(); + client.set_workspace(index); + } + + pub fn toggle_screen_struts(&mut self) { + let screen = self.active_screen_mut(); + + if screen.showing_struts() { + let struts = screen.hide_and_yield_struts(); + + for strut in struts { + self.conn.unmap_window(strut); + } + } else { + let struts = screen.show_and_yield_struts(); + + for strut in struts { + self.conn.map_window(strut); + } + } + + // TODO: apply layout to workspace active on screen + let workspace_index = self.active_workspace(); + self.apply_layout(workspace_index, false); + } + + pub fn toggle_workspace(&mut self) { + self.activate_workspace(self.prev_workspace); + } + + pub fn activate_next_workspace(&mut self) { + let index = self.active_workspace() + 1; + let index = index % self.workspaces.len(); + + self.activate_workspace(index); + } + + pub fn activate_prev_workspace(&mut self) { + let index = if self.active_workspace() == 0 { + self.workspaces.len() - 1 + } else { + self.active_workspace() - 1 + }; + + self.activate_workspace(index); + } + + pub fn activate_workspace( + &mut self, + index: Index, + ) { + if index == self.active_workspace() || index >= self.workspaces.len() { + return; + } + + info!("activating workspace {}", index); + + self.stop_moving(); + self.stop_resizing(); + + self.prev_workspace = self.workspaces.active_index(); + let mut clients_to_map = Vec::with_capacity(20); + let mut windows_to_unmap = Vec::with_capacity(20); + + let workspace_index = self.active_workspace(); + let workspace = self.workspace(workspace_index); + + workspace + .iter() + .map(|&window| self.client(window).unwrap()) + .for_each(|client| { + if client.is_mapped() && !client.is_sticky() { + windows_to_unmap.push(client.window()); + } + }); + + self.workspaces.activate_for(&Selector::AtIndex(index)); + + let workspace_index = self.active_workspace(); + let workspace = self.workspace(workspace_index); + + workspace + .iter() + .map(|&window| self.client(window).unwrap()) + .for_each(|client| { + if !client.is_mapped() { + clients_to_map.push(client.window()); + } + }); + + clients_to_map + .iter() + .for_each(|&window| self.map_client(window)); + + windows_to_unmap + .iter() + .for_each(|&window| self.unmap_client(window)); + + let mut sticky_windows = Vec::with_capacity(self.sticky_clients.len()); + + for &window in self.sticky_clients.iter() { + sticky_windows.push(window); + } + + for window in sticky_windows { + if let Some(client) = self.client_mut(window) { + client.set_workspace(index); + } + } + + self.apply_layout(self.active_workspace(), true); + self.sync_focus(); + self.conn.set_current_desktop(index); + } + + pub fn change_gap_size( + &mut self, + change: Change, + ) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.change_gap_size(change, 5); + self.apply_layout(workspace_index, true); + } + + pub fn reset_layout(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.reset_layout(); + self.apply_layout(workspace_index, true); + } + + pub fn reset_gap_size(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.reset_gap_size(); + self.apply_layout(workspace_index, true); + } + + pub fn change_main_count( + &mut self, + change: Change, + ) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.change_main_count(change); + self.apply_layout(workspace_index, true); + } + + pub fn change_main_factor( + &mut self, + change: Change, + ) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.change_main_factor(change, 0.05f32); + self.apply_layout(workspace_index, true); + } + + pub fn change_margin( + &mut self, + edge: Edge, + change: Change, + ) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.change_margin(edge, change, 5); + self.apply_layout(workspace_index, true); + } + + pub fn reset_margin(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.reset_margin(); + self.apply_layout(workspace_index, true); + } + + pub fn set_layout( + &mut self, + layout: LayoutKind, + ) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + info!( + "activating layout {:?} on workspace {}", + layout, workspace_index + ); + + workspace.set_layout(layout); + self.apply_layout(workspace_index, true); + } + + pub fn toggle_layout(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + workspace.toggle_layout(); + self.apply_layout(workspace_index, true); + } + + pub fn toggle_in_window_focus(&mut self) { + if let Some(focus) = self.focus { + if let Some(client) = self.client_mut(focus) { + let is_in_window = client.is_in_window(); + client.set_in_window(!is_in_window); + + if is_in_window { + self.fullscreen(focus); + } else { + self.unfullscreen(focus); + } + } + } + } + + pub fn toggle_invincible_focus(&mut self) { + if let Some(focus) = self.focus { + if let Some(client) = self.client_mut(focus) { + let is_invincible = client.is_invincible(); + client.set_invincible(!is_invincible); + } + } + } + + pub fn toggle_producing_focus(&mut self) { + if let Some(focus) = self.focus { + if let Some(client) = self.client_mut(focus) { + let is_producing = client.is_producing(); + client.set_producing(!is_producing); + } + } + } + + pub fn toggle_float_focus(&mut self) { + if let Some(focus) = self.focus { + self.toggle_float_client(focus); + } + } + + pub fn toggle_float_client( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + let active_workspace_index = client.workspace(); + let workspace_index = client.workspace(); + + let client = self.client_mut(window).unwrap(); + let must_float = !client.is_floating(); + + info!( + "{}floating client with window {:#0x}", + if must_float { "" } else { "un" }, + client.window() + ); + + client.set_floating(must_float); + + if active_workspace_index == workspace_index { + self.apply_layout(workspace_index, true); + } + } + } + + fn active_partition(&self) -> usize { + self.partitions.active_index() + } + + fn active_screen(&self) -> &Screen { + self.partitions.active_element().unwrap().screen() + } + + fn active_screen_mut(&mut self) -> &mut Screen { + self.partitions.active_element_mut().unwrap().screen_mut() + } + + pub fn active_workspace(&self) -> usize { + self.workspaces.active_index() + } + + fn window_workspace( + &self, + window: Window, + ) -> Option<usize> { + self.client(window).map(|c| c.workspace()) + } + + fn focused_client(&self) -> Option<&Client> { + self.focus + .or_else(|| { + self.workspace(self.active_workspace()).focused_client() + }) + .and_then(|id| self.client_map.get(&id)) + } + + fn focused_client_mut(&mut self) -> Option<&mut Client> { + self.focus + .or_else(|| { + self.workspace(self.active_workspace()).focused_client() + }) + .and_then(move |id| self.client_map.get_mut(&id)) + } + + fn focus( + &mut self, + window: Window, + ) { + if let Some(frame) = self.frame(window) { + if let Some(window) = self.window(window) { + if Some(window) == self.focus { + return; + } + + info!("focusing client with window {:#0x}", window); + + let active_workspace_index = self.active_workspace(); + let client = self.client(window); + + if !self.is_focusable(window) { + return; + } + + let client = client.unwrap(); + let client_workspace_index = client.workspace(); + + if client_workspace_index != active_workspace_index { + self.activate_workspace(client_workspace_index); + } + + if let Some(prev_focus) = self.focus { + self.unfocus(prev_focus); + } + + self.conn.ungrab_buttons(frame); + + if let Some(client) = self.client_mut(window) { + client.set_focused(true); + client.set_urgent(false); + } + + let workspace = self.workspace_mut(client_workspace_index); + workspace.focus_client(window); + + if workspace.layout_config().persistent { + self.apply_layout(client_workspace_index, false); + } + + if self.conn.get_focused_window() != window { + self.conn.focus_window(window); + } + + self.focus = Some(window); + self.refresh_client(window); + self.apply_stack(client_workspace_index); + } + } + } + + fn unfocus( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + let (window, frame) = client.windows(); + let current_pos = self.conn.get_pointer_position(); + + self.conn + .set_window_background_color(frame, REGULAR_FRAME_COLOR); + self.conn.regrab_buttons(frame); + + info!("unfocusing client with window {:#0x}", window); + + let client = self.client_mut(window).unwrap(); + client.set_warp_pos(current_pos); + client.set_focused(false); + self.refresh_client(window); + } + } + + fn sync_focus(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace_mut(workspace_index); + + if !workspace.is_empty() { + if let Some(ws_focus) = workspace.focused_client() { + if Some(ws_focus) != self.focus { + self.focus(ws_focus); + } + } + } else { + self.conn.unfocus(); + self.focus = None; + } + } + + pub fn toggle_fullscreen_focus(&mut self) { + if let Some(focus) = self.focus { + self.toggle_fullscreen_client(focus); + } + } + + pub fn toggle_fullscreen_client( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + if client.is_fullscreen() { + self.unfullscreen(window); + } else { + self.fullscreen(window); + } + } + } + + pub fn jump_client( + &mut self, + criterium: &JumpCriterium, + ) { + let mut window = match criterium { + JumpCriterium::OnWorkspaceBySelector(index, sel) => { + let index = *index; + + if index >= self.workspaces.len() { + return; + } + + let workspace = self.workspace(index); + let window = workspace.get_client_for(sel); + + if window.is_none() { + return; + } + + *window.unwrap() + }, + JumpCriterium::ByName(name, match_method) => { + let mut clients = self + .client_map + .iter() + .filter(|&(_, client)| { + client.is_managed() + && match match_method { + MatchMethod::Equals => client.name() == *name, + MatchMethod::Contains => { + client.name().contains(name) + }, + } + }) + .map(|(_, client)| client) + .collect::<Vec<&Client>>(); + + clients.sort_by_key(|&c| c.last_focused()); + + if let Some(client) = clients.last() { + client.window() + } else { + return; + } + }, + JumpCriterium::ByClass(class, match_method) => { + let mut clients = self + .client_map + .iter() + .filter(|&(_, client)| { + client.is_managed() + && match match_method { + MatchMethod::Equals => client.class() == *class, + MatchMethod::Contains => { + client.class().contains(class) + }, + } + }) + .map(|(_, client)| client) + .collect::<Vec<&Client>>(); + + clients.sort_by_key(|&c| c.last_focused()); + + if let Some(client) = clients.last() { + client.window() + } else { + return; + } + }, + JumpCriterium::ByInstance(instance, match_method) => { + let mut clients = self + .client_map + .iter() + .filter(|&(_, client)| { + client.is_managed() + && match match_method { + MatchMethod::Equals => { + client.instance() == *instance + }, + MatchMethod::Contains => { + client.instance().contains(instance) + }, + } + }) + .map(|(_, client)| client) + .collect::<Vec<&Client>>(); + + clients.sort_by_key(|&c| c.last_focused()); + + if let Some(client) = clients.last() { + client.window() + } else { + return; + } + }, + JumpCriterium::ForCond(cond) => { + let mut clients = self + .client_map + .iter() + .filter(|&(_, client)| client.is_managed() && cond(client)) + .map(|(_, client)| client) + .collect::<Vec<&Client>>(); + + clients.sort_by_key(|&c| c.last_focused()); + + if let Some(client) = clients.last() { + client.window() + } else { + return; + } + }, + }; + + if let Some(focus) = self.focus { + if window == focus { + let jumped_from = self.jumped_from; + + if jumped_from.is_none() || jumped_from == Some(focus) { + return; + } + + if let Some(jumped_from) = jumped_from { + window = jumped_from; + } + } + + self.jumped_from = Some(focus); + } + + info!("jumping to client with window {:#0x}", window); + self.focus(window); + } + + fn fullscreen( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + let free_region = *client.free_region(); + let window = client.window(); + + info!("enabling fullscreen for client with window {:#0x}", window); + + self.conn + .set_window_state(window, WindowState::Fullscreen, true); + self.fullscreen_regions.insert(window, free_region); + + let client = self.client_mut(window).unwrap(); + client.set_fullscreen(true); + + let workspace = client.workspace(); + if workspace == self.active_workspace() { + self.apply_layout(workspace, true); + } + } + } + + fn unfullscreen( + &mut self, + window: Window, + ) { + if let Some(client) = self.client(window) { + let window = client.window(); + let free_region = + self.fullscreen_regions.get(&window).map(|&region| region); + + info!("disabling fullscreen for client with window {:#0x}", window); + + self.conn + .set_window_state(window, WindowState::Fullscreen, false); + + let client = self.client_mut(window).unwrap(); + client.set_fullscreen(false); + + if let Some(free_region) = free_region { + client.set_free_region(&free_region); + } + + let workspace = client.workspace(); + + if workspace == self.active_workspace() { + self.apply_layout(workspace, true); + } + + self.fullscreen_regions.remove(&window); + } + } + + pub fn toggle_stick_focus(&mut self) { + if let Some(focus) = self.focus { + if let Some(client) = self.client(focus) { + if client.is_sticky() { + self.unstick(focus); + } else { + self.stick(focus); + } + } + } + } + + fn stick( + &mut self, + window: Window, + ) { + let client = self.client_mut(window); + + if client.is_none() { + return; + } + + let client = client.unwrap(); + let window = client.window(); + let workspace_index = client.workspace(); + + info!("enabling sticky for client with window {:#0x}", window); + + client.set_sticky(true); + self.conn + .set_window_state(window, WindowState::Sticky, true); + self.sticky_clients.push(window); + + for workspace in self.workspaces.iter_mut() { + if workspace.number() as Index != workspace_index { + workspace.add_client(window, &InsertPos::Back); + } + } + + self.refresh_client(window); + } + + fn unstick( + &mut self, + window: Window, + ) { + let client = self.client_mut(window); + + if client.is_none() { + return; + } + + let client = client.unwrap(); + let window = client.window(); + let workspace_index = client.workspace(); + + info!("disabling sticky for client with window {:#0x}", window); + + client.set_sticky(false); + self.conn + .set_window_state(window, WindowState::Sticky, false); + + if let Some(index) = + self.sticky_clients.iter().position(|&s| s == window) + { + self.sticky_clients.remove(index); + } + + for workspace in self.workspaces.iter_mut() { + if workspace.number() as Index != workspace_index { + workspace.remove_client(window); + workspace.remove_icon(window); + } + } + + self.refresh_client(window); + } + + pub fn iconify_focus(&mut self) { + if let Some(focus) = self.focus { + if let Some(client) = self.client(focus) { + if !client.is_iconified() { + self.iconify(focus); + } + } + } + } + + pub fn pop_deiconify(&mut self) { + let workspace_index = self.active_workspace(); + let workspace = self.workspace(workspace_index); + + if let Some(icon) = workspace.focused_icon() { + self.deiconify(icon); + } + } + + pub fn deiconify_all( + &mut self, + index: Index, + ) { + if index >= self.workspaces.len() { + warn!("attempting to deicony_all from nonexistent workspace"); + return; + } + + let mut workspace = self.workspace(index); + + while let Some(icon) = workspace.focused_icon() { + self.deiconify(icon); + workspace = self.workspace(index); + } + } + + fn iconify( + &mut self, + window: Window, + ) { + let client = self.client(window); + + if client.is_none() { + return; + } + + let client = self.client_mut(window).unwrap(); + let window = client.window(); + let workspace_index = client.workspace(); + + info!("iconifying client with window {:#0x}", window); + + client.set_iconified(true); + self.unmap_client(window); + self.conn + .set_icccm_window_state(window, IcccmWindowState::Iconic); + + let workspace = self.workspace_mut(workspace_index); + workspace.client_to_icon(window); + self.sync_focus(); + self.apply_layout(workspace_index, true); + } + + fn deiconify( + &mut self, + window: Window, + ) { + let client = self.client(window); + + if client.is_none() { + return; + } + + let client = self.client_mut(window).unwrap(); + let window = client.window(); + let workspace_index = client.workspace(); + + info!("deiconifying client with window {:#0x}", window); + + client.set_iconified(false); + self.map_client(window); + self.conn + .set_icccm_window_state(window, IcccmWindowState::Normal); + + let workspace = self.workspace_mut(workspace_index); + workspace.icon_to_client(window); + self.sync_focus(); + self.apply_layout(workspace_index, true); + } + + pub fn snap_focus( + &mut self, + edge: Edge, + ) { + if let Some(focus) = self.focus { + self.snap_client(focus, edge); + } + } + + fn snap_client( + &mut self, + window: Window, + edge: Edge, + ) { + if let Some(client) = self.client(window) { + if self.is_free(client) { + let screen = self.active_screen(); + let placeable_region = screen.placeable_region(); + let mut region = *client.free_region(); + let window = client.window(); + + info!( + "snapping client with window {:#0x} to edge {:?}", + window, edge + ); + + match edge { + Edge::Left => region.pos.x = placeable_region.pos.x, + Edge::Right => { + let x = placeable_region.dim.w as i32 + + placeable_region.pos.x; + + region.pos.x = std::cmp::max(0, x - region.dim.w as i32) + }, + Edge::Top => region.pos.y = placeable_region.pos.y, + Edge::Bottom => { + let y = placeable_region.dim.h as i32 + + placeable_region.pos.y; + + region.pos.y = std::cmp::max(0, y - region.dim.h as i32) + }, + } + + let placement = Placement { + window, + region: Some(region), + extents: *client.frame_extents(), + }; + + self.update_client_placement(&placement, LayoutMethod::Free); + self.place_client(window, LayoutMethod::Free); + } + } + } + + pub fn nudge_focus( + &mut self, + edge: Edge, + step: i32, + ) { + if let Some(focus) = self.focus { + self.nudge_client(focus, edge, step); + } + } + + fn nudge_client( + &mut self, + window: Window, + edge: Edge, + step: i32, + ) { + if let Some(client) = self.client(window) { + if self.is_free(client) { + let mut region = *client.free_region(); + let window = client.window(); + + info!( + "nudging client with window {:#0x} at the {:?} by {}", + window, edge, step + ); + + match edge { + Edge::Left => region.pos.x -= step, + Edge::Right => region.pos.x += step, + Edge::Top => region.pos.y -= step, + Edge::Bottom => region.pos.y += step, + } + + let placement = Placement { + window, + region: Some(region), + extents: *client.frame_extents(), + }; + + self.update_client_placement(&placement, LayoutMethod::Free); + self.place_client(window, LayoutMethod::Free); + } + } + } + + pub fn grow_ratio_client( + &mut self, + window: Window, + step: i32, + ) { + if let Some(client) = self.client(window) { + if self.is_free(client) { + let frame_extents = client.frame_extents_unchecked(); + let original_region = *client.free_region(); + let region = original_region; + let window = client.window(); + let (width, height) = region.dim.values(); + + let fraction = width as f64 / (width + height) as f64; + let width_inc = fraction * step as f64; + let height_inc = step as f64 - width_inc; + let width_inc = width_inc.round() as i32; + let height_inc = height_inc.round() as i32; + + let mut region = region.without_extents(&frame_extents); + + if (width_inc.is_negative() + && -width_inc >= region.dim.w as i32) + || (height_inc.is_negative() + && -height_inc >= region.dim.h as i32) + || (region.dim.w as i32 + width_inc + <= MIN_WINDOW_DIM.w as i32) + || (region.dim.h as i32 + height_inc + <= MIN_WINDOW_DIM.h as i32) + { + return; + } + + info!( + "{} client with window {:#0x} by {}", + if step >= 0 { "growing" } else { "shrinking" }, + window, + step.abs() + ); + + region.dim.w = (region.dim.w as i32 + width_inc) as u32; + region.dim.h = (region.dim.h as i32 + height_inc) as u32; + + let mut region = region.with_extents(&frame_extents); + let dx = region.dim.w as i32 - original_region.dim.w as i32; + let dy = region.dim.h as i32 - original_region.dim.h as i32; + + let width_shift = (dx as f64 / 2f64) as i32; + let height_shift = (dy as f64 / 2f64) as i32; + + region.pos.x -= width_shift; + region.pos.y -= height_shift; + + let placement = Placement { + window, + region: Some(region), + extents: *client.frame_extents(), + }; + + self.update_client_placement(&placement, LayoutMethod::Free); + self.place_client(placement.window, LayoutMethod::Free); + } + } + } + + pub fn stretch_focus( + &mut self, + edge: Edge, + step: i32, + ) { + if let Some(focus) = self.focus { + self.stretch_client(focus, edge, step); + } + } + + fn stretch_client( + &mut self, + window: Window, + edge: Edge, + step: i32, + ) { + if let Some(client) = self.client(window) { + if self.is_free(client) { + let frame_extents = client.frame_extents_unchecked(); + let window = client.window(); + let mut region = + (*client.free_region()).without_extents(&frame_extents); + + info!( + "stretching client with window {:#0x} at the {:?} by {}", + window, edge, step + ); + + match edge { + Edge::Left => { + if step.is_negative() && -step >= region.dim.w as i32 { + return; + } + + if region.dim.w as i32 + step <= MIN_WINDOW_DIM.w as i32 + { + region.pos.x -= + MIN_WINDOW_DIM.w as i32 - region.dim.w as i32; + region.dim.w = MIN_WINDOW_DIM.w; + } else { + region.pos.x -= step; + region.dim.w = (region.dim.w as i32 + step) as u32; + } + }, + Edge::Right => { + if step.is_negative() && -step >= region.dim.w as i32 { + return; + } + + if region.dim.w as i32 + step <= MIN_WINDOW_DIM.w as i32 + { + region.dim.w = MIN_WINDOW_DIM.w; + } else { + region.dim.w = (region.dim.w as i32 + step) as u32; + } + }, + Edge::Top => { + if step.is_negative() && -step >= region.dim.h as i32 { + return; + } + + if region.dim.h as i32 + step <= MIN_WINDOW_DIM.h as i32 + { + region.pos.y -= + MIN_WINDOW_DIM.h as i32 - region.dim.h as i32; + region.dim.h = MIN_WINDOW_DIM.h; + } else { + region.pos.y -= step; + region.dim.h = (region.dim.h as i32 + step) as u32; + } + }, + Edge::Bottom => { + if step.is_negative() && -step >= region.dim.h as i32 { + return; + } + + if region.dim.h as i32 + step <= MIN_WINDOW_DIM.h as i32 + { + region.dim.h = MIN_WINDOW_DIM.h; + } else { + region.dim.h = (region.dim.h as i32 + step) as u32; + } + }, + } + + let region = region.with_extents(&frame_extents); + + let placement = Placement { + window, + region: Some(region), + extents: *client.frame_extents(), + }; + + self.update_client_placement(&placement, LayoutMethod::Free); + + self.place_client(window, LayoutMethod::Free); + } + } + } + + pub fn start_moving( + &mut self, + window: Window, + ) { + if !self.move_buffer.is_occupied() && !self.resize_buffer.is_occupied() + { + if let Some(client) = self.client(window) { + let current_pos = self.conn.get_pointer_position(); + let client_region = *client.free_region(); + + self.move_buffer.set( + window, + Grip::Corner(Corner::TopLeft), + current_pos, + client_region, + ); + + self.conn.confine_pointer(self.move_buffer.handle()); + } + } + } + + pub fn stop_moving(&mut self) { + if self.move_buffer.is_occupied() { + self.conn.release_pointer(); + self.move_buffer.unset(); + } + } + + pub fn handle_move( + &mut self, + pos: &Pos, + ) { + if let Some(client) = self.client(self.move_buffer.window().unwrap()) { + if self.is_free(client) { + if let Some(grip_pos) = self.move_buffer.grip_pos() { + if let Some(window_region) = + self.move_buffer.window_region() + { + let placement = Placement { + window: client.window(), + region: Some(Region { + pos: window_region.pos + grip_pos.dist(*pos), + dim: client.free_region().dim, + }), + extents: *client.frame_extents(), + }; + + self.update_client_placement( + &placement, + LayoutMethod::Free, + ); + + self.place_client(placement.window, LayoutMethod::Free); + } + } + } + } + } + + pub fn start_resizing( + &mut self, + window: Window, + ) { + if !self.move_buffer.is_occupied() && !self.resize_buffer.is_occupied() + { + if let Some(client) = self.client(window) { + let current_pos = self.conn.get_pointer_position(); + let client_region = *client.free_region(); + let corner = client.free_region().nearest_corner(current_pos); + + self.resize_buffer.set( + window, + Grip::Corner(corner), + current_pos, + client_region, + ); + + self.conn.confine_pointer(self.resize_buffer.handle()); + } + } + } + + pub fn stop_resizing(&mut self) { + if self.resize_buffer.is_occupied() { + self.conn.release_pointer(); + self.resize_buffer.unset(); + } + } + + pub fn handle_resize( + &mut self, + pos: &Pos, + ) { + let window = self.resize_buffer.window().unwrap(); + + if let Some(client) = self.client(window) { + if self.is_free(client) { + let grip_pos = self.resize_buffer.grip_pos().unwrap(); + let window_region = self.resize_buffer.window_region().unwrap(); + let grip = self.resize_buffer.grip().unwrap(); + + let current_pos = *pos; + let previous_region = *client.previous_region(); + let frame_extents = client.frame_extents_unchecked(); + let (pos, mut dim) = client + .free_region() + .without_extents(&frame_extents) + .values(); + + let top_grip = grip.is_top_grip(); + let left_grip = grip.is_left_grip(); + let delta = grip_pos.dist(current_pos); + + let dest_w = if left_grip { + window_region.dim.w as i32 - delta.dx + } else { + window_region.dim.w as i32 + delta.dx + }; + + let dest_h = if top_grip { + window_region.dim.h as i32 - delta.dy + } else { + window_region.dim.h as i32 + delta.dy + }; + + dim.w = std::cmp::max(0, dest_w) as u32; + dim.h = std::cmp::max(0, dest_h) as u32; + + if let Some(size_hints) = client.size_hints() { + size_hints.apply(&mut dim); + } + + let mut region = (Region { + pos, + dim, + }) + .with_extents(&frame_extents); + + if top_grip { + region.pos.y = window_region.pos.y + + (window_region.dim.h as i32 - region.dim.h as i32); + } + + if left_grip { + region.pos.x = window_region.pos.x + + (window_region.dim.w as i32 - region.dim.w as i32); + } + + if region == previous_region { + return; + } + + let placement = Placement { + window: client.window(), + region: Some(region), + extents: Some(frame_extents), + }; + + self.update_client_placement(&placement, LayoutMethod::Free); + + self.place_client(placement.window, LayoutMethod::Free); + } + } + } + + fn handle_mouse( + &mut self, + event: MouseEvent, + mouse_bindings: &mut MouseBindings, + ) { + let mut window = event.window; + let subwindow = event.subwindow; + + match event.kind { + MouseEventKind::Release => { + if self.move_buffer.is_occupied() { + self.stop_moving(); + return; + } else if self.resize_buffer.is_occupied() { + self.stop_resizing(); + return; + } + }, + MouseEventKind::Motion => { + if self.move_buffer.is_occupied() { + self.handle_move(&event.root_rpos); + } else if self.resize_buffer.is_occupied() { + self.handle_resize(&event.root_rpos); + } + + return; + }, + _ => {}, + } + + { + // handle global mouse bindings + let binding = mouse_bindings.get_mut(&( + MouseEventKey { + kind: event.kind, + target: EventTarget::Global, + }, + event.shortcut.clone(), + )); + + if let Some((action, moves_focus)) = binding { + action(self, &event, None); + + if *moves_focus { + // TODO: config.focus_follows_mouse + if let Some(focus) = self.focus { + if window != focus { + self.focus(window); + } + } + } + + return; + } + } + + if event.on_root { + if let Some(subwindow) = subwindow { + window = subwindow; + } else { + // handle root-targeted mouse bindings + let binding = mouse_bindings.get_mut(&( + MouseEventKey { + kind: event.kind, + target: EventTarget::Root, + }, + event.shortcut.clone(), + )); + + if let Some((action, _)) = binding { + action(self, &event, None); + } + + return; + } + } + + { + // handle client-targeted mouse bindings + let binding = mouse_bindings.get_mut(&( + MouseEventKey { + kind: event.kind, + target: EventTarget::Client, + }, + event.shortcut.clone(), + )); + + if let Some(window) = self.window(window) { + if let Some((action, moves_focus)) = binding { + action(self, &event, Some(window)); + + if *moves_focus { + // TODO: config.focus_follows_mouse + if let Some(focus) = self.focus { + if window != focus { + self.focus(window); + } + } + } + } else { + // TODO: config.focus_follows_mouse + if event.kind != MouseEventKind::Release { + if let Some(focus) = self.focus { + if window != focus { + self.focus(window); + } + } + } + } + } + } + } + + fn handle_key( + &mut self, + key_code: KeyCode, + key_bindings: &mut KeyBindings, + ) { + if let Some(action) = key_bindings.get_mut(&key_code.clone()) { + debug!("processing key binding: {:?}", key_code); + action(self); + } + } + + fn handle_map_request( + &mut self, + window: Window, + ignore: bool, + ) { + debug!("MAP_REQUEST for window {:#0x}", window); + + if ignore { + if let Some(struts) = self.conn.get_window_strut(window) { + let screen = self.active_screen_mut(); + screen.add_struts(struts); + + if !screen.showing_struts() { + self.conn.unmap_window(window); + } else { + screen.compute_placeable_region(); + self.apply_layout(self.active_workspace(), true); + } + } + + let preferred_state = self.conn.get_window_preferred_state(window); + let preferred_type = self.conn.get_window_preferred_type(window); + let geometry = self.conn.get_window_geometry(window); + + match (preferred_state, preferred_type) { + (Some(WindowState::Below), _) => Some(StackLayer::Below), + (_, WindowType::Desktop) => Some(StackLayer::Desktop), + (_, WindowType::Dock) => { + if let Ok(geometry) = geometry { + let screen = self.active_screen_mut(); + + if !screen.contains_window(window) { + let strut = match ( + (geometry.pos.x, geometry.pos.y), + (geometry.dim.w, geometry.dim.h), + ) { + ((0, 0), (w, h)) + if w == screen.full_region().dim.w => + { + Some((Edge::Top, h)) + }, + ((0, 0), (w, h)) + if h == screen.full_region().dim.h => + { + Some((Edge::Left, w)) + }, + ((0, 0), (w, h)) if w > h => { + Some((Edge::Top, h)) + }, + ((0, 0), (w, h)) if w < h => { + Some((Edge::Left, w)) + }, + ((_, y), (_, h)) + if y == screen.full_region().dim.h + as i32 + - h as i32 => + { + Some((Edge::Bottom, h)) + }, + ((x, _), (w, _)) + if x == screen.full_region().dim.w + as i32 + - w as i32 => + { + Some((Edge::Right, w)) + }, + _ => None, + }; + + if let Some((edge, width)) = strut { + screen.add_strut(edge, window, width); + + if !screen.showing_struts() { + self.conn.unmap_window(window); + } else { + screen.compute_placeable_region(); + self.apply_layout( + self.active_workspace(), + true, + ); + } + } + } + } + + Some(StackLayer::Dock) + }, + (_, WindowType::Notification) => Some(StackLayer::Notification), + (Some(WindowState::Above), _) => Some(StackLayer::Above), + (..) => None, + } + .map(|layer| self.stack.add_window(window, layer)); + + self.apply_stack(self.active_workspace()); + } + + if self.client_map.contains_key(&window) { + return; + } + + self.manage(window, ignore); + } + + fn handle_map( + &mut self, + window: Window, + _ignore: bool, + ) { + debug!("MAP for window {:#0x}", window); + } + + fn handle_enter( + &mut self, + window: Window, + _root_rpos: Pos, + _window_rpos: Pos, + ) { + debug!("ENTER for window {:#0x}", window); + + if let Some(window) = self.window(window) { + if let Some(focus) = self.focus { + if focus != window { + self.unfocus(focus); + } + } + + self.focus(window); + } + } + + fn handle_leave( + &mut self, + window: Window, + _root_rpos: Pos, + _window_rpos: Pos, + ) { + debug!("LEAVE for window {:#0x}", window); + self.unfocus(window); + } + + fn handle_destroy( + &mut self, + window: Window, + ) { + debug!("DESTROY for window {:#0x}", window); + + let active_workspace = self.active_workspace(); + let screen = self.active_screen_mut(); + + if screen.has_strut_window(window) { + screen.remove_window_strut(window); + screen.compute_placeable_region(); + self.apply_layout(active_workspace, true); + } + + if let Some(index) = + self.unmanaged_windows.iter().position(|&s| s == window) + { + self.unmanaged_windows.remove(index); + } + + let client = self.client_any(window); + + if client.is_none() { + return; + } + + let client = client.unwrap(); + let is_managed = client.is_managed(); + let (window, frame) = client.windows(); + + let client = self.client_any_mut(window).unwrap(); + if client.consume_unmap_if_expecting() { + return; + } + + if !is_managed { + self.remanage(window, true); + } + + let client = self.client_any(window).unwrap(); + let workspace = client.workspace(); + + if let Ok(geometry) = self.conn.get_window_geometry(frame) { + self.conn.unparent_window(window, geometry.pos); + } + + self.conn.cleanup_window(window); + self.conn.destroy_window(frame); + + self.remove_window(window); + + if workspace == active_workspace { + self.apply_layout(workspace, false); + } + } + + fn handle_expose( + &mut self, + _window: Window, + ) { + } + + fn handle_unmap( + &mut self, + window: Window, + _ignore: bool, + ) { + debug!("UNMAP for window {:#0x}", window); + + if self.unmanaged_windows.contains(&window) { + return; + } + + self.handle_destroy(window); + } + + fn handle_configure( + &mut self, + window: Window, + _region: Region, + on_root: bool, + ) { + if on_root { + debug!("CONFIGURE for window {:#0x}", window); + self.acquire_partitions(); + } + } + + fn handle_state_request( + &mut self, + window: Window, + state: WindowState, + action: ToggleAction, + on_root: bool, + ) { + debug!( + "STATE_REQUEST for window {:#0x}, with state {:?} and action {:?}", + window, state, action + ); + + let client = self.client_any(window); + + if client.is_none() { + return; + } + + let client = client.unwrap(); + + match action { + ToggleAction::Add => match state { + WindowState::Fullscreen => self.fullscreen(window), + WindowState::Sticky => self.stick(window), + WindowState::DemandsAttention => { + let hints = Hints { + urgent: true, + input: None, + initial_state: None, + group: None, + }; + + self.conn.set_icccm_window_hints(window, hints); + + if let Some(client) = self.client_any_mut(window) { + client.set_urgent(true); + self.refresh_client(window); + } + }, + _ => {}, + }, + ToggleAction::Remove => match state { + WindowState::Fullscreen => self.unfullscreen(window), + WindowState::Sticky => self.unstick(window), + WindowState::DemandsAttention => { + let hints = Hints { + urgent: false, + input: None, + initial_state: None, + group: None, + }; + + self.conn.set_icccm_window_hints(window, hints); + + if let Some(client) = self.client_any_mut(window) { + client.set_urgent(false); + self.refresh_client(window); + } + }, + _ => {}, + }, + ToggleAction::Toggle => { + let is_fullscreen = client.is_fullscreen(); + + self.handle_state_request( + window, + state, + if is_fullscreen { + ToggleAction::Remove + } else { + ToggleAction::Add + }, + on_root, + ); + }, + } + } + + fn handle_focus_request( + &mut self, + window: Window, + on_root: bool, + ) { + debug!("FOCUS_REQUEST for window {:#0x}", window); + + if !on_root { + self.focus(window); + } + } + + fn handle_close_request( + &mut self, + window: Window, + on_root: bool, + ) { + debug!("CLOSE_REQUEST for window {:#0x}", window); + + if !on_root { + self.conn.kill_window(window); + } + } + + fn handle_workspace_request( + &mut self, + _window: Option<Window>, + index: usize, + on_root: bool, + ) { + debug!("WORKSPACE_REQUEST for workspace {}", index); + + if on_root { + self.activate_workspace(index); + } + } + + fn handle_placement_request( + &mut self, + window: Window, + pos: Option<Pos>, + dim: Option<Dim>, + _on_root: bool, + ) { + debug!( + "PLACEMENT_REQUEST for window {:#0x} with pos {:?} and dim {:?}", + window, pos, dim + ); + + if pos.is_some() || dim.is_some() { + let event_window = window; + + if let Some(client) = self.client(window) { + if self.is_free(client) { + let window = client.window(); + let frame_extents = client.frame_extents_unchecked(); + + let region = if event_window == window { + Some(Region { + pos: if let Some(pos) = pos { + Pos { + x: pos.x - frame_extents.left as i32, + y: pos.y - frame_extents.top as i32, + } + } else { + client.free_region().pos + }, + dim: if let Some(dim) = dim { + Dim { + w: dim.w + + frame_extents.left + + frame_extents.right, + h: dim.h + + frame_extents.top + + frame_extents.bottom, + } + } else { + client.free_region().dim + }, + }) + } else { + Some(Region { + pos: if let Some(pos) = pos { + pos + } else { + client.free_region().pos + }, + dim: if let Some(dim) = dim { + dim + } else { + client.free_region().dim + }, + }) + } + .map(|region| { + if client.size_hints().is_some() { + region + .without_extents(&frame_extents) + .with_size_hints(&client.size_hints()) + .with_extents(&frame_extents) + } else { + region + .without_extents(&frame_extents) + .with_minimum_dim(&MIN_WINDOW_DIM) + .with_extents(&frame_extents) + } + }); + + if let Some(region) = region { + let placement = Placement { + window, + region: Some(region), + extents: *client.frame_extents(), + }; + + self.update_client_placement( + &placement, + LayoutMethod::Free, + ); + self.place_client(placement.window, LayoutMethod::Free); + } + } + } else { + let geometry = self.conn.get_window_geometry(window); + + if let Ok(mut geometry) = geometry { + if let Some(pos) = pos { + geometry.pos = pos; + } + + if let Some(dim) = dim { + geometry.dim = dim; + } + + self.conn.place_window(window, &geometry); + } + } + } + } + + fn handle_grip_request( + &mut self, + window: Window, + pos: Pos, + grip: Option<Grip>, + _on_root: bool, + ) { + debug!( + "GRIP_REQUEST for window {:#0x} with pos {:?} and grip {:?}", + window, pos, grip + ); + + if let Some(grip) = grip { + // initiate resize from grip + self.move_buffer.unset(); + self.resize_buffer.unset(); + + if let Some(client) = self.client(window) { + let current_pos = self.conn.get_pointer_position(); + let client_region = *client.free_region(); + + self.resize_buffer.set( + window, + grip, + current_pos, + client_region, + ); + + self.conn.confine_pointer(self.resize_buffer.handle()); + } + } else { + // initiate move + self.start_moving(window); + } + } + + fn handle_restack_request( + &mut self, + window: Window, + sibling: Window, + mode: StackMode, + _on_root: bool, + ) { + debug!( + "RESTACK_REQUEST for window {:#0x} with sibling {:?} and mode {:?}", + window, sibling, mode + ); + + match mode { + StackMode::Above => self.stack.add_above_other(window, sibling), + StackMode::Below => self.stack.add_below_other(window, sibling), + } + + self.apply_stack(self.active_workspace()); + } + + fn handle_property( + &mut self, + window: Window, + kind: PropertyKind, + _on_root: bool, + ) { + debug!("PROPERTY for window {:#0x} of kind {:?}", window, kind); + + match kind { + PropertyKind::Name => { + let name = self.conn.get_icccm_window_name(window); + + if let Some(client) = self.client_any_mut(window) { + client.set_name(name); + } + }, + PropertyKind::Class => { + let class = self.conn.get_icccm_window_class(window); + let instance = self.conn.get_icccm_window_instance(window); + + if let Some(client) = self.client_any_mut(window) { + client.set_class(class); + client.set_instance(instance); + } + }, + PropertyKind::Size => { + if let Some(client) = self.client_any(window) { + let window = client.window(); + let workspace = client.workspace(); + let geometry = self.conn.get_window_geometry(window); + + if geometry.is_err() { + return; + } + + let frame_extents = client.frame_extents_unchecked(); + let mut geometry = geometry.unwrap(); + let (_, size_hints) = + self.conn.get_icccm_window_size_hints( + window, + Some(MIN_WINDOW_DIM), + &client.size_hints(), + ); + + geometry = if size_hints.is_some() { + geometry.with_size_hints(&size_hints) + } else { + geometry.with_minimum_dim(&MIN_WINDOW_DIM) + }; + + geometry.pos = client.free_region().pos; + geometry.dim.w += frame_extents.left + frame_extents.right; + geometry.dim.h += frame_extents.top + frame_extents.bottom; + + let client = self.client_any_mut(window).unwrap(); + client.set_size_hints(size_hints); + client.set_free_region(&geometry); + + if client.is_managed() + && workspace == self.active_workspace() + { + self.apply_layout(workspace, true); + } + } + }, + PropertyKind::Strut => { + if let Some(struts) = self.conn.get_window_strut(window) { + // TODO: screen of window + let screen = self.active_screen_mut(); + + screen.remove_window_strut(window); + screen.add_struts(struts); + screen.compute_placeable_region(); + + self.apply_layout(self.active_workspace(), true); + } + }, + } + } + + fn handle_frame_extents_request( + &self, + window: Window, + _on_root: bool, + ) { + debug!("FRAME_EXTENTS_REQUEST for window {:#0x}", window); + + self.conn.set_window_frame_extents( + window, + if let Some(client) = self.client_any(window) { + client.frame_extents_unchecked() + } else { + if self.conn.must_manage_window(window) { + FREE_EXTENTS + } else { + Extents { + left: 0, + right: 0, + top: 0, + bottom: 0, + } + } + }, + ); + } + + fn handle_mapping( + &mut self, + request: u8, + ) { + debug!("MAPPING with request {}", request); + if self.conn.is_mapping_request(request) {} // TODO + } + + fn handle_screen_change(&mut self) { + debug!("SCREEN_CHANGE"); + + let workspace = + self.partitions.active_element().unwrap().screen().number(); + self.workspaces.activate_for(&Selector::AtIndex(workspace)); + } + + fn handle_randr(&mut self) { + debug!("RANDR"); + self.acquire_partitions(); + } + + pub fn exit(&mut self) { + info!("exit called, shutting down window manager"); + + for index in 0..self.workspaces.len() { + self.deiconify_all(index); + } + + for (window, client) in self.client_map.drain() { + self.conn.unparent_window(window, client.free_region().pos); + } + + self.running = false; + + self.conn.cleanup(); + self.conn.flush(); + } +} diff --git a/src/core/partition.rs b/src/core/partition.rs @@ -0,0 +1,63 @@ +use crate::common::Ident; +use crate::common::Identify; +use crate::common::Index; + +use winsys::screen::Screen; + +#[derive(Clone)] +pub struct Partition { + screen: Screen, + index: Index, +} + +impl Partition { + pub fn new( + screen: Screen, + index: Index, + ) -> Self { + Self { + screen, + index, + } + } + + pub fn screen(&self) -> &Screen { + &self.screen + } + + pub fn screen_mut(&mut self) -> &mut Screen { + &mut self.screen + } + + pub fn index(&self) -> Index { + self.index + } +} + +impl Identify for Partition { + fn id(&self) -> Ident { + self.screen.number() as Ident + } +} + +impl PartialEq for Partition { + fn eq( + &self, + other: &Self, + ) -> bool { + self.screen.number() == other.screen.number() + } +} + +#[allow(unused)] +impl std::fmt::Debug for Partition { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + f.debug_struct("Partition") + .field("screen", &self.screen) + .field("workspace", &self.index) + .finish() + } +} diff --git a/src/core/profile.rs b/src/core/profile.rs @@ -0,0 +1 @@ + diff --git a/src/core/rule.rs b/src/core/rule.rs @@ -0,0 +1,80 @@ +use crate::client::Client; + +pub struct Rules { + pub float: Option<bool>, + pub center: Option<bool>, + pub fullscreen: Option<bool>, + pub workspace: Option<usize>, + pub context: Option<usize>, +} + +impl Rules { + pub fn propagate( + &self, + client: &mut Client, + ) { + if let Some(float) = self.float { + client.set_floating(float); + } + + if let Some(fullscreen) = self.fullscreen { + client.set_fullscreen(fullscreen); + } + + if let Some(workspace) = self.workspace { + client.set_workspace(workspace); + } + + if let Some(context) = self.context { + client.set_context(context); + } + } + + pub fn float( + &self, + must_float: &mut bool, + ) -> bool { + if let Some(float) = self.float { + *must_float = float; + return float; + } + + false + } + + pub fn center( + &self, + must_center: &mut bool, + ) -> bool { + if let Some(center) = self.center { + *must_center = center; + return center; + } + + false + } + + pub fn fullscreen( + &self, + must_fullscreen: &mut bool, + ) -> bool { + if let Some(fullscreen) = self.fullscreen { + *must_fullscreen = fullscreen; + return fullscreen; + } + + false + } +} + +impl Default for Rules { + fn default() -> Self { + Self { + float: None, + center: None, + fullscreen: None, + workspace: None, + context: None, + } + } +} diff --git a/src/core/stack.rs b/src/core/stack.rs @@ -0,0 +1,162 @@ +use winsys::common::Window; + +use std::collections::HashMap; +use std::vec::Vec; + +#[derive(Debug)] +pub enum StackLayer { + Desktop, + Below, + Dock, + // Regular, + // Free, + // Transient, + Above, + // Fullscreen, + Notification, +} + +pub struct StackManager { + window_layers: HashMap<Window, StackLayer>, + + desktop_windows: Vec<Window>, + below_windows: Vec<Window>, + dock_windows: Vec<Window>, + above_windows: Vec<Window>, + notification_windows: Vec<Window>, + + // which windows should be stacked directly {above,below} which other windows + above_other: HashMap<Window, Window>, + below_other: HashMap<Window, Window>, +} + +impl StackManager { + pub fn new() -> Self { + Self { + window_layers: HashMap::with_capacity(5), + + desktop_windows: Vec::with_capacity(10), + below_windows: Vec::with_capacity(10), + dock_windows: Vec::with_capacity(10), + above_windows: Vec::with_capacity(10), + notification_windows: Vec::with_capacity(10), + + above_other: HashMap::with_capacity(30), + below_other: HashMap::with_capacity(30), + } + } + + pub fn add_window( + &mut self, + window: Window, + layer: StackLayer, + ) { + if !self.window_layers.contains_key(&window) { + let layer_windows = match layer { + StackLayer::Desktop => &mut self.desktop_windows, + StackLayer::Below => &mut self.below_windows, + StackLayer::Dock => &mut self.dock_windows, + StackLayer::Above => &mut self.above_windows, + StackLayer::Notification => &mut self.notification_windows, + }; + + layer_windows.push(window); + self.window_layers.insert(window, layer); + } + } + + pub fn add_above_other( + &mut self, + window: Window, + sibling: Window, + ) { + if !self.above_other.contains_key(&window) { + self.above_other.insert(window, sibling); + } + } + + pub fn add_below_other( + &mut self, + window: Window, + sibling: Window, + ) { + if !self.below_other.contains_key(&window) { + self.below_other.insert(window, sibling); + } + } + + pub fn remove_window( + &mut self, + window: Window, + ) { + if let Some(layer) = self.window_layers.get(&window) { + let layer_windows = match layer { + StackLayer::Desktop => &mut self.desktop_windows, + StackLayer::Below => &mut self.below_windows, + StackLayer::Dock => &mut self.dock_windows, + StackLayer::Above => &mut self.above_windows, + StackLayer::Notification => &mut self.notification_windows, + }; + + let index = + layer_windows.iter().position(|&w| w == window).unwrap(); + + layer_windows.remove(index); + self.window_layers.remove(&window); + } + + self.above_other.remove(&window); + self.below_other.remove(&window); + } + + pub fn relayer_window( + &mut self, + window: Window, + layer: StackLayer, + ) { + self.remove_window(window); + self.add_window(window, layer); + } + + pub fn raise_window( + &mut self, + window: Window, + ) { + if let Some(layer) = self.window_layers.get(&window) { + let layer_windows = match layer { + StackLayer::Desktop => &mut self.desktop_windows, + StackLayer::Below => &mut self.below_windows, + StackLayer::Dock => &mut self.dock_windows, + StackLayer::Above => &mut self.above_windows, + StackLayer::Notification => &mut self.notification_windows, + }; + + let index = + layer_windows.iter().position(|&w| w == window).unwrap(); + + layer_windows.remove(index); + layer_windows.push(window); + } + } + + pub fn layer_windows( + &self, + layer: StackLayer, + ) -> Vec<Window> { + match layer { + StackLayer::Desktop => self.desktop_windows.to_owned(), + StackLayer::Below => self.below_windows.to_owned(), + StackLayer::Dock => self.dock_windows.to_owned(), + StackLayer::Above => self.above_windows.to_owned(), + StackLayer::Notification => self.notification_windows.to_owned(), + } + } + + pub fn above_other(&self) -> &HashMap<Window, Window> { + &self.above_other + } + + pub fn below_other(&self) -> &HashMap<Window, Window> { + &self.below_other + } +} diff --git a/src/core/util.rs b/src/core/util.rs @@ -0,0 +1,181 @@ +use crate::common::Change; +use crate::common::Index; + +use winsys::input::CodeMap; +use winsys::input::KeyCode; + +use std::cmp::Ord; +use std::hash::BuildHasherDefault; +use std::hash::Hasher; +use std::ops::Add; +use std::ops::AddAssign; +use std::ops::Sub; +use std::ops::SubAssign; +use std::process::Command; +use std::process::Stdio; + +use x11rb::protocol::xproto::ModMask; + +#[derive(Default)] +pub struct IdHasher { + state: u64, +} + +impl Hasher for IdHasher { + #[inline] + fn write( + &mut self, + bytes: &[u8], + ) { + for &byte in bytes { + self.state = self.state.rotate_left(8) + u64::from(byte); + } + } + + #[inline] + fn finish(&self) -> u64 { + self.state + } +} + +pub type BuildIdHasher = BuildHasherDefault<IdHasher>; + +pub struct Util {} + +impl Util { + #[inline] + pub fn last_index(iter: impl ExactSizeIterator) -> Index { + if iter.len() != 0 { + iter.len() - 1 + } else { + 0 + } + } + + #[inline] + pub fn change_within_range<T>( + min: T, + max: T, + mut base: T, + change: Change, + delta: T, + ) -> T + where + T: Ord + + Add<Output = T> + + AddAssign + + Sub<Output = T> + + SubAssign + + Copy, + { + match change { + Change::Inc => { + base += delta; + if base > max { + max + } else { + base + } + }, + Change::Dec => { + if base >= min + delta { + base - delta + } else { + min + } + }, + } + } + + pub fn spawn<S: Into<String>>(cmd: S) { + let cmd = cmd.into(); + let args: Vec<&str> = cmd.split_whitespace().collect(); + + if args.len() > 1 { + println!("args: {:?}", args); + Command::new(args[0]) + .args(&args[1..]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .ok(); + } else { + println!("args: {:?}", args); + Command::new(args[0]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .ok(); + }; + } + + pub fn spawn_shell<S: Into<String>>(cmd: S) { + let cmd = cmd.into(); + + Command::new("sh") + .arg("-c") + .arg(cmd) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .ok(); + } + + pub fn system_keycodes() -> CodeMap { + match Command::new("xmodmap").arg("-pke").output() { + Err(e) => panic!("unable to fetch keycodes via xmodmap: {}", e), + Ok(o) => match String::from_utf8(o.stdout) { + Err(e) => panic!("invalid utf8 from xmodmap: {}", e), + Ok(s) => s + .lines() + .flat_map(|l| { + let mut words = l.split_whitespace(); + let key_code: u8 = + words.nth(1).unwrap().parse().unwrap(); + + words.skip(1).map(move |name| (name.into(), key_code)) + }) + .collect::<CodeMap>(), + }, + } + } + + pub fn parse_key_binding( + key_binding: impl Into<String>, + keycodes: &CodeMap, + ) -> Option<KeyCode> { + let s = key_binding.into(); + let mut constituents: Vec<&str> = s.split('-').collect(); + + match keycodes.get(constituents.remove(constituents.len() - 1)) { + Some(code) => { + let mask = constituents + .iter() + .map(|&modifier| match modifier { + "A" | "Alt" | "Meta" => u16::from(ModMask::M1), + "M" | "Super" => u16::from(ModMask::M4), + "S" | "Shift" => u16::from(ModMask::SHIFT), + "C" | "Control" => u16::from(ModMask::CONTROL), + "1" | "Mod" => u16::from(if cfg!(debug_assertions) { + ModMask::M1 + } else { + ModMask::M4 + }), + "2" | "Sec" => u16::from(if cfg!(debug_assertions) { + ModMask::M4 + } else { + ModMask::M1 + }), + _ => panic!("invalid modifier: {}", s), + }) + .fold(0, |acc, modifier| acc | modifier); + + Some(KeyCode { + mask: mask as u16, + code: *code, + }) + }, + None => None, + } + } +} diff --git a/src/core/workspace.rs b/src/core/workspace.rs @@ -0,0 +1,588 @@ +use crate::client::Client; +use crate::common::Change; +use crate::common::Direction; +use crate::common::Ident; +use crate::common::Identify; +use crate::common::Index; +use crate::common::Placement; +use crate::common::FREE_EXTENTS; +use crate::cycle::Cycle; +use crate::cycle::InsertPos; +use crate::cycle::Selector; +use crate::layout::Layout; +use crate::layout::LayoutApply; +use crate::layout::LayoutConfig; +use crate::layout::LayoutFactory; +use crate::layout::LayoutKind; + +use winsys::common::Edge; +use winsys::common::Grip; +use winsys::common::Pos; +use winsys::common::Region; +use winsys::common::Window; + +use std::collections::HashMap; +use std::collections::VecDeque; + +#[derive(Clone, Copy)] +pub enum ClientSelector { + AtActive, + AtMaster, + AtIndex(Index), + AtIdent(Window), + First, + Last, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum BufferKind { + Move, + Resize, + Scratchpad, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Buffer { + kind: BufferKind, + handle: Window, + window: Option<Window>, + grip: Option<Grip>, + grip_pos: Option<Pos>, + window_region: Option<Region>, +} + +impl Buffer { + pub fn new( + kind: BufferKind, + handle: Window, + ) -> Self { + Self { + kind, + handle, + window: None, + grip: None, + grip_pos: None, + window_region: None, + } + } + + pub fn set( + &mut self, + window: Window, + grip: Grip, + pos: Pos, + region: Region, + ) { + self.window = Some(window); + self.grip = Some(grip); + self.grip_pos = Some(pos); + self.window_region = Some(region); + } + + pub fn unset(&mut self) { + self.window = None; + self.grip = None; + self.grip_pos = None; + self.window_region = None; + } + + pub fn is_occupied(&self) -> bool { + self.window.is_some() + } + + pub fn handle(&self) -> Window { + self.handle + } + + pub fn window(&self) -> Option<Window> { + self.window + } + + pub fn grip(&self) -> Option<Grip> { + self.grip + } + + pub fn grip_pos(&self) -> Option<Pos> { + self.grip_pos + } + + pub fn set_grip_pos( + &mut self, + pos: Pos, + ) { + self.grip_pos = Some(pos); + } + + pub fn window_region(&self) -> &Option<Region> { + &self.window_region + } + + pub fn set_window_region( + &mut self, + region: Region, + ) { + self.window_region = Some(region); + } +} + +#[derive(Debug, Clone)] +pub struct Scratchpad { + command: String, + client: Option<Window>, + active: bool, +} + +#[derive(Debug, Clone)] +pub struct Workspace { + number: Ident, + name: String, + clients: Cycle<Window>, + icons: Cycle<Window>, + layouts: Cycle<Layout>, + previous_layout: LayoutKind, +} + +impl Workspace { + pub fn new( + name: impl Into<String>, + number: Ident, + ) -> Self { + Self { + number, + name: name.into(), + clients: Cycle::new(Vec::new(), true), + icons: Cycle::new(Vec::new(), true), + layouts: Cycle::new( + vec![ + LayoutFactory::create_layout(LayoutKind::Center), + LayoutFactory::create_layout(LayoutKind::Monocle), + LayoutFactory::create_layout(LayoutKind::Paper), + LayoutFactory::create_layout(LayoutKind::PaperCenter), + LayoutFactory::create_layout(LayoutKind::SStack), + LayoutFactory::create_layout(LayoutKind::SingleFloat), + LayoutFactory::create_layout(LayoutKind::Stack), + LayoutFactory::create_layout(LayoutKind::Float), + ], + false, + ), + previous_layout: LayoutKind::Float, + } + } + + pub fn number(&self) -> Ident { + self.number + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn set_name( + &mut self, + name: impl Into<String>, + ) { + self.name = name.into(); + } + + pub fn len(&self) -> usize { + self.clients.len() + } + + pub fn contains( + &self, + window: Window, + ) -> bool { + self.clients.contains(&window) + } + + pub fn is_empty(&self) -> bool { + self.clients.len() == 0 + } + + pub fn iter(&self) -> std::collections::vec_deque::Iter<Window> { + self.clients.iter() + } + + pub fn iter_mut(&mut self) -> std::collections::vec_deque::IterMut<Window> { + self.clients.iter_mut() + } + + pub fn stack(&self) -> &VecDeque<Window> { + self.clients.stack() + } + + pub fn stack_after_focus(&self) -> Vec<Window> { + self.clients.stack_after_focus() + } + + pub fn focused_client(&self) -> Option<Window> { + self.clients.active_element().copied() + } + + pub fn get_client_for( + &self, + sel: &ClientSelector, + ) -> Option<&Window> { + let sel = match sel { + ClientSelector::AtActive => Selector::AtActive, + ClientSelector::AtMaster => { + if let Some(layout) = self.layouts.active_element() { + if let Some(main_count) = layout.main_count() { + Selector::AtIndex(std::cmp::min( + main_count as usize, + self.clients.len(), + )) + } else { + return None; + } + } else { + return None; + } + }, + ClientSelector::AtIndex(index) => Selector::AtIndex(*index), + ClientSelector::AtIdent(window) => { + Selector::AtIdent(*window as Ident) + }, + ClientSelector::First => Selector::First, + ClientSelector::Last => Selector::Last, + }; + + self.clients.get_for(&sel) + } + + pub fn next_client( + &self, + dir: Direction, + ) -> Option<Window> { + self.clients.next_element(dir).copied() + } + + pub fn add_client( + &mut self, + window: Window, + insert: &InsertPos, + ) { + self.clients.insert_at(insert, window); + } + + pub fn replace_client( + &mut self, + window: Window, + replacement: Window, + ) { + self.clients.remove_for(&Selector::AtIdent(replacement)); + self.clients + .insert_at(&InsertPos::BeforeIdent(window), replacement); + self.clients.remove_for(&Selector::AtIdent(window)); + } + + pub fn focus_client( + &mut self, + window: Window, + ) -> Option<Window> { + let prev_active = match self.clients.active_element() { + Some(c) => *c, + None => return None, + }; + + self.clients.activate_for(&Selector::AtIdent(window)); + Some(prev_active) + } + + pub fn remove_client( + &mut self, + window: Window, + ) -> Option<Window> { + self.clients.remove_for(&Selector::AtIdent(window)) + } + + pub fn remove_focused_client(&mut self) -> Option<Window> { + self.clients.remove_for(&Selector::AtActive) + } + + pub fn arrange_with_filter<F>( + &self, + screen_region: Region, + client_map: &HashMap<Window, Client>, + filter: F, + ) -> Vec<Placement> + where + F: Fn(&Client) -> bool, + { + if !self.clients.is_empty() { + let layout = self.layouts.active_element().unwrap(); + let (fullscreen, non_fullscreen): (Vec<&Client>, Vec<&Client>) = + self.clients + .iter() + .map(|window| client_map.get(window).unwrap()) + .partition(|client| { + client.is_fullscreen() && !client.is_in_window() + }); + let (tiled, floating): (Vec<&Client>, Vec<&Client>) = + non_fullscreen.iter().partition(|client| filter(client)); + + let mut placements = if tiled.is_empty() { + Vec::with_capacity(floating.len()) + } else { + layout.arrange(&tiled, self.focused_client(), &screen_region) + }; + + placements.append( + &mut fullscreen + .iter() + .map(|client| Placement { + window: client.window(), + region: if !client.is_focused() + && self.layout_config().persistent + && self.layout_config().single + { + None + } else { + Some(screen_region) + }, + extents: None, + }) + .collect(), + ); + + placements.append( + &mut floating + .iter() + .map(|client| Placement { + window: client.window(), + region: if !client.is_focused() + && self.layout_config().persistent + && self.layout_config().single + { + None + } else { + Some(*client.free_region()) + }, + extents: Some(FREE_EXTENTS), + }) + .collect(), + ); + + placements + } else { + vec![] + } + } + + pub fn arrange( + &self, + screen_region: Region, + client_map: &HashMap<Window, Client>, + ) -> Vec<Placement> { + if !self.clients.is_empty() { + let layout = self.layouts.active_element().unwrap(); + let clients: Vec<&Client> = self + .clients + .iter() + .map(|window| client_map.get(window).unwrap()) + .collect(); + + layout.arrange(&clients, self.focused_client(), &screen_region) + } else { + vec![] + } + } + + pub fn set_layout( + &mut self, + kind: LayoutKind, + ) -> Option<&Layout> { + let layout = self.layouts.active_element().unwrap(); + + if layout.kind == kind { + return None; + } + + self.previous_layout = layout.kind; + + let layout = + self.layouts.activate_for(&Selector::AtIdent(kind as Ident)); + + layout + } + + pub fn toggle_layout(&mut self) -> Option<&Layout> { + self.set_layout(self.previous_layout) + } + + pub fn cycle_layout( + &mut self, + dir: Direction, + ) -> char { + self.layouts.cycle_active(dir); + self.layout_symbol() + } + + pub fn layout_kind(&self) -> LayoutKind { + self.layouts.active_element().unwrap().kind + } + + pub fn layout_symbol(&self) -> char { + self.layouts.active_element().unwrap().symbol + } + + pub fn layout_name(&self) -> &str { + &self.layouts.active_element().unwrap().name + } + + pub fn layout_config(&self) -> LayoutConfig { + self.layouts.active_element().unwrap().config + } + + pub fn cycle_focus( + &mut self, + dir: Direction, + ) -> Option<(Window, Window)> { + if self.clients.len() < 2 { + return None; + } + + if !self.layout_config().wraps && self.clients.next_will_wrap(dir) { + return None; + } + + let prev_active = *self.clients.active_element()?; + let now_active = *self.clients.cycle_active(dir)?; + + if prev_active != now_active { + Some((prev_active, now_active)) + } else { + None + } + } + + pub fn drag_focus( + &mut self, + dir: Direction, + ) -> Option<Window> { + self.clients.drag_active(dir).copied() + } + + pub fn rotate_clients( + &mut self, + dir: Direction, + ) -> Option<(Window, Window)> { + if self.clients.len() < 2 { + return None; + } + + let prev_active = *self.clients.active_element()?; + + self.clients.rotate(dir); + + let now_active = *self.clients.active_element()?; + + if prev_active != now_active { + Some((prev_active, now_active)) + } else { + None + } + } + + pub fn reset_layout(&mut self) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.reset(); + } + + pub fn change_gap_size( + &mut self, + change: Change, + delta: u32, + ) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.change_gap_size(change, delta); + } + + pub fn reset_gap_size(&mut self) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.reset_gap_size(); + } + + pub fn change_main_count( + &mut self, + change: Change, + ) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.change_main_count(change); + } + + pub fn change_main_factor( + &mut self, + change: Change, + delta: f32, + ) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.change_main_factor(change, delta); + } + + pub fn change_margin( + &mut self, + edge: Edge, + change: Change, + delta: u32, + ) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.change_margin(edge, change, delta); + } + + pub fn reset_margin(&mut self) { + let layout = self.layouts.active_element_mut().unwrap(); + layout.reset_margin(); + } + + pub fn focused_icon(&self) -> Option<Window> { + self.icons.active_element().copied() + } + + pub fn icon_to_client( + &mut self, + window: Window, + ) { + if let Some(icon) = self.remove_icon(window) { + self.add_client(icon, &InsertPos::Back); + } + } + + pub fn client_to_icon( + &mut self, + window: Window, + ) { + if let Some(client) = self.remove_client(window) { + self.add_icon(client); + } + } + + pub fn add_icon( + &mut self, + window: Window, + ) { + self.icons.insert_at(&InsertPos::Back, window); + } + + pub fn remove_icon( + &mut self, + window: Window, + ) -> Option<Window> { + self.icons.remove_for(&Selector::AtIdent(window)) + } +} + +impl Identify for Workspace { + fn id(&self) -> Ident { + self.number + } +} + +impl PartialEq for Workspace { + fn eq( + &self, + other: &Self, + ) -> bool { + self.number == other.number + } +} diff --git a/src/main.rs b/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/src/winsys/common.rs b/src/winsys/common.rs @@ -0,0 +1,1082 @@ +use std::default::Default; +use std::ops::Add; +use std::ops::AddAssign; +use std::ops::Sub; +use std::ops::SubAssign; + +pub type Atom = u32; +pub type Window = u32; +pub type Pid = u32; +pub type Extents = Padding; + +pub struct Hex32(pub u32); + +impl std::fmt::Debug for Hex32 { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(f, "{:#0x}", &self.0) + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum IcccmWindowState { + Withdrawn, + Normal, + Iconic, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum WindowState { + Modal, + Sticky, + MaximizedVert, + MaximizedHorz, + Shaded, + SkipTaskbar, + SkipPager, + Hidden, + Fullscreen, + Above, + Below, + DemandsAttention, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum WindowType { + Desktop, + Dock, + Toolbar, + Menu, + Utility, + Splash, + Dialog, + DropdownMenu, + PopupMenu, + Tooltip, + Notification, + Combo, + Dnd, + Normal, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum Grip { + Edge(Edge), + Corner(Corner), +} + +impl Grip { + pub fn is_top_grip(&self) -> bool { + *self == Grip::Edge(Edge::Top) + || *self == Grip::Corner(Corner::TopLeft) + || *self == Grip::Corner(Corner::TopRight) + } + + pub fn is_left_grip(&self) -> bool { + *self == Grip::Edge(Edge::Left) + || *self == Grip::Corner(Corner::TopLeft) + || *self == Grip::Corner(Corner::BottomLeft) + } +} + +#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] +pub enum Edge { + Left, + Right, + Top, + Bottom, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum Corner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub struct Region { + pub pos: Pos, + pub dim: Dim, +} + +impl Default for Region { + fn default() -> Self { + Self { + pos: Default::default(), + dim: Default::default(), + } + } +} + +impl Region { + pub fn new( + x: i32, + y: i32, + w: u32, + h: u32, + ) -> Self { + Self { + pos: Pos { + x, + y, + }, + dim: Dim { + w, + h, + }, + } + } + + pub fn values(&self) -> (Pos, Dim) { + (self.pos, self.dim) + } + + pub fn with_size_hints( + self, + size_hints: &Option<SizeHints>, + ) -> Self { + let mut geometry = self; + + if let Some(size_hints) = size_hints { + size_hints.apply(&mut geometry.dim); + } + + geometry + } + + pub fn encompasses( + &self, + pos: Pos, + ) -> bool { + pos.x >= self.pos.x + && pos.y >= self.pos.y + && pos.x <= self.pos.x + self.dim.w as i32 + && pos.y <= self.pos.y + self.dim.h as i32 + } + + pub fn contains( + &self, + region: Region, + ) -> bool { + self.encompasses(region.pos) && self.encompasses(region.bottom_right()) + } + + pub fn occludes( + &self, + region: Region, + ) -> bool { + self.encompasses(region.pos) || region.encompasses(self.pos) + } + + pub fn nearest_corner( + &self, + mut pos: Pos, + ) -> Corner { + pos += self.pos.dist(Pos { + x: 0, + y: 0, + }); + self.dim.nearest_corner(pos) + } + + pub fn quadrant_center_from_pos( + &self, + pos: Pos, + ) -> Option<Pos> { + if self.encompasses(pos) { + return None; + } + + let mut dists = vec![ + (Corner::TopLeft, self.pos.dist(pos).pythagorean()), + (Corner::TopRight, self.top_right().dist(pos).pythagorean()), + ( + Corner::BottomLeft, + self.bottom_left().dist(pos).pythagorean(), + ), + ( + Corner::BottomRight, + self.bottom_right().dist(pos).pythagorean(), + ), + ]; + + dists.sort_by_key(|&(corner, dist)| dist); + + match dists.first().unwrap() { + (Corner::TopLeft, _) => { + let (left, _) = self + .split_at_width((self.dim.w as f64 / 2f64).round() as u32); + let (topleft, _) = left + .split_at_height((left.dim.h as f64 / 2f64).round() as u32); + + Some(Pos::from_center_of_region(topleft)) + }, + (Corner::TopRight, _) => { + let (_, right) = self + .split_at_width((self.dim.w as f64 / 2f64).round() as u32); + let (topright, _) = + right.split_at_height( + (right.dim.h as f64 / 2f64).round() as u32 + ); + + Some(Pos::from_center_of_region(topright)) + }, + (Corner::BottomLeft, _) => { + let (left, _) = self + .split_at_width((self.dim.w as f64 / 2f64).round() as u32); + let (_, bottomleft) = left + .split_at_height((left.dim.h as f64 / 2f64).round() as u32); + + Some(Pos::from_center_of_region(bottomleft)) + }, + (Corner::BottomRight, _) => { + let (_, right) = self + .split_at_width((self.dim.w as f64 / 2f64).round() as u32); + let (_, bottomright) = + right.split_at_height( + (right.dim.h as f64 / 2f64).round() as u32 + ); + + Some(Pos::from_center_of_region(bottomright)) + }, + } + } + + pub fn split_at_width( + &self, + width: u32, + ) -> (Self, Self) { + assert!(width < self.dim.w, "Desired width exceeds divisible width."); + + ( + Self { + dim: Dim { + w: width, + ..self.dim + }, + ..*self + }, + Self { + pos: Pos { + x: self.pos.x + width as i32, + ..self.pos + }, + dim: Dim { + w: self.dim.w - width, + ..self.dim + }, + }, + ) + } + + pub fn split_at_height( + &self, + height: u32, + ) -> (Self, Self) { + assert!( + height < self.dim.h, + "Desired height exceeds divisible height." + ); + + ( + Self { + dim: Dim { + h: height, + ..self.dim + }, + ..*self + }, + Self { + pos: Pos { + y: self.pos.y + height as i32, + ..self.pos + }, + dim: Dim { + h: self.dim.h - height, + ..self.dim + }, + }, + ) + } + + pub fn with_minimum_dim( + self, + minimum_dim: &Dim, + ) -> Self { + Self { + pos: self.pos, + dim: Dim { + w: std::cmp::max(minimum_dim.w, self.dim.w), + h: std::cmp::max(minimum_dim.h, self.dim.h), + }, + } + } + + pub fn with_maximum_dim( + self, + maximum_dim: &Dim, + ) -> Self { + Self { + pos: self.pos, + dim: Dim { + w: std::cmp::min(maximum_dim.w, self.dim.w), + h: std::cmp::min(maximum_dim.h, self.dim.h), + }, + } + } + + pub fn from_absolute_inner_center( + self, + dim: &Dim, + ) -> Self { + if dim.w > self.dim.w || dim.h > self.dim.h { + return self; + } + + Self { + pos: Pos { + x: self.pos.x + ((self.dim.w - dim.w) as f32 / 2f32) as i32, + y: self.pos.y + ((self.dim.h - dim.h) as f32 / 2f32) as i32, + }, + dim: *dim, + } + } + + pub fn without_extents( + mut self, + extents: &Extents, + ) -> Self { + self.pos.x += extents.left as i32; + self.pos.y += extents.top as i32; + self.dim.w -= extents.left + extents.right; + self.dim.h -= extents.top + extents.bottom; + self + } + + pub fn with_extents( + mut self, + extents: &Extents, + ) -> Self { + self.pos.x -= extents.left as i32; + self.pos.y -= extents.top as i32; + self.dim.w += extents.left + extents.right; + self.dim.h += extents.top + extents.bottom; + self + } + + pub fn top_right(&self) -> Pos { + Pos { + x: self.pos.x + self.dim.w as i32, + y: self.pos.y, + } + } + + pub fn bottom_left(&self) -> Pos { + Pos { + x: self.pos.x, + y: self.pos.y + self.dim.h as i32, + } + } + + pub fn bottom_right(&self) -> Pos { + Pos { + x: self.pos.x + self.dim.w as i32, + y: self.pos.y + self.dim.h as i32, + } + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub struct Pos { + pub x: i32, + pub y: i32, +} + +impl Default for Pos { + fn default() -> Self { + Self { + x: 0, + y: 0, + } + } +} + +impl Pos { + pub fn from_center_of_region(region: Region) -> Self { + Self { + x: region.pos.x + (region.dim.w as f32 / 2f32) as i32, + y: region.pos.y + (region.dim.h as f32 / 2f32) as i32, + } + } + + pub fn from_center_of_dim(dim: Dim) -> Self { + Self { + x: dim.w as i32 / 2, + y: dim.h as i32 / 2, + } + } + + pub fn values(&self) -> (i32, i32) { + (self.x, self.y) + } + + pub fn dist( + &self, + pos: Self, + ) -> Distance { + Distance { + dx: (pos.x - self.x), + dy: (pos.y - self.y), + } + } + + pub fn relative_to( + &self, + pos: Self, + ) -> Self { + Pos { + x: self.x - pos.x, + y: self.y - pos.y, + } + } +} + +impl Add<Pos> for Pos { + type Output = Self; + + fn add( + self, + other: Pos, + ) -> Self::Output { + Self::Output { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub struct Dim { + pub w: u32, + pub h: u32, +} + +impl Default for Dim { + fn default() -> Self { + Self { + w: 480, + h: 260, + } + } +} + +impl Dim { + pub fn values(&self) -> (u32, u32) { + (self.w, self.h) + } + + pub fn center(&self) -> Pos { + Pos { + x: (self.w as f32 / 2f32) as i32, + y: (self.h as f32 / 2f32) as i32, + } + } + + pub fn nearest_corner( + &self, + pos: Pos, + ) -> Corner { + let center = self.center(); + + if pos.x >= center.x { + if pos.y >= center.y { + Corner::BottomRight + } else { + Corner::TopRight + } + } else { + if pos.y >= center.y { + Corner::BottomLeft + } else { + Corner::TopLeft + } + } + } +} + +impl Add<Dim> for Pos { + type Output = Self; + + fn add( + self, + other: Dim, + ) -> Self::Output { + Self::Output { + x: self.x + other.w as i32, + y: self.y + other.h as i32, + } + } +} + +impl Sub<Dim> for Pos { + type Output = Self; + + fn sub( + self, + other: Dim, + ) -> Self::Output { + Self::Output { + x: self.x - other.w as i32, + y: self.y - other.h as i32, + } + } +} + +impl Sub for Pos { + type Output = Dim; + + fn sub( + self, + other: Self, + ) -> Self::Output { + Self::Output { + w: (self.x as i32 - other.x) as u32, + h: (self.y as i32 - other.y) as u32, + } + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub struct Distance { + pub dx: i32, + pub dy: i32, +} + +impl Distance { + pub fn values(&self) -> (i32, i32) { + (self.dx, self.dy) + } + + pub fn pythagorean(&self) -> u32 { + let dx = self.dx.pow(2) as f64; + let dy = self.dy.pow(2) as f64; + + (dx + dy).sqrt().round() as u32 + } +} + +impl Add<Distance> for Pos { + type Output = Self; + + fn add( + self, + dist: Distance, + ) -> Self::Output { + Self::Output { + x: self.x + dist.dx, + y: self.y + dist.dy, + } + } +} + +impl AddAssign<Distance> for Pos { + fn add_assign( + &mut self, + dist: Distance, + ) { + *self = Self { + x: self.x + dist.dx, + y: self.y + dist.dy, + }; + } +} + +impl Sub<Distance> for Pos { + type Output = Self; + + fn sub( + self, + dist: Distance, + ) -> Self::Output { + Self::Output { + x: self.x - dist.dx, + y: self.y - dist.dy, + } + } +} + +impl SubAssign<Distance> for Pos { + fn sub_assign( + &mut self, + dist: Distance, + ) { + *self = Self { + x: self.x - dist.dx, + y: self.y - dist.dy, + }; + } +} + +impl Add<Distance> for Dim { + type Output = Self; + + fn add( + self, + dist: Distance, + ) -> Self::Output { + Self::Output { + w: (self.w as i32 + dist.dx).abs() as u32, + h: (self.h as i32 + dist.dy).abs() as u32, + } + } +} + +impl AddAssign<Distance> for Dim { + fn add_assign( + &mut self, + dist: Distance, + ) { + *self = Self { + w: (self.w as i32 + dist.dx).abs() as u32, + h: (self.h as i32 + dist.dy).abs() as u32, + }; + } +} + +impl Sub<Distance> for Dim { + type Output = Self; + + fn sub( + self, + dist: Distance, + ) -> Self::Output { + Self::Output { + w: (self.w as i32 - dist.dx).abs() as u32, + h: (self.h as i32 - dist.dy).abs() as u32, + } + } +} + +impl SubAssign<Distance> for Dim { + fn sub_assign( + &mut self, + dist: Distance, + ) { + *self = Self { + w: (self.w as i32 - dist.dx).abs() as u32, + h: (self.h as i32 - dist.dy).abs() as u32, + }; + } +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub struct Ratio { + pub numerator: i32, + pub denominator: i32, +} + +impl Ratio { + pub fn new( + numerator: i32, + denominator: i32, + ) -> Self { + Self { + numerator, + denominator, + } + } +} + +#[derive(Debug, Copy, Clone, PartialOrd)] +pub struct SizeHints { + pub by_user: bool, + pub pos: Option<Pos>, + pub min_width: Option<u32>, + pub min_height: Option<u32>, + pub max_width: Option<u32>, + pub max_height: Option<u32>, + pub base_width: Option<u32>, + pub base_height: Option<u32>, + pub inc_width: Option<u32>, + pub inc_height: Option<u32>, + pub min_ratio: Option<f64>, + pub max_ratio: Option<f64>, + pub min_ratio_vulgar: Option<Ratio>, + pub max_ratio_vulgar: Option<Ratio>, +} + +impl SizeHints { + fn new( + by_user: bool, + pos: Option<Pos>, + min_width: Option<u32>, + min_height: Option<u32>, + max_width: Option<u32>, + max_height: Option<u32>, + base_width: Option<u32>, + base_height: Option<u32>, + inc_width: Option<u32>, + inc_height: Option<u32>, + min_ratio: Option<f64>, + max_ratio: Option<f64>, + min_ratio_vulgar: Option<Ratio>, + max_ratio_vulgar: Option<Ratio>, + ) -> Self { + Self { + by_user, + pos, + min_width, + min_height, + max_width, + max_height, + base_width, + base_height, + inc_width, + inc_height, + min_ratio, + max_ratio, + min_ratio_vulgar, + max_ratio_vulgar, + } + } + + pub fn apply( + &self, + dim: &mut Dim, + ) { + let mut dest_width = dim.w as i32; + let mut dest_height = dim.h as i32; + + if let Some(min_width) = self.min_width { + dest_width = std::cmp::max(dest_width, min_width as i32); + } + + if let Some(min_height) = self.min_height { + dest_height = std::cmp::max(dest_height, min_height as i32); + } + + if let Some(max_width) = self.max_width { + dest_width = std::cmp::min(dest_width, max_width as i32); + } + + if let Some(max_height) = self.max_height { + dest_height = std::cmp::min(dest_height, max_height as i32); + } + + let base_width = if let Some(base_width) = self.base_width { + base_width as i32 + } else { + 0 + }; + + let base_height = if let Some(base_height) = self.base_height { + base_height as i32 + } else { + 0 + }; + + let mut width = if base_width < dest_width { + dest_width - base_width as i32 + } else { + dest_width + }; + + let mut height = if base_height < dest_height { + dest_height - base_height as i32 + } else { + dest_height + }; + + if self.min_ratio.is_some() || self.max_ratio.is_some() { + if height == 0 { + height = 1; + } + + let current_ratio = width as f64 / height as f64; + let mut new_ratio = None; + + if let Some(min_ratio) = self.min_ratio { + if current_ratio < min_ratio { + new_ratio = Some(min_ratio); + } + } + + if new_ratio.is_none() { + if let Some(max_ratio) = self.max_ratio { + if current_ratio > max_ratio { + new_ratio = Some(max_ratio); + } + } + } + + if let Some(new_ratio) = new_ratio { + height = (width as f64 / new_ratio).round() as i32; + width = (height as f64 * new_ratio).round() as i32; + + dest_width = width + base_width as i32; + dest_height = height + base_height as i32; + } + } + + if let Some(inc_height) = self.inc_height { + if dest_height >= base_height { + dest_height -= base_height as i32; + dest_height -= dest_height % inc_height as i32; + dest_height += base_height as i32; + } + } + + if let Some(inc_width) = self.inc_width { + if dest_width >= base_width { + dest_width -= base_width as i32; + dest_width -= dest_width % inc_width as i32; + dest_width += base_width as i32; + } + } + + dim.w = std::cmp::max(dest_width, 0) as u32; + dim.h = std::cmp::max(dest_height, 0) as u32; + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Padding { + pub left: u32, + pub right: u32, + pub top: u32, + pub bottom: u32, +} + +impl Default for Padding { + fn default() -> Self { + Self { + left: 0, + right: 0, + top: 0, + bottom: 0, + } + } +} + +impl Padding { + pub fn with_each_edge(size: u32) -> Self { + Self { + left: size, + right: size, + top: size, + bottom: size, + } + } +} + +impl Add<Padding> for Region { + type Output = Self; + + fn add( + self, + padding: Padding, + ) -> Self::Output { + Self::Output { + pos: Pos { + x: self.pos.x - padding.left as i32, + y: self.pos.y - padding.top as i32, + }, + dim: Dim { + w: self.dim.w + padding.left + padding.right, + h: self.dim.h + padding.top + padding.bottom, + }, + } + } +} + +impl Sub<Padding> for Region { + type Output = Self; + + fn sub( + self, + padding: Padding, + ) -> Self::Output { + Self::Output { + pos: Pos { + x: self.pos.x + padding.left as i32, + y: self.pos.y + padding.top as i32, + }, + dim: Dim { + w: self.dim.w - padding.left - padding.right, + h: self.dim.h - padding.top - padding.bottom, + }, + } + } +} + +impl AddAssign<Padding> for Region { + fn add_assign( + &mut self, + padding: Padding, + ) { + *self = Self { + pos: Pos { + x: self.pos.x - padding.left as i32, + y: self.pos.y - padding.top as i32, + }, + dim: Dim { + w: self.dim.w + padding.left + padding.right, + h: self.dim.h + padding.top + padding.bottom, + }, + }; + } +} + +impl SubAssign<Padding> for Region { + fn sub_assign( + &mut self, + padding: Padding, + ) { + *self = Self { + pos: Pos { + x: self.pos.x + padding.left as i32, + y: self.pos.y + padding.top as i32, + }, + dim: Dim { + w: self.dim.w - padding.left - padding.right, + h: self.dim.h - padding.top - padding.bottom, + }, + }; + } +} + +impl Add<Padding> for Dim { + type Output = Self; + + fn add( + self, + padding: Padding, + ) -> Self::Output { + Self::Output { + w: self.w + padding.left + padding.right, + h: self.h + padding.top + padding.bottom, + } + } +} + +impl Sub<Padding> for Dim { + type Output = Self; + + fn sub( + self, + padding: Padding, + ) -> Self::Output { + Self::Output { + w: self.w - padding.left - padding.right, + h: self.h - padding.top - padding.bottom, + } + } +} + +impl AddAssign<Padding> for Dim { + fn add_assign( + &mut self, + padding: Padding, + ) { + *self = Self { + w: self.w + padding.left + padding.right, + h: self.h + padding.top + padding.bottom, + }; + } +} + +impl SubAssign<Padding> for Dim { + fn sub_assign( + &mut self, + padding: Padding, + ) { + *self = Self { + w: self.w - padding.left - padding.right, + h: self.h - padding.top - padding.bottom, + }; + } +} + +impl PartialEq for SizeHints { + fn eq( + &self, + other: &Self, + ) -> bool { + self.min_width == other.min_width + && self.min_height == other.min_height + && self.max_width == other.max_width + && self.max_height == other.max_height + && self.base_width == other.base_width + && self.base_height == other.base_height + && self.inc_width == other.inc_width + && self.inc_height == other.inc_height + && self.min_ratio_vulgar == other.min_ratio_vulgar + && self.max_ratio_vulgar == other.max_ratio_vulgar + } +} + +impl Eq for SizeHints {} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub struct Hints { + pub urgent: bool, + pub input: Option<bool>, + pub initial_state: Option<IcccmWindowState>, + pub group: Option<Window>, +} + +impl Hints { + fn new( + urgent: bool, + input: Option<bool>, + initial_state: Option<IcccmWindowState>, + group: Option<Window>, + ) -> Self { + Self { + urgent, + input, + initial_state, + group, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Strut { + pub window: Window, + pub width: u32, +} + +impl Strut { + pub fn new( + window: Window, + width: u32, + ) -> Self { + Self { + window, + width, + } + } +} + +impl PartialOrd for Strut { + fn partial_cmp( + &self, + other: &Self, + ) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for Strut { + fn cmp( + &self, + other: &Self, + ) -> std::cmp::Ordering { + other.width.cmp(&self.width) + } +} diff --git a/src/winsys/connection.rs b/src/winsys/connection.rs @@ -0,0 +1,350 @@ +use crate::common::Dim; +use crate::common::Extents; +use crate::common::Hints; +use crate::common::IcccmWindowState; +use crate::common::Pid; +use crate::common::Pos; +use crate::common::Region; +use crate::common::SizeHints; +use crate::common::Strut; +use crate::common::Window; +use crate::common::WindowState; +use crate::common::WindowType; +use crate::event::Event; +use crate::input::*; +use crate::screen::Screen; +use crate::Result; + +use std::collections::HashMap; + +pub trait Connection { + fn flush(&self) -> bool; + fn step(&self) -> Option<Event>; + fn connected_outputs(&self) -> Vec<Screen>; + fn top_level_windows(&self) -> Vec<Window>; + fn get_pointer_position(&self) -> Pos; + fn warp_pointer_center_of_window_or_root( + &self, + window: Option<Window>, + screen: &Screen, + ); + fn warp_pointer( + &self, + pos: Pos, + ); + fn warp_pointer_rpos( + &self, + window: Window, + pos: Pos, + ); + fn confine_pointer( + &mut self, + window: Window, + ); + fn release_pointer(&mut self); + fn is_mapping_request( + &self, + request: u8, + ) -> bool; + fn cleanup(&self); + + // Window manipulation + fn create_frame( + &self, + region: Region, + ) -> Window; + fn create_handle(&self) -> Window; + fn init_window( + &self, + window: Window, + focus_follows_mouse: bool, + ); + fn init_frame( + &self, + window: Window, + focus_follows_mouse: bool, + ); + fn init_unmanaged( + &self, + window: Window, + ); + fn cleanup_window( + &self, + window: Window, + ); + fn map_window( + &self, + window: Window, + ); + fn unmap_window( + &self, + window: Window, + ); + fn reparent_window( + &self, + window: Window, + parent: Window, + pos: Pos, + ); + fn unparent_window( + &self, + window: Window, + pos: Pos, + ); + fn destroy_window( + &self, + window: Window, + ); + fn close_window( + &self, + window: Window, + ) -> bool; + fn kill_window( + &self, + window: Window, + ) -> bool; + fn place_window( + &self, + window: Window, + region: &Region, + ); + fn move_window( + &self, + window: Window, + pos: Pos, + ); + fn resize_window( + &self, + window: Window, + dim: Dim, + ); + fn focus_window( + &self, + window: Window, + ); + fn stack_window_above( + &self, + window: Window, + sibling: Option<Window>, + ); + fn stack_window_below( + &self, + window: Window, + sibling: Option<Window>, + ); + fn insert_window_in_save_set( + &self, + window: Window, + ); + fn grab_bindings( + &self, + key_codes: &[KeyCode], + mouse_bindings: &[&(MouseEventKey, MouseShortcut)], + ); + fn regrab_buttons( + &self, + window: Window, + ); + fn ungrab_buttons( + &self, + window: Window, + ); + fn unfocus(&self); + fn set_window_border_width( + &self, + window: Window, + width: u32, + ); + fn set_window_border_color( + &self, + window: Window, + color: u32, + ); + fn set_window_background_color( + &self, + window: Window, + color: u32, + ); + fn update_window_offset( + &self, + window: Window, + frame: Window, + ); + fn get_focused_window(&self) -> Window; + fn get_window_geometry( + &self, + window: Window, + ) -> Result<Region>; + fn get_window_pid( + &self, + window: Window, + ) -> Option<Pid>; + fn must_manage_window( + &self, + window: Window, + ) -> bool; + fn must_free_window( + &self, + window: Window, + ) -> bool; + fn window_is_mappable( + &self, + window: Window, + ) -> bool; + + // ICCCM + fn set_icccm_window_state( + &self, + window: Window, + state: IcccmWindowState, + ); + fn set_icccm_window_hints( + &self, + window: Window, + hints: Hints, + ); + fn get_icccm_window_name( + &self, + window: Window, + ) -> String; + fn get_icccm_window_class( + &self, + window: Window, + ) -> String; + fn get_icccm_window_instance( + &self, + window: Window, + ) -> String; + fn get_icccm_window_transient_for( + &self, + window: Window, + ) -> Option<Window>; + fn get_icccm_window_client_leader( + &self, + window: Window, + ) -> Option<Window>; + fn get_icccm_window_hints( + &self, + window: Window, + ) -> Option<Hints>; + fn get_icccm_window_size_hints( + &self, + window: Window, + min_window_dim: Option<Dim>, + current_size_hints: &Option<SizeHints>, + ) -> (bool, Option<SizeHints>); + + // EWMH + fn init_wm_properties( + &self, + wm_name: &str, + desktop_names: &[&str], + ); + fn set_current_desktop( + &self, + index: usize, + ); + fn set_root_window_name( + &self, + name: &str, + ); + fn set_window_desktop( + &self, + window: Window, + index: usize, + ); + fn set_window_fullscreen( + &self, + window: Window, + on: bool, + ); + fn set_window_above( + &self, + window: Window, + on: bool, + ); + fn set_window_below( + &self, + window: Window, + on: bool, + ); + fn set_window_state( + &self, + window: Window, + state: WindowState, + on: bool, + ); + fn set_window_frame_extents( + &self, + window: Window, + extents: Extents, + ); + fn set_desktop_geometry( + &self, + geometries: &[&Region], + ); + fn set_desktop_viewport( + &self, + viewports: &[&Region], + ); + fn set_workarea( + &self, + workareas: &[&Region], + ); + fn update_desktops( + &self, + desktop_names: &[&str], + ); + fn update_client_list( + &self, + clients: &[Window], + ); + fn update_client_list_stacking( + &self, + clients: &[Window], + ); + fn get_window_strut( + &self, + window: Window, + ) -> Option<Vec<Option<Strut>>>; + fn get_window_strut_partial( + &self, + window: Window, + ) -> Option<Vec<Option<Strut>>>; + fn get_window_desktop( + &self, + window: Window, + ) -> Option<usize>; + fn get_window_preferred_type( + &self, + window: Window, + ) -> WindowType; + fn get_window_types( + &self, + window: Window, + ) -> Vec<WindowType>; + fn get_window_preferred_state( + &self, + window: Window, + ) -> Option<WindowState>; + fn get_window_states( + &self, + window: Window, + ) -> Vec<WindowState>; + fn window_is_fullscreen( + &self, + window: Window, + ) -> bool; + fn window_is_above( + &self, + window: Window, + ) -> bool; + fn window_is_below( + &self, + window: Window, + ) -> bool; + fn window_is_sticky( + &self, + window: Window, + ) -> bool; +} diff --git a/src/winsys/event.rs b/src/winsys/event.rs @@ -0,0 +1,127 @@ +pub use crate::Result; + +use crate::common::Dim; +use crate::common::Grip; +use crate::common::Pos; +use crate::common::Region; +use crate::common::Window; +use crate::common::WindowState; +use crate::common::WindowType; +use crate::input::KeyCode; +use crate::input::MouseEvent; +use crate::screen::Screen; + +#[derive(Debug, Clone)] +pub enum Event { + Mouse { + event: MouseEvent, + }, + Key { + key_code: KeyCode, + }, + MapRequest { + window: Window, + ignore: bool, + }, + Map { + window: Window, + ignore: bool, + }, + Enter { + window: Window, + root_rpos: Pos, + window_rpos: Pos, + }, + Leave { + window: Window, + root_rpos: Pos, + window_rpos: Pos, + }, + Destroy { + window: Window, + }, + Expose { + window: Window, + }, + Unmap { + window: Window, + ignore: bool, + }, + StateRequest { + window: Window, + state: WindowState, + action: ToggleAction, + on_root: bool, + }, + FocusRequest { + window: Window, + on_root: bool, + }, + CloseRequest { + window: Window, + on_root: bool, + }, + WorkspaceRequest { + window: Option<Window>, + index: usize, + on_root: bool, + }, + PlacementRequest { + window: Window, + pos: Option<Pos>, + dim: Option<Dim>, + on_root: bool, + }, + GripRequest { + window: Window, + pos: Pos, + grip: Option<Grip>, + on_root: bool, + }, + RestackRequest { + window: Window, + sibling: Window, + mode: StackMode, + on_root: bool, + }, + Configure { + window: Window, + region: Region, + on_root: bool, + }, + Property { + window: Window, + kind: PropertyKind, + on_root: bool, + }, + FrameExtentsRequest { + window: Window, + on_root: bool, + }, + Mapping { + request: u8, + }, + ScreenChange, + Randr, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum StackMode { + Above, + Below, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum ToggleAction { + Toggle, + Add, + Remove, +} + +#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum PropertyKind { + Name, + Class, + Size, + Strut, +} diff --git a/src/winsys/input.rs b/src/winsys/input.rs @@ -0,0 +1,121 @@ +pub use crate::Result; + +use crate::common::Pos; +use crate::common::Window; + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::vec::Vec; + +use strum::EnumIter; +use strum::IntoEnumIterator; + +pub type CodeMap = HashMap<String, u8>; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct KeyCode { + pub mask: u16, + pub code: u8, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum Button { + Left, + Middle, + Right, + ScrollUp, + ScrollDown, + Backward, + Forward, +} + +#[derive(Debug, PartialEq, EnumIter, Eq, Hash, Clone, Copy, PartialOrd, Ord)] +pub enum Modifier { + Ctrl, + Shift, + Alt, + AltGr, + Meta, + NumLock, + ScrollLock, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct MouseShortcut { + pub button: Button, + pub modifiers: Vec<Modifier>, +} + +impl MouseShortcut { + pub fn new( + button: Button, + mut modifiers: Vec<Modifier>, + ) -> Self { + modifiers.sort(); + Self { + button, + modifiers, + } + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum MouseEventKind { + Press, + Release, + Motion, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum EventTarget { + Global, + Root, + Client, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct MouseEventKey { + pub kind: MouseEventKind, + pub target: EventTarget, +} + +#[derive(Debug, Clone)] +pub struct MouseEvent { + pub kind: MouseEventKind, + pub window: Window, + pub subwindow: Option<Window>, + pub on_root: bool, + pub root_rpos: Pos, + pub window_rpos: Pos, + pub shortcut: MouseShortcut, +} + +impl MouseEvent { + pub fn new( + kind: MouseEventKind, + window: Window, + subwindow: Option<Window>, + root: Window, + root_rx: i16, + root_ry: i16, + window_rx: i16, + window_ry: i16, + shortcut: MouseShortcut, + ) -> Self { + Self { + kind, + window, + subwindow, + on_root: window == root, + root_rpos: Pos { + x: root_rx as i32, + y: root_ry as i32, + }, + window_rpos: Pos { + x: window_rx as i32, + y: window_ry as i32, + }, + shortcut, + } + } +} diff --git a/src/winsys/mod.rs b/src/winsys/mod.rs @@ -0,0 +1,12 @@ +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] + +pub type Result<T> = anyhow::Result<T>; + +pub mod common; +pub mod connection; +pub mod event; +pub mod input; +pub mod screen; +pub mod xdata; diff --git a/src/winsys/screen.rs b/src/winsys/screen.rs @@ -0,0 +1,273 @@ +use crate::common::Dim; +use crate::common::Edge; +use crate::common::Pos; +use crate::common::Region; +use crate::common::Strut; +use crate::common::Window; + +use std::{ + collections::HashMap, + sync::atomic::{AtomicUsize, Ordering}, + vec::Vec, +}; + +#[derive(Debug, Clone)] +pub struct Screen { + number: usize, + full_region: Region, + placeable_region: Region, + windows: HashMap<Window, Vec<Edge>>, + struts: HashMap<Edge, Vec<Strut>>, + showing_struts: bool, +} + +impl std::cmp::PartialEq<Self> for Screen { + fn eq( + &self, + other: &Self, + ) -> bool { + self.number == other.number + } +} + +impl Screen { + pub fn new( + region: Region, + number: usize, + ) -> Self { + Screen::init(Self { + number, + full_region: region, + placeable_region: region, + windows: HashMap::new(), + struts: HashMap::with_capacity(4), + showing_struts: true, + }) + } + + fn init(mut self) -> Self { + self.struts.insert(Edge::Left, Vec::with_capacity(1)); + self.struts.insert(Edge::Right, Vec::with_capacity(1)); + self.struts.insert(Edge::Top, Vec::with_capacity(1)); + self.struts.insert(Edge::Bottom, Vec::with_capacity(1)); + + self + } + + pub fn showing_struts(&self) -> bool { + self.showing_struts + } + + pub fn number(&self) -> usize { + self.number + } + + pub fn set_number( + &mut self, + number: usize, + ) { + self.number = number + } + + pub fn show_and_yield_struts(&mut self) -> Vec<Window> { + self.showing_struts = true; + self.compute_placeable_region(); + + self.windows.keys().cloned().collect() + } + + pub fn hide_and_yield_struts(&mut self) -> Vec<Window> { + self.showing_struts = false; + self.compute_placeable_region(); + + self.windows.keys().cloned().collect() + } + + pub fn full_region(&self) -> Region { + self.full_region + } + + pub fn placeable_region(&self) -> Region { + self.placeable_region + } + + pub fn contains_window( + &self, + window: Window, + ) -> bool { + self.windows.contains_key(&window) + } + + pub fn compute_placeable_region(&mut self) { + let mut region = self.full_region; + + if self.showing_struts { + if let Some(strut) = self.struts.get(&Edge::Left).unwrap().last() { + region.pos.x += strut.width as i32; + region.dim.w -= strut.width; + } + + if let Some(strut) = self.struts.get(&Edge::Right).unwrap().last() { + region.dim.w -= strut.width; + } + + if let Some(strut) = self.struts.get(&Edge::Top).unwrap().last() { + region.pos.y += strut.width as i32; + region.dim.h -= strut.width; + } + + if let Some(strut) = self.struts.get(&Edge::Bottom).unwrap().last() + { + region.dim.h -= strut.width; + } + } + + self.placeable_region = region; + } + + pub fn add_strut( + &mut self, + edge: Edge, + window: Window, + width: u32, + ) { + let strut = self.struts.get_mut(&edge).unwrap(); + let index = strut.binary_search_by(|s| s.width.cmp(&width)); + + strut.insert(index.unwrap_or_else(|e| e), Strut::new(window, width)); + self.windows.entry(window).or_insert(vec![edge]).push(edge); + } + + pub fn add_struts( + &mut self, + struts: Vec<Option<Strut>>, + ) { + if let Some(left_strut) = struts[0] { + self.add_strut(Edge::Left, left_strut.window, left_strut.width); + } + + if let Some(right_strut) = struts[1] { + self.add_strut(Edge::Right, right_strut.window, right_strut.width); + } + + if let Some(top_strut) = struts[2] { + self.add_strut(Edge::Top, top_strut.window, top_strut.width); + } + + if let Some(bottom_strut) = struts[3] { + self.add_strut( + Edge::Bottom, + bottom_strut.window, + bottom_strut.width, + ); + } + } + + pub fn remove_window_strut( + &mut self, + window: Window, + ) { + for (_, struts) in &mut self.struts { + // a window may have strut at multiple screen edges + struts.retain(|s| s.window != window); + } + + self.windows.remove(&window); + } + + pub fn update_strut( + &mut self, + edge: Edge, + window: Window, + width: u32, + ) { + self.remove_window_strut(window); + self.add_strut(edge, window, width); + } + + pub fn max_strut_val( + &self, + edge: Edge, + ) -> Option<u32> { + match edge { + Edge::Left => { + if let Some(strut) = + self.struts.get(&Edge::Left).unwrap().last() + { + return Some(strut.width); + } + }, + Edge::Right => { + if let Some(strut) = + self.struts.get(&Edge::Right).unwrap().last() + { + return Some(strut.width); + } + }, + Edge::Top => { + if let Some(strut) = self.struts.get(&Edge::Top).unwrap().last() + { + return Some(strut.width); + } + }, + Edge::Bottom => { + if let Some(strut) = + self.struts.get(&Edge::Bottom).unwrap().last() + { + return Some(strut.width); + } + }, + }; + + None + } + + pub fn has_strut_window( + &self, + window: Window, + ) -> bool { + self.windows.contains_key(&window) + } + + pub fn full_encompasses( + &self, + pos: Pos, + ) -> bool { + self.full_region.encompasses(pos) + } + + pub fn placeable_encompasses( + &self, + pos: Pos, + ) -> bool { + self.placeable_region.encompasses(pos) + } + + pub fn full_contains( + &self, + region: Region, + ) -> bool { + self.full_region.contains(region) + } + + pub fn placeable_contains( + &self, + region: Region, + ) -> bool { + self.placeable_region.contains(region) + } + + pub fn full_occludes( + &self, + region: Region, + ) -> bool { + self.full_region.occludes(region) + } + + pub fn placeable_occludes( + &self, + region: Region, + ) -> bool { + self.placeable_region.occludes(region) + } +} diff --git a/src/winsys/xdata/event.rs b/src/winsys/xdata/event.rs @@ -0,0 +1,12 @@ +use super::super::event::Result; +pub use super::super::event::*; + +use crate::common::Pos; +use crate::common::Region; +use crate::common::Window; +use crate::common::WindowState; +use crate::common::WindowType; +use crate::input::KeyCode; +use crate::screen::Screen; + +use x11rb::protocol::xproto; diff --git a/src/winsys/xdata/input.rs b/src/winsys/xdata/input.rs @@ -0,0 +1,197 @@ +use super::super::input::Result; +pub use super::super::input::*; + +use crate::common::Pos; +use crate::common::Window; + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::vec::Vec; + +use strum::EnumIter; +use strum::IntoEnumIterator; + +use anyhow::anyhow; + +use x11rb::protocol::xproto::ButtonPressEvent; +use x11rb::protocol::xproto::ButtonReleaseEvent; +use x11rb::protocol::xproto::KeyPressEvent; +use x11rb::protocol::xproto::ModMask; +use x11rb::protocol::xproto::MotionNotifyEvent; + +impl KeyCode { + pub fn from_press_event(event: &KeyPressEvent) -> Self { + Self { + mask: event.state, + code: event.detail, + } + } + + pub fn without_mask( + &self, + mask: ModMask, + ) -> Self { + Self { + mask: self.mask & !(u16::from(mask)), + code: self.code, + } + } +} + +impl From<Button> for u8 { + fn from(button: Button) -> u8 { + match button { + Button::Left => 1, + Button::Middle => 2, + Button::Right => 3, + Button::ScrollUp => 4, + Button::ScrollDown => 5, + Button::Backward => 8, + Button::Forward => 9, + } + } +} + +impl TryFrom<u8> for Button { + type Error = anyhow::Error; + + fn try_from(val: u8) -> Result<Self> { + match val { + 1 => Ok(Self::Left), + 2 => Ok(Self::Middle), + 3 => Ok(Self::Right), + 4 => Ok(Self::ScrollUp), + 5 => Ok(Self::ScrollDown), + 8 => Ok(Self::Backward), + 9 => Ok(Self::Forward), + _ => Err(anyhow!("no matching button for value {}", val)), + } + } +} + +impl Modifier { + pub fn was_held( + &self, + mask: u16, + ) -> bool { + mask & u16::from(*self) > 0 + } +} + +impl From<Modifier> for u16 { + fn from(modifier: Modifier) -> u16 { + u16::from(match modifier { + Modifier::Ctrl => ModMask::CONTROL, + Modifier::Shift => ModMask::SHIFT, + Modifier::Alt => ModMask::M1, + Modifier::Meta => ModMask::M4, + Modifier::AltGr => ModMask::M3, + Modifier::NumLock => ModMask::M2, + Modifier::ScrollLock => ModMask::M5, + }) + } +} + +impl TryFrom<&str> for Modifier { + type Error = anyhow::Error; + + fn try_from(val: &str) -> Result<Self> { + match val { + "C" => Ok(Self::Ctrl), + "A" => Ok(Self::Alt), + "S" => Ok(Self::Shift), + "M" => Ok(Self::Meta), + "AltGr" => Ok(Self::Alt), + "Num" => Ok(Self::NumLock), + "Scroll" => Ok(Self::ScrollLock), + _ => Err(anyhow!("unable to resolve \"{}\" to modifier", val)), + } + } +} + +impl MouseShortcut { + pub fn from_event( + detail: u8, + state: u16, + ) -> Result<Self> { + Ok(Self { + button: Button::try_from(detail)?, + modifiers: Modifier::iter().filter(|m| m.was_held(state)).collect(), + }) + } + + pub fn mask(&self) -> u16 { + self.modifiers + .iter() + .fold(0, |acc, &val| acc | u16::from(val)) + } + + pub fn button(&self) -> u8 { + self.button.into() + } +} + +impl MouseEvent { + pub fn from_press_event( + event: &ButtonPressEvent, + root: Window, + ) -> Result<Self> { + Ok(Self::new( + MouseEventKind::Press, + event.event, + if event.child != x11rb::NONE { + Some(event.child) + } else { + None + }, + root, + event.root_x, + event.root_y, + event.event_x, + event.event_y, + MouseShortcut::from_event(event.detail, event.state)?, + )) + } + + pub fn from_release_event( + event: &ButtonReleaseEvent, + root: Window, + ) -> Result<Self> { + Ok(Self::new( + MouseEventKind::Release, + event.event, + if event.child != x11rb::NONE { + Some(event.child) + } else { + None + }, + root, + event.root_x, + event.root_y, + event.event_x, + event.event_y, + MouseShortcut::from_event(event.detail, event.state)?, + )) + } + + pub fn from_motion_event( + event: &MotionNotifyEvent, + root: Window, + ) -> Result<Self> { + Ok(Self::new( + MouseEventKind::Motion, + event.event, + if event.child != x11rb::NONE { + Some(event.child) + } else { + None + }, + root, + event.root_x, + event.root_y, + event.event_x, + event.event_y, + MouseShortcut::from_event(1, event.state)?, + )) + } +} diff --git a/src/winsys/xdata/mod.rs b/src/winsys/xdata/mod.rs @@ -0,0 +1,3 @@ +pub mod event; +pub mod input; +pub mod xconnection; diff --git a/src/winsys/xdata/xconnection.rs b/src/winsys/xdata/xconnection.rs @@ -0,0 +1,2980 @@ +use crate::common::Atom; +use crate::common::Corner; +use crate::common::Dim; +use crate::common::Edge; +use crate::common::Extents; +use crate::common::Grip; +use crate::common::Hints; +use crate::common::IcccmWindowState; +use crate::common::Pid; +use crate::common::Pos; +use crate::common::Ratio; +use crate::common::Region; +use crate::common::SizeHints; +use crate::common::Strut; +use crate::common::Window; +use crate::common::WindowState; +use crate::common::WindowType; +use crate::connection::Connection; +use crate::event::Event; +use crate::event::PropertyKind; +use crate::event::StackMode; +use crate::event::ToggleAction; +use crate::input::Button; +use crate::input::KeyCode; +use crate::input::MouseEvent; +use crate::input::MouseEventKey; +use crate::input::MouseShortcut; +use crate::screen::Screen; +use crate::Result; + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::str::FromStr; + +use anyhow::anyhow; +use strum::*; + +use x11rb::connection; +use x11rb::cursor::Handle as CursorHandle; +use x11rb::errors::ReplyError; +use x11rb::properties; +use x11rb::protocol; +use x11rb::protocol::randr; +use x11rb::protocol::xproto; +use x11rb::protocol::xproto::ConnectionExt; +use x11rb::protocol::xproto::EventMask; +use x11rb::protocol::xproto::ModMask; +use x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT; +use x11rb::protocol::ErrorKind; +use x11rb::protocol::Event as XEvent; +use x11rb::resource_manager::Database; +use x11rb::wrapper::ConnectionExt as _; + +x11rb::atom_manager! { + pub Atoms: AtomsCookie { + Any, + ATOM, + CARDINAL, + WINDOW, + STRING, + UTF8_STRING, + + // ICCCM client properties + WM_NAME, + WM_CLASS, + WM_CLIENT_MACHINE, + WM_PROTOCOLS, + WM_NORMAL_HINTS, + WM_DELETE_WINDOW, + WM_WINDOW_ROLE, + WM_CLIENT_LEADER, + WM_TRANSIENT_FOR, + WM_TAKE_FOCUS, + + // ICCCM window manager properties + WM_STATE, + WM_ICON_SIZE, + + // EWMH root properties + _NET_SUPPORTED, + _NET_CLIENT_LIST, + _NET_CLIENT_LIST_STACKING, + _NET_NUMBER_OF_DESKTOPS, + _NET_DESKTOP_GEOMETRY, + _NET_DESKTOP_VIEWPORT, + _NET_CURRENT_DESKTOP, + _NET_DESKTOP_NAMES, + _NET_ACTIVE_WINDOW, + _NET_WORKAREA, + _NET_SUPPORTING_WM_CHECK, + _NET_VIRTUAL_ROOTS, + _NET_DESKTOP_LAYOUT, + _NET_SHOWING_DESKTOP, + + // EWMH root messages + _NET_CLOSE_WINDOW, + _NET_MOVERESIZE_WINDOW, + _NET_WM_MOVERESIZE, + _NET_REQUEST_FRAME_EXTENTS, + + // EWMH application properties + _NET_WM_NAME, + _NET_WM_VISIBLE_NAME, + _NET_WM_ICON_NAME, + _NET_WM_VISIBLE_ICON_NAME, + _NET_WM_DESKTOP, + _NET_WM_WINDOW_TYPE, + _NET_WM_STATE, + _NET_WM_ALLOWED_ACTIONS, + _NET_WM_STRUT, + _NET_WM_STRUT_PARTIAL, + _NET_WM_ICON_GEOMETRY, + _NET_WM_ICON, + _NET_WM_PID, + _NET_WM_HANDLED_ICONS, + _NET_WM_USER_TIME, + _NET_WM_USER_TIME_WINDOW, + _NET_FRAME_EXTENTS, + _NET_WM_OPAQUE_REGION, + _NET_WM_BYPASS_COMPOSITOR, + + // EWMH window states + _NET_WM_STATE_MODAL, + _NET_WM_STATE_STICKY, + _NET_WM_STATE_MAXIMIZED_VERT, + _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_SHADED, + _NET_WM_STATE_SKIP_TASKBAR, + _NET_WM_STATE_SKIP_PAGER, + _NET_WM_STATE_HIDDEN, + _NET_WM_STATE_FULLSCREEN, + _NET_WM_STATE_ABOVE, + _NET_WM_STATE_BELOW, + _NET_WM_STATE_DEMANDS_ATTENTION, + _NET_WM_STATE_FOCUSED, + + // EWMH window types + _NET_WM_WINDOW_TYPE_DESKTOP, + _NET_WM_WINDOW_TYPE_DOCK, + _NET_WM_WINDOW_TYPE_TOOLBAR, + _NET_WM_WINDOW_TYPE_MENU, + _NET_WM_WINDOW_TYPE_UTILITY, + _NET_WM_WINDOW_TYPE_SPLASH, + _NET_WM_WINDOW_TYPE_DIALOG, + _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + _NET_WM_WINDOW_TYPE_POPUP_MENU, + _NET_WM_WINDOW_TYPE_TOOLTIP, + _NET_WM_WINDOW_TYPE_NOTIFICATION, + _NET_WM_WINDOW_TYPE_COMBO, + _NET_WM_WINDOW_TYPE_DND, + _NET_WM_WINDOW_TYPE_NORMAL, + + // EWMH protocols + _NET_WM_PING, + _NET_WM_SYNC_REQUEST, + _NET_WM_FULLSCREEN_MONITORS, + + // System tray protocols + _NET_SYSTEM_TRAY_ORIENTATION, + _NET_SYSTEM_TRAY_OPCODE, + _NET_SYSTEM_TRAY_ORIENTATION_HORZ, + _NET_SYSTEM_TRAY_S0, + _XEMBED, + _XEMBED_INFO, + } +} + +pub struct XConnection<'a, C: connection::Connection> { + conn: &'a C, + atoms: Atoms, + type_map: HashMap<Atom, WindowType>, + state_map: HashMap<Atom, WindowState>, + screen: xproto::Screen, + check_window: Window, + background_gc: xproto::Gcontext, + database: Option<Database>, + confined_to: Option<Window>, + + root_event_mask: EventMask, + window_event_mask: EventMask, + frame_event_mask: EventMask, + mouse_event_mask: EventMask, + regrab_event_mask: EventMask, +} + +impl<'a, C: connection::Connection> XConnection<'a, C> { + pub fn new( + conn: &'a C, + screen_num: usize, + ) -> Result<Self> { + let screen = conn.setup().roots[screen_num].clone(); + let root = screen.root; + + let aux = xproto::ChangeWindowAttributesAux::default().event_mask( + EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, + ); + + let res = conn.change_window_attributes(screen.root, &aux)?.check(); + + if let Err(ReplyError::X11Error(err)) = res { + if err.error_kind == ErrorKind::Access { + return Err(anyhow!( + "another window manager is already running" + )); + } else { + return Err(anyhow!("unable to set up window manager")); + } + } + + let atoms = Atoms::new(conn)?.reply()?; + let check_window = conn.generate_id()?; + + let type_map: HashMap<Atom, WindowType> = { + let mut types = HashMap::with_capacity(10); + types + .insert(atoms._NET_WM_WINDOW_TYPE_DESKTOP, WindowType::Desktop); + types.insert(atoms._NET_WM_WINDOW_TYPE_DOCK, WindowType::Dock); + types + .insert(atoms._NET_WM_WINDOW_TYPE_TOOLBAR, WindowType::Toolbar); + types.insert(atoms._NET_WM_WINDOW_TYPE_MENU, WindowType::Menu); + types + .insert(atoms._NET_WM_WINDOW_TYPE_UTILITY, WindowType::Utility); + types.insert(atoms._NET_WM_WINDOW_TYPE_SPLASH, WindowType::Splash); + types.insert(atoms._NET_WM_WINDOW_TYPE_DIALOG, WindowType::Dialog); + types.insert( + atoms._NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + WindowType::DropdownMenu, + ); + types.insert( + atoms._NET_WM_WINDOW_TYPE_POPUP_MENU, + WindowType::PopupMenu, + ); + types + .insert(atoms._NET_WM_WINDOW_TYPE_TOOLTIP, WindowType::Tooltip); + types.insert( + atoms._NET_WM_WINDOW_TYPE_NOTIFICATION, + WindowType::Notification, + ); + types.insert(atoms._NET_WM_WINDOW_TYPE_COMBO, WindowType::Combo); + types.insert(atoms._NET_WM_WINDOW_TYPE_DND, WindowType::Dnd); + types.insert(atoms._NET_WM_WINDOW_TYPE_NORMAL, WindowType::Normal); + types + }; + + let state_map: HashMap<Atom, WindowState> = { + let mut states = HashMap::with_capacity(10); + states.insert(atoms._NET_WM_STATE_MODAL, WindowState::Modal); + states.insert(atoms._NET_WM_STATE_STICKY, WindowState::Sticky); + states.insert( + atoms._NET_WM_STATE_MAXIMIZED_VERT, + WindowState::MaximizedVert, + ); + states.insert( + atoms._NET_WM_STATE_MAXIMIZED_HORZ, + WindowState::MaximizedHorz, + ); + states.insert(atoms._NET_WM_STATE_SHADED, WindowState::Shaded); + states.insert( + atoms._NET_WM_STATE_SKIP_TASKBAR, + WindowState::SkipTaskbar, + ); + states + .insert(atoms._NET_WM_STATE_SKIP_PAGER, WindowState::SkipPager); + states.insert(atoms._NET_WM_STATE_HIDDEN, WindowState::Hidden); + states.insert( + atoms._NET_WM_STATE_FULLSCREEN, + WindowState::Fullscreen, + ); + states.insert(atoms._NET_WM_STATE_ABOVE, WindowState::Above); + states.insert(atoms._NET_WM_STATE_BELOW, WindowState::Below); + states.insert( + atoms._NET_WM_STATE_DEMANDS_ATTENTION, + WindowState::DemandsAttention, + ); + states + }; + + conn.create_window( + x11rb::COPY_DEPTH_FROM_PARENT, + check_window, + root, + -1, + -1, + 1, + 1, + 0, + xproto::WindowClass::INPUT_ONLY, + x11rb::COPY_FROM_PARENT, + &xproto::CreateWindowAux::default().override_redirect(1), + )?; + + drop(conn.map_window(check_window)); + + let aux = xproto::ConfigureWindowAux::default() + .stack_mode(xproto::StackMode::BELOW); + + drop(conn.configure_window(check_window, &aux)); + + randr::select_input( + conn, + check_window, + randr::NotifyMask::OUTPUT_CHANGE + | randr::NotifyMask::CRTC_CHANGE + | randr::NotifyMask::SCREEN_CHANGE, + )?; + + let background_gc = conn.generate_id()?; + conn.create_gc( + background_gc, + screen.root, + &xproto::CreateGCAux::default(), + )?; + + let database = Database::new_from_default(conn).ok(); + + if let Some(ref database) = database { + drop(CursorHandle::new(conn, screen_num, &database).map( + |cookie| { + cookie.reply().map(|reply| { + let aux = xproto::ChangeWindowAttributesAux::default() + .cursor(reply.load_cursor(conn, "left_ptr").ok()); + + drop(conn.change_window_attributes(screen.root, &aux)); + }) + }, + )); + } + + let root_event_mask: EventMask = EventMask::PROPERTY_CHANGE + | EventMask::SUBSTRUCTURE_REDIRECT + | EventMask::STRUCTURE_NOTIFY + | EventMask::BUTTON_PRESS + | EventMask::POINTER_MOTION + | EventMask::FOCUS_CHANGE; + + let window_event_mask: EventMask = EventMask::PROPERTY_CHANGE + | EventMask::STRUCTURE_NOTIFY + | EventMask::FOCUS_CHANGE; + + let frame_event_mask: EventMask = EventMask::STRUCTURE_NOTIFY + | EventMask::SUBSTRUCTURE_REDIRECT + | EventMask::SUBSTRUCTURE_NOTIFY + | EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::POINTER_MOTION; + + let mouse_event_mask: EventMask = EventMask::BUTTON_PRESS + | EventMask::BUTTON_RELEASE + | EventMask::BUTTON_MOTION; + + let regrab_event_mask: EventMask = + EventMask::BUTTON_PRESS | EventMask::BUTTON_RELEASE; + + Self::init(Self { + conn, + atoms, + type_map, + state_map, + screen, + check_window, + background_gc, + database, + confined_to: None, + + root_event_mask, + window_event_mask, + frame_event_mask, + mouse_event_mask, + regrab_event_mask, + }) + } + + #[inline] + fn init(connection: Self) -> Result<Self> { + Ok(connection) + } + + pub fn window_is_any_of_types( + &self, + window: Window, + types: &[Atom], + ) -> bool { + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_WINDOW_TYPE, + self.atoms.ATOM, + 0, + std::u32::MAX, + ) + .map_or(false, |cookie| { + cookie.reply().map_or(false, |reply| { + reply.value32().map_or(false, |mut window_types| { + window_types.any(|type_| types.contains(&type_)) + }) + }) + }) + } + + pub fn window_is_any_of_state( + &self, + window: Window, + states: &[Atom], + ) -> bool { + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_STATE, + self.atoms.ATOM, + 0, + std::u32::MAX, + ) + .map_or(false, |cookie| { + cookie.reply().map_or(false, |reply| { + reply.value32().map_or(false, |mut window_states| { + window_states.any(|state| states.contains(&state)) + }) + }) + }) + } + + pub fn window_has_any_of_protocols( + &self, + window: Window, + protocols: &[Atom], + ) -> bool { + self.conn + .get_property( + false, + window, + self.atoms.WM_PROTOCOLS, + self.atoms.ATOM, + 0, + std::u32::MAX, + ) + .map_or(false, |cookie| { + cookie.reply().map_or(false, |reply| { + reply.value32().map_or(false, |mut window_protocols| { + window_protocols + .any(|protocol| protocols.contains(&protocol)) + }) + }) + }) + } + + #[inline] + fn send_client_message( + &self, + window: Window, + atom: Atom, + type_: Atom, + ) -> Result<()> { + let data = [atom, x11rb::CURRENT_TIME, 0, 0, 0]; + + let event = xproto::ClientMessageEvent { + response_type: CLIENT_MESSAGE_EVENT, + format: 32, + sequence: 0, + window, + type_, + data: data.into(), + }; + + self.conn + .send_event(false, window, EventMask::NO_EVENT, &event)?; + + Ok(()) + } + + #[inline] + fn send_protocol_client_message( + &self, + window: Window, + atom: Atom, + ) -> Result<()> { + self.send_client_message(window, atom, self.atoms.WM_PROTOCOLS) + } + + #[inline] + fn get_window_state_from_atom( + &self, + atom: Atom, + ) -> Option<WindowState> { + self.state_map.get(&atom).map(|&state| state) + } + + #[inline] + fn get_atom_from_window_state( + &self, + state: WindowState, + ) -> Atom { + match state { + WindowState::Modal => self.atoms._NET_WM_STATE_MODAL, + WindowState::Sticky => self.atoms._NET_WM_STATE_STICKY, + WindowState::MaximizedVert => { + self.atoms._NET_WM_STATE_MAXIMIZED_VERT + }, + WindowState::MaximizedHorz => { + self.atoms._NET_WM_STATE_MAXIMIZED_HORZ + }, + WindowState::Shaded => self.atoms._NET_WM_STATE_SHADED, + WindowState::SkipTaskbar => self.atoms._NET_WM_STATE_SKIP_TASKBAR, + WindowState::SkipPager => self.atoms._NET_WM_STATE_SKIP_PAGER, + WindowState::Hidden => self.atoms._NET_WM_STATE_HIDDEN, + WindowState::Fullscreen => self.atoms._NET_WM_STATE_FULLSCREEN, + WindowState::Above => self.atoms._NET_WM_STATE_ABOVE, + WindowState::Below => self.atoms._NET_WM_STATE_BELOW, + WindowState::DemandsAttention => { + self.atoms._NET_WM_STATE_DEMANDS_ATTENTION + }, + } + } + + #[inline] + fn get_window_type_from_atom( + &self, + atom: Atom, + ) -> Option<WindowType> { + self.type_map.get(&atom).map(|&type_| type_) + } + + #[inline] + fn get_atom_from_window_type( + &self, + type_: WindowType, + ) -> Atom { + match type_ { + WindowType::Desktop => self.atoms._NET_WM_WINDOW_TYPE_DESKTOP, + WindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK, + WindowType::Toolbar => self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR, + WindowType::Menu => self.atoms._NET_WM_WINDOW_TYPE_MENU, + WindowType::Utility => self.atoms._NET_WM_WINDOW_TYPE_UTILITY, + WindowType::Splash => self.atoms._NET_WM_WINDOW_TYPE_SPLASH, + WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG, + WindowType::DropdownMenu => { + self.atoms._NET_WM_WINDOW_TYPE_DROPDOWN_MENU + }, + WindowType::PopupMenu => self.atoms._NET_WM_WINDOW_TYPE_POPUP_MENU, + WindowType::Tooltip => self.atoms._NET_WM_WINDOW_TYPE_TOOLTIP, + WindowType::Notification => { + self.atoms._NET_WM_WINDOW_TYPE_NOTIFICATION + }, + WindowType::Combo => self.atoms._NET_WM_WINDOW_TYPE_COMBO, + WindowType::Dnd => self.atoms._NET_WM_WINDOW_TYPE_DND, + WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL, + } + } + + fn set_window_state_atom( + &self, + window: Window, + state_atom: Atom, + on: bool, + ) { + if on { + if self.window_is_any_of_state(window, &[state_atom]) { + return; + } + + drop(self.conn.change_property32( + xproto::PropMode::APPEND, + window, + self.atoms._NET_WM_STATE, + xproto::AtomEnum::ATOM, + &[state_atom], + )); + } else { + let mut states = self + .conn + .get_property( + false, + window, + self.atoms._NET_WM_STATE, + self.atoms.ATOM, + 0, + std::u32::MAX, + ) + .map_or(Vec::new(), |cookie| { + cookie.reply().map_or(Vec::new(), |reply| { + reply.value32().map_or(Vec::new(), |window_states| { + let mut states = + Vec::with_capacity(reply.value_len as usize); + window_states.for_each(|state| states.push(state)); + states + }) + }) + }); + + states.retain(|&state| state != state_atom); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + window, + self.atoms._NET_WM_STATE, + xproto::AtomEnum::ATOM, + &states, + )); + } + } + + #[inline] + fn on_button_press( + &self, + event: &xproto::ButtonPressEvent, + ) -> Option<Event> { + Some(Event::Mouse { + event: MouseEvent::from_press_event(&event, self.screen.root) + .ok()?, + }) + } + + #[inline] + fn on_button_release( + &self, + event: &xproto::ButtonReleaseEvent, + ) -> Option<Event> { + Some(Event::Mouse { + event: MouseEvent::from_release_event(&event, self.screen.root) + .ok()?, + }) + } + + #[inline] + fn on_motion_notify( + &self, + event: &xproto::MotionNotifyEvent, + ) -> Option<Event> { + Some(Event::Mouse { + event: MouseEvent::from_motion_event(&event, self.screen.root) + .ok()?, + }) + } + + #[inline] + fn on_key_press( + &self, + event: &xproto::KeyPressEvent, + ) -> Option<Event> { + Some(Event::Key { + key_code: KeyCode::from_press_event(&event) + .without_mask(ModMask::M2), + }) + } + + #[inline] + fn on_map_request( + &self, + event: &xproto::MapRequestEvent, + ) -> Option<Event> { + Some(Event::MapRequest { + window: event.window, + ignore: !self.must_manage_window(event.window), + }) + } + + #[inline] + fn on_map_notify( + &self, + event: &xproto::MapNotifyEvent, + ) -> Option<Event> { + Some(Event::Map { + window: event.window, + ignore: !self.must_manage_window(event.window), + }) + } + + #[inline] + fn on_enter_notify( + &self, + event: &xproto::EnterNotifyEvent, + ) -> Option<Event> { + Some(Event::Enter { + window: event.event, + root_rpos: Pos { + x: event.root_x as i32, + y: event.root_y as i32, + }, + window_rpos: Pos { + x: event.event_x as i32, + y: event.event_y as i32, + }, + }) + } + + #[inline] + fn on_leave_notify( + &self, + event: &xproto::LeaveNotifyEvent, + ) -> Option<Event> { + Some(Event::Leave { + window: event.event, + root_rpos: Pos { + x: event.root_x as i32, + y: event.root_y as i32, + }, + window_rpos: Pos { + x: event.event_x as i32, + y: event.event_y as i32, + }, + }) + } + + #[inline] + fn on_destroy_notify( + &self, + event: &xproto::DestroyNotifyEvent, + ) -> Option<Event> { + Some(Event::Destroy { + window: event.window, + }) + } + + #[inline] + fn on_expose( + &self, + event: &xproto::ExposeEvent, + ) -> Option<Event> { + Some(Event::Expose { + window: event.window, + }) + } + + #[inline] + fn on_unmap_notify( + &self, + event: &xproto::UnmapNotifyEvent, + ) -> Option<Event> { + self.conn + .get_window_attributes(event.window) + .map(|cookie| Event::Unmap { + window: event.window, + ignore: cookie + .reply() + .map_or(false, |reply| reply.override_redirect), + }) + .ok() + } + + #[inline] + fn on_configure_request( + &self, + event: &xproto::ConfigureRequestEvent, + ) -> Option<Event> { + let geometry = self.get_window_geometry(event.window).ok()?; + + let mut x = None; + let mut y = None; + let mut w = None; + let mut h = None; + + if event.value_mask & u16::from(xproto::ConfigWindow::X) != 0 { + x = Some(event.x as i32); + } + + if event.value_mask & u16::from(xproto::ConfigWindow::Y) != 0 { + y = Some(event.y as i32); + } + + if event.value_mask & u16::from(xproto::ConfigWindow::WIDTH) != 0 { + w = Some(event.width as u32); + } + + if event.value_mask & u16::from(xproto::ConfigWindow::HEIGHT) != 0 { + h = Some(event.height as u32); + } + + let pos = match (x, y) { + (Some(x), Some(y)) => Some(Pos { + x, + y, + }), + (None, Some(y)) => Some(Pos { + x: geometry.pos.x, + y, + }), + (Some(x), None) => Some(Pos { + x, + y: geometry.pos.y, + }), + _ => None, + }; + + let dim = match (w, h) { + (Some(w), Some(h)) => Some(Dim { + w, + h, + }), + (None, Some(h)) => Some(Dim { + w: geometry.dim.w, + h, + }), + (Some(w), None) => Some(Dim { + w, + h: geometry.dim.h, + }), + _ => None, + }; + + if pos.is_some() || dim.is_some() { + return Some(Event::PlacementRequest { + window: event.window, + pos, + dim, + on_root: event.window == self.screen.root, + }); + } + + if event.value_mask & u16::from(xproto::ConfigWindow::STACK_MODE) != 0 { + if event.sibling != x11rb::NONE { + match event.stack_mode { + // window is placed above sibling + xproto::StackMode::ABOVE => { + return Some(Event::RestackRequest { + window: event.window, + sibling: event.sibling, + mode: StackMode::Above, + on_root: event.window == self.screen.root, + }); + }, + // sibling is placed above window + xproto::StackMode::BELOW => { + return Some(Event::RestackRequest { + window: event.window, + sibling: event.sibling, + mode: StackMode::Below, + on_root: event.window == self.screen.root, + }); + }, + _ => {}, + } + } + } + + None + } + + #[inline] + fn on_configure_notify( + &self, + event: &xproto::ConfigureNotifyEvent, + ) -> Option<Event> { + Some(Event::Configure { + window: event.window, + region: Region::new( + event.x as i32, + event.y as i32, + event.width as u32, + event.height as u32, + ), + on_root: event.window == self.screen.root, + }) + } + + #[inline] + fn on_property_notify( + &self, + event: &xproto::PropertyNotifyEvent, + ) -> Option<Event> { + if event.state == xproto::Property::NEW_VALUE { + if event.atom == self.atoms.WM_NAME + || event.atom == self.atoms._NET_WM_NAME + { + return Some(Event::Property { + window: event.window, + kind: PropertyKind::Name, + on_root: event.window == self.screen.root, + }); + } + + if event.atom == self.atoms.WM_CLASS { + return Some(Event::Property { + window: event.window, + kind: PropertyKind::Class, + on_root: event.window == self.screen.root, + }); + } + + if event.atom == self.atoms.WM_NORMAL_HINTS { + return Some(Event::Property { + window: event.window, + kind: PropertyKind::Size, + on_root: event.window == self.screen.root, + }); + } + } + + if event.atom == self.atoms._NET_WM_STRUT + || event.atom == self.atoms._NET_WM_STRUT_PARTIAL + { + return Some(Event::Property { + window: event.window, + kind: PropertyKind::Strut, + on_root: event.window == self.screen.root, + }); + } + + None + } + + #[inline] + fn on_client_message( + &self, + event: &xproto::ClientMessageEvent, + ) -> Option<Event> { + let data = match event.format { + 8 => event.data.as_data8().iter().map(|&i| i as usize).collect(), + 16 => event.data.as_data16().iter().map(|&i| i as usize).collect(), + 32 => event.data.as_data32().iter().map(|&i| i as usize).collect(), + _ => Vec::new(), + }; + + if event.type_ == self.atoms._NET_WM_STATE { + if event.format != 32 || data.len() < 3 { + return None; + } + + let mut state = None; + + for i in 1..=2 { + if state.is_none() { + if data[i] != 0 { + state = + self.get_window_state_from_atom(data[i] as Atom); + } + } + } + + if let Some(state) = state { + return Some(Event::StateRequest { + window: event.window, + state, + action: match data[0] { + 0 => ToggleAction::Remove, + 1 => ToggleAction::Add, + 2 => ToggleAction::Toggle, + _ => return None, + }, + on_root: event.window == self.screen.root, + }); + } + } else if event.type_ == self.atoms._NET_MOVERESIZE_WINDOW { + // TODO: handle gravity + let x = data.get(1); + let y = data.get(2); + let width = data.get(3); + let height = data.get(4); + + if x.is_none() || y.is_none() || width.is_none() || height.is_none() + { + return None; + } + + let x = *x.unwrap(); + let y = *y.unwrap(); + let width = *width.unwrap(); + let height = *height.unwrap(); + + return Some(Event::PlacementRequest { + window: event.window, + pos: Some(Pos { + x: x as i32, + y: y as i32, + }), + dim: Some(Dim { + w: width as u32, + h: height as u32, + }), + on_root: event.window == self.screen.root, + }); + } else if event.type_ == self.atoms._NET_WM_MOVERESIZE { + let x_root = data.get(0); + let y_root = data.get(1); + let direction = data.get(2); + + if x_root.is_none() || y_root.is_none() || direction.is_none() { + return None; + } + + let x_root = *x_root.unwrap(); + let y_root = *y_root.unwrap(); + let direction = *direction.unwrap(); + + return Some(Event::GripRequest { + window: event.window, + pos: Pos { + x: x_root as i32, + y: y_root as i32, + }, + grip: match direction { + 0 => Some(Grip::Corner(Corner::TopLeft)), + 1 => Some(Grip::Edge(Edge::Top)), + 2 => Some(Grip::Corner(Corner::TopRight)), + 3 => Some(Grip::Edge(Edge::Right)), + 4 => Some(Grip::Corner(Corner::BottomRight)), + 5 => Some(Grip::Edge(Edge::Bottom)), + 6 => Some(Grip::Corner(Corner::BottomLeft)), + 7 => Some(Grip::Edge(Edge::Left)), + 8 => None, + _ => return None, + }, + on_root: event.window == self.screen.root, + }); + } else if event.type_ == self.atoms._NET_REQUEST_FRAME_EXTENTS { + return Some(Event::FrameExtentsRequest { + window: event.window, + on_root: event.window == self.screen.root, + }); + } else if event.type_ == self.atoms._NET_CURRENT_DESKTOP { + if let Some(&index) = data.get(0) { + return Some(Event::WorkspaceRequest { + window: None, + index, + on_root: event.window == self.screen.root, + }); + } + } else if event.type_ == self.atoms._NET_CLOSE_WINDOW { + return Some(Event::CloseRequest { + window: event.window, + on_root: event.window == self.screen.root, + }); + } else if event.type_ == self.atoms._NET_ACTIVE_WINDOW { + if let Some(&source) = data.get(0) { + if source <= 2 { + return Some(Event::FocusRequest { + window: event.window, + on_root: event.window == self.screen.root, + }); + } + } + } + + None + } + + #[inline] + fn on_mapping_notify( + &self, + event: &xproto::MappingNotifyEvent, + ) -> Option<Event> { + Some(Event::Mapping { + request: u8::from(event.request), + }) + } + + #[inline] + fn on_randr_notify( + &self, + _event: &randr::NotifyEvent, + ) -> Option<Event> { + Some(Event::Randr) + } +} + +impl<'a, C: connection::Connection> Connection for XConnection<'a, C> { + #[inline] + fn flush(&self) -> bool { + self.conn.flush().is_ok() + } + + fn step(&self) -> Option<Event> { + self.conn + .wait_for_event() + .ok() + .and_then(|event| match event { + XEvent::ButtonPress(e) => self.on_button_press(&e), + XEvent::ButtonRelease(e) => self.on_button_release(&e), + XEvent::MotionNotify(e) => self.on_motion_notify(&e), + XEvent::KeyPress(e) => self.on_key_press(&e), + XEvent::MapRequest(e) => self.on_map_request(&e), + XEvent::MapNotify(e) => self.on_map_notify(&e), + XEvent::EnterNotify(e) => self.on_enter_notify(&e), + XEvent::LeaveNotify(e) => self.on_leave_notify(&e), + XEvent::DestroyNotify(e) => self.on_destroy_notify(&e), + XEvent::Expose(e) => self.on_expose(&e), + XEvent::UnmapNotify(e) => self.on_unmap_notify(&e), + XEvent::ConfigureRequest(e) => self.on_configure_request(&e), + XEvent::ConfigureNotify(e) => self.on_configure_notify(&e), + XEvent::PropertyNotify(e) => self.on_property_notify(&e), + XEvent::ClientMessage(e) => self.on_client_message(&e), + XEvent::MappingNotify(e) => self.on_mapping_notify(&e), + XEvent::RandrNotify(e) => self.on_randr_notify(&e), + _ => None, + }) + } + + fn connected_outputs(&self) -> Vec<Screen> { + let resources = + randr::get_screen_resources(self.conn, self.check_window); + + if let Ok(resources) = resources { + if let Ok(reply) = resources.reply() { + return reply + .crtcs + .iter() + .flat_map(|crtc| { + randr::get_crtc_info(self.conn, *crtc, 0) + .map(|cookie| cookie.reply().map(|reply| reply)) + }) + .enumerate() + .map(|(i, r)| { + let r = r.unwrap(); + let region = Region { + pos: Pos { + x: r.x as i32, + y: r.y as i32, + }, + dim: Dim { + w: r.width as u32, + h: r.height as u32, + }, + }; + + Screen::new(region, i) + }) + .filter(|screen| screen.full_region().dim.w > 0) + .collect(); + } + } + + panic!("could not obtain screen resources") + } + + fn top_level_windows(&self) -> Vec<Window> { + self.conn + .query_tree(self.screen.root) + .map_or(Vec::new(), |cookie| { + cookie.reply().map_or(Vec::new(), |reply| { + reply + .children + .iter() + .filter(|&w| self.must_manage_window(*w)) + .cloned() + .collect() + }) + }) + } + + #[inline] + fn get_pointer_position(&self) -> Pos { + self.conn.query_pointer(self.screen.root).map_or( + Pos::default(), + |cookie| { + cookie.reply().map_or(Pos::default(), |reply| Pos { + x: reply.root_x as i32, + y: reply.root_y as i32, + }) + }, + ) + } + + #[inline] + fn warp_pointer_center_of_window_or_root( + &self, + window: Option<Window>, + screen: &Screen, + ) { + let (pos, window) = match window { + Some(window) => { + let geometry = self.get_window_geometry(window); + + if geometry.is_err() { + return; + } + + (Pos::from_center_of_dim(geometry.unwrap().dim), window) + }, + None => ( + Pos::from_center_of_dim(screen.placeable_region().dim), + self.screen.root, + ), + }; + + drop(self.conn.warp_pointer( + x11rb::NONE, + window, + 0, + 0, + 0, + 0, + pos.x as i16, + pos.y as i16, + )); + } + + #[inline] + fn warp_pointer( + &self, + pos: Pos, + ) { + drop(self.conn.warp_pointer( + x11rb::NONE, + self.screen.root, + 0, + 0, + 0, + 0, + pos.x as i16, + pos.y as i16, + )); + } + + fn warp_pointer_rpos( + &self, + window: Window, + pos: Pos, + ) { + drop(self.conn.warp_pointer( + x11rb::NONE, + window, + 0, + 0, + 0, + 0, + pos.x as i16, + pos.y as i16, + )); + } + + #[inline] + fn confine_pointer( + &mut self, + window: Window, + ) { + if self.confined_to.is_none() { + if let Ok(_) = self.conn.grab_pointer( + false, + self.screen.root, + u32::from(EventMask::POINTER_MOTION | EventMask::BUTTON_RELEASE) + as u16, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, + self.screen.root, + x11rb::NONE, + x11rb::CURRENT_TIME, + ) { + drop(self.conn.grab_keyboard( + false, + self.screen.root, + x11rb::CURRENT_TIME, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, + )); + + self.confined_to = Some(window); + } + } + } + + #[inline] + fn release_pointer(&mut self) { + if self.confined_to.is_some() { + drop(self.conn.ungrab_pointer(x11rb::CURRENT_TIME)); + drop(self.conn.ungrab_keyboard(x11rb::CURRENT_TIME)); + + self.confined_to = None; + } + } + + #[inline] + fn is_mapping_request( + &self, + request: u8, + ) -> bool { + request == u8::from(xproto::Mapping::KEYBOARD) + || request == u8::from(xproto::Mapping::MODIFIER) + } + + fn cleanup(&self) { + drop(self.conn.ungrab_key( + xproto::Grab::ANY, + self.screen.root, + xproto::ModMask::ANY, + )); + + drop(self.conn.destroy_window(self.check_window)); + + drop( + self.conn.delete_property( + self.screen.root, + self.atoms._NET_ACTIVE_WINDOW, + ), + ); + + drop(self.conn.delete_property( + self.screen.root, + self.atoms._NET_SUPPORTING_WM_CHECK, + )); + + drop( + self.conn + .delete_property(self.screen.root, self.atoms._NET_WM_NAME), + ); + + drop( + self.conn + .delete_property(self.screen.root, self.atoms.WM_CLASS), + ); + + drop( + self.conn + .delete_property(self.screen.root, self.atoms._NET_SUPPORTED), + ); + + drop( + self.conn + .delete_property(self.screen.root, self.atoms._NET_WM_PID), + ); + + drop( + self.conn + .delete_property(self.screen.root, self.atoms._NET_CLIENT_LIST), + ); + + drop(self.conn); + } + + #[inline] + fn create_frame( + &self, + region: Region, + ) -> Window { + const ERR: &str = "unable to create frame"; + + let frame = self.conn.generate_id().expect(ERR); + let aux = xproto::CreateWindowAux::new() + .backing_store(Some(xproto::BackingStore::ALWAYS)) + .event_mask(EventMask::EXPOSURE | EventMask::KEY_PRESS); + + drop( + self.conn + .create_window( + x11rb::COPY_DEPTH_FROM_PARENT, + frame, + self.screen.root, + region.pos.x as i16, + region.pos.y as i16, + region.dim.w as u16, + region.dim.h as u16, + 0, + xproto::WindowClass::INPUT_OUTPUT, + 0, + &aux, + ) + .expect(ERR), + ); + + self.flush(); + + frame + } + + #[inline] + fn create_handle(&self) -> Window { + const ERR: &str = "unable to create handle"; + + let handle = self.conn.generate_id().expect(ERR); + let aux = xproto::CreateWindowAux::new().override_redirect(1); + + drop( + self.conn + .create_window( + x11rb::COPY_DEPTH_FROM_PARENT, + handle, + self.screen.root, + -2, + -2, + 1, + 1, + 0, + xproto::WindowClass::INPUT_ONLY, + 0, + &aux, + ) + .expect(ERR), + ); + + self.flush(); + + handle + } + + #[inline] + fn init_window( + &self, + window: Window, + focus_follows_mouse: bool, + ) { + let aux = xproto::ChangeWindowAttributesAux::default() + .event_mask(self.window_event_mask); + + drop(self.conn.change_window_attributes(window, &aux)); + } + + #[inline] + fn init_frame( + &self, + window: Window, + focus_follows_mouse: bool, + ) { + let aux = xproto::ChangeWindowAttributesAux::default().event_mask( + self.frame_event_mask + | if focus_follows_mouse { + EventMask::ENTER_WINDOW + } else { + EventMask::NO_EVENT + }, + ); + + drop(self.conn.change_window_attributes(window, &aux)); + } + + #[inline] + fn init_unmanaged( + &self, + window: Window, + ) { + let aux = xproto::ChangeWindowAttributesAux::default() + .event_mask(EventMask::STRUCTURE_NOTIFY); + + drop(self.conn.change_window_attributes(window, &aux)); + } + + #[inline] + fn cleanup_window( + &self, + window: Window, + ) { + drop(self.conn.delete_property(window, self.atoms._NET_WM_STATE)); + drop( + self.conn + .delete_property(window, self.atoms._NET_WM_DESKTOP), + ); + } + + #[inline] + fn map_window( + &self, + window: Window, + ) { + drop(self.conn.map_window(window)); + } + + #[inline] + fn unmap_window( + &self, + window: Window, + ) { + drop(self.conn.unmap_window(window)); + } + + #[inline] + fn reparent_window( + &self, + window: Window, + parent: Window, + pos: Pos, + ) { + drop(self.conn.reparent_window( + window, + parent, + pos.x as i16, + pos.y as i16, + )); + } + + #[inline] + fn unparent_window( + &self, + window: Window, + pos: Pos, + ) { + drop(self.conn.reparent_window( + window, + self.screen.root, + pos.x as i16, + pos.y as i16, + )); + } + + #[inline] + fn destroy_window( + &self, + window: Window, + ) { + drop(self.conn.destroy_window(window)); + } + + #[inline] + fn close_window( + &self, + window: Window, + ) -> bool { + match self + .send_protocol_client_message(window, self.atoms.WM_DELETE_WINDOW) + { + Ok(_) => self.flush(), + Err(_) => false, + } + } + + #[inline] + fn kill_window( + &self, + window: Window, + ) -> bool { + let protocols = &[self.atoms.WM_DELETE_WINDOW]; + + if self.window_has_any_of_protocols(window, protocols) { + self.close_window(window) + } else { + if self.conn.kill_client(window).is_ok() { + self.flush() + } else { + false + } + } + } + + #[inline] + fn place_window( + &self, + window: Window, + region: &Region, + ) { + let aux = xproto::ConfigureWindowAux::default() + .x(region.pos.x as i32) + .y(region.pos.y as i32) + .width(region.dim.w as u32) + .height(region.dim.h as u32); + + drop(self.conn.configure_window(window, &aux)); + } + + #[inline] + fn move_window( + &self, + window: Window, + pos: Pos, + ) { + let aux = xproto::ConfigureWindowAux::default() + .x(pos.x as i32) + .y(pos.y as i32); + + drop(self.conn.configure_window(window, &aux)); + } + + #[inline] + fn resize_window( + &self, + window: Window, + dim: Dim, + ) { + let aux = xproto::ConfigureWindowAux::default() + .width(dim.w as u32) + .height(dim.h as u32); + + drop(self.conn.configure_window(window, &aux)); + } + + #[inline] + fn focus_window( + &self, + window: Window, + ) { + drop(self.conn.set_input_focus( + xproto::InputFocus::PARENT, + window, + x11rb::CURRENT_TIME, + )); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_ACTIVE_WINDOW, + xproto::AtomEnum::WINDOW, + &[window], + )); + } + + #[inline] + fn stack_window_above( + &self, + window: Window, + sibling: Option<Window>, + ) { + let mut aux = xproto::ConfigureWindowAux::default() + .stack_mode(xproto::StackMode::ABOVE); + + if let Some(sibling) = sibling { + aux = aux.sibling(sibling); + } + + drop(self.conn.configure_window(window, &aux)); + } + + #[inline] + fn stack_window_below( + &self, + window: Window, + sibling: Option<Window>, + ) { + let mut aux = xproto::ConfigureWindowAux::default() + .stack_mode(xproto::StackMode::BELOW); + + if let Some(sibling) = sibling { + aux = aux.sibling(sibling); + } + + drop(self.conn.configure_window(window, &aux)); + } + + #[inline] + fn insert_window_in_save_set( + &self, + window: Window, + ) { + drop(self.conn.change_save_set(xproto::SetMode::INSERT, window)); + } + + fn grab_bindings( + &self, + key_codes: &[KeyCode], + mouse_bindings: &[&(MouseEventKey, MouseShortcut)], + ) { + for m in &[0, u16::from(ModMask::M2)] { + for k in key_codes { + drop(self.conn.grab_key( + false, + self.screen.root, + if *m != 0 { k.mask | *m } else { k.mask }, + k.code, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, + )); + } + + for (_, state) in mouse_bindings { + drop(self.conn.grab_button( + false, + self.screen.root, + u32::from(self.mouse_event_mask) as u16, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, + x11rb::NONE, + x11rb::NONE, + xproto::ButtonIndex::try_from(state.button()).unwrap(), + state.mask() | *m, + )); + } + } + + let aux = xproto::ChangeWindowAttributesAux::default() + .event_mask(self.root_event_mask); + + drop(self.conn.change_window_attributes(self.screen.root, &aux)); + + self.flush(); + } + + #[inline] + fn regrab_buttons( + &self, + window: Window, + ) { + drop(self.conn.grab_button( + true, + window, + u32::from(self.regrab_event_mask) as u16, + xproto::GrabMode::ASYNC, + xproto::GrabMode::ASYNC, + x11rb::NONE, + x11rb::NONE, + xproto::ButtonIndex::ANY, + xproto::ModMask::ANY, + )); + } + + #[inline] + fn ungrab_buttons( + &self, + window: Window, + ) { + drop(self.conn.ungrab_button( + xproto::ButtonIndex::ANY, + window, + xproto::ModMask::ANY, + )); + } + + #[inline] + fn unfocus(&self) { + drop(self.conn.set_input_focus( + xproto::InputFocus::PARENT, + self.check_window, + x11rb::CURRENT_TIME, + )); + + drop( + self.conn.delete_property( + self.screen.root, + self.atoms._NET_ACTIVE_WINDOW, + ), + ); + } + + #[inline] + fn set_window_border_width( + &self, + window: Window, + width: u32, + ) { + let aux = xproto::ConfigureWindowAux::default().border_width(width); + + drop(self.conn.configure_window(window, &aux)); + } + + #[inline] + fn set_window_border_color( + &self, + window: Window, + color: u32, + ) { + let aux = + xproto::ChangeWindowAttributesAux::default().border_pixel(color); + + drop(self.conn.change_window_attributes(window, &aux)); + } + + #[inline] + fn set_window_background_color( + &self, + window: Window, + color: u32, + ) { + if let Ok(geometry) = self.get_window_geometry(window) { + drop(self.conn.change_gc( + self.background_gc, + &xproto::ChangeGCAux::new().foreground(color), + )); + + drop(self.conn.poly_fill_rectangle(window, self.background_gc, &[ + xproto::Rectangle { + x: 0, + y: 0, + width: geometry.dim.w as u16, + height: geometry.dim.h as u16, + }, + ])); + } + } + + #[inline] + fn update_window_offset( + &self, + window: Window, + frame: Window, + ) { + if let Ok(frame_geometry) = self.get_window_geometry(frame) { + if let Ok(window_geometry) = self.get_window_geometry(window) { + let event = xproto::ConfigureNotifyEvent { + response_type: xproto::CONFIGURE_NOTIFY_EVENT, + sequence: 0, + event: window, + window, + above_sibling: x11rb::NONE, + x: (frame_geometry.pos.x + window_geometry.pos.x) as i16, + y: (frame_geometry.pos.y + window_geometry.pos.y) as i16, + width: window_geometry.dim.w as u16, + height: window_geometry.dim.h as u16, + border_width: 0, + override_redirect: false, + }; + + drop(self.conn.send_event( + false, + window, + EventMask::STRUCTURE_NOTIFY, + &event, + )); + } + } + } + + #[inline] + fn get_focused_window(&self) -> Window { + self.conn + .get_input_focus() + .map_or(self.screen.root, |cookie| { + cookie.reply().map_or(self.screen.root, |reply| reply.focus) + }) + } + + #[inline] + fn get_window_geometry( + &self, + window: Window, + ) -> Result<Region> { + let geometry = self.conn.get_geometry(window)?.reply()?; + + Ok(Region::new( + geometry.x as i32, + geometry.y as i32, + geometry.width as u32, + geometry.height as u32, + )) + } + + #[inline] + fn get_window_pid( + &self, + window: Window, + ) -> Option<Pid> { + let id_spec = protocol::res::ClientIdSpec { + client: window, + mask: u8::from(protocol::res::ClientIdMask::LOCAL_CLIENT_PID) + as u32, + }; + + protocol::res::query_client_ids(self.conn, &[id_spec]) + .ok() + .and_then(|cookie| cookie.reply().ok()) + .and_then(|reply| { + for i in reply.ids { + if (i.spec.mask + & (u8::from( + protocol::res::ClientIdMask::LOCAL_CLIENT_PID, + )) as u32) + != 0 + { + if i.value.len() > 0 && i.value[0] != 0 { + return Some(i.value[0] as Pid); + } + } + } + + None + }) + } + + #[inline] + fn must_manage_window( + &self, + window: Window, + ) -> bool { + let do_not_manage = + self.conn + .get_window_attributes(window) + .map_or(false, |cookie| { + cookie.reply().map_or(false, |reply| { + reply.override_redirect + || reply.class == xproto::WindowClass::INPUT_ONLY + }) + }); + + if do_not_manage { + return false; + } + + let to_exclude = &[ + self.atoms._NET_WM_WINDOW_TYPE_DOCK, + self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR, + ]; + + !self.window_is_any_of_types(window, to_exclude) + } + + #[inline] + fn must_free_window( + &self, + window: Window, + ) -> bool { + let has_float_type = self.window_is_any_of_types(window, &[ + self.atoms._NET_WM_WINDOW_TYPE_DIALOG, + self.atoms._NET_WM_WINDOW_TYPE_UTILITY, + self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR, + self.atoms._NET_WM_WINDOW_TYPE_SPLASH, + ]); + + if has_float_type { + return true; + } + + let has_float_state = self + .window_is_any_of_state(window, &[self.atoms._NET_WM_STATE_MODAL]); + + if has_float_state { + return true; + } + + if let Some(desktop) = self.get_window_desktop(window) { + if desktop == 0xFFFFFFFF { + return true; + } + } + + self.get_window_geometry(window).map_or(false, |geometry| { + let (_, size_hints) = + self.get_icccm_window_size_hints(window, None, &None); + size_hints.map_or(false, |size_hints| { + size_hints.min_width.map_or(false, |min_width| { + size_hints.min_height.map_or(false, |min_height| { + size_hints.max_width.map_or(false, |max_width| { + size_hints.max_height.map_or(false, |max_height| { + max_width > 0 + && max_height > 0 + && max_width == min_width + && max_height == min_height + }) + }) + }) + }) + }) + }) + } + + fn window_is_mappable( + &self, + window: Window, + ) -> bool { + self.conn + .get_window_attributes(window) + .map_or(false, |cookie| { + cookie.reply().map_or(false, |reply| { + let default_state = properties::WmHintsState::Normal; + let initial_state = + properties::WmHints::get(self.conn, window) + .ok() + .map_or(default_state, |cookie| { + cookie.reply().map_or(default_state, |reply| { + reply + .initial_state + .map_or(default_state, |i| i) + }) + }); + + reply.class != xproto::WindowClass::INPUT_ONLY + && !self.window_is_any_of_state(window, &[self + .atoms + ._NET_WM_STATE_HIDDEN]) + && match initial_state { + properties::WmHintsState::Normal => true, + _ => false, + } + }) + }) + } + + #[inline] + fn set_icccm_window_state( + &self, + window: Window, + state: IcccmWindowState, + ) { + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + window, + self.atoms.WM_STATE, + self.atoms.CARDINAL, + &[ + match state { + IcccmWindowState::Withdrawn => 0, + IcccmWindowState::Normal => 1, + IcccmWindowState::Iconic => 3, + }, + 0, + ], + )); + } + + #[inline] + fn set_icccm_window_hints( + &self, + window: Window, + hints: Hints, + ) { + let wm_hints = properties::WmHints { + input: hints.input, + initial_state: match hints.initial_state { + Some(IcccmWindowState::Normal) => { + Some(properties::WmHintsState::Normal) + }, + Some(IcccmWindowState::Iconic) => { + Some(properties::WmHintsState::Iconic) + }, + _ => None, + }, + icon_pixmap: None, + icon_window: None, + icon_position: None, + icon_mask: None, + window_group: hints.group, + urgent: hints.urgent, + }; + + drop(wm_hints.set(self.conn, window)); + } + + #[inline] + fn get_icccm_window_name( + &self, + window: Window, + ) -> String { + const NO_NAME: &str = "n/a"; + + self.conn + .get_property( + false, + window, + self.atoms.WM_NAME, + self.atoms.UTF8_STRING, + 0, + std::u32::MAX, + ) + .map_or(String::from(NO_NAME), |cookie| { + cookie.reply().map_or(String::from(NO_NAME), |reply| { + std::str::from_utf8( + &reply.value8().map_or(Vec::new(), |value| { + value.collect::<Vec<u8>>() + }), + ) + .map_or(String::from(NO_NAME), |name| name.to_string()) + }) + }) + } + + #[inline] + fn get_icccm_window_class( + &self, + window: Window, + ) -> String { + const NO_CLASS: &str = "n/a"; + + properties::WmClass::get(self.conn, window).map_or( + String::from(NO_CLASS), + |cookie| { + cookie.reply().map_or(String::from(NO_CLASS), |reply| { + std::str::from_utf8(reply.class()) + .map_or(String::from(NO_CLASS), |class| { + String::from(class) + }) + }) + }, + ) + } + + #[inline] + fn get_icccm_window_instance( + &self, + window: Window, + ) -> String { + const NO_INSTANCE: &str = "n/a"; + + properties::WmClass::get(self.conn, window).map_or( + String::from(NO_INSTANCE), + |cookie| { + cookie.reply().map_or(String::from(NO_INSTANCE), |reply| { + std::str::from_utf8(reply.instance()) + .map_or(String::from(NO_INSTANCE), |instance| { + String::from(instance) + }) + }) + }, + ) + } + + #[inline] + fn get_icccm_window_transient_for( + &self, + window: Window, + ) -> Option<Window> { + self.conn + .get_property( + false, + window, + self.atoms.WM_TRANSIENT_FOR, + self.atoms.WINDOW, + 0, + std::u32::MAX, + ) + .ok()? + .reply() + .map_or(None, |transient_for| { + let transient_for: Vec<u32> = + transient_for.value32()?.collect(); + + if transient_for.is_empty() { + None + } else { + Some(transient_for[0]) + } + }) + } + + #[inline] + fn get_icccm_window_client_leader( + &self, + window: Window, + ) -> Option<Window> { + self.conn + .get_property( + false, + window, + self.atoms.WM_CLIENT_LEADER, + self.atoms.WINDOW, + 0, + std::u32::MAX, + ) + .ok()? + .reply() + .map_or(None, |client_leader| { + let client_leader: Vec<u32> = + client_leader.value32()?.collect(); + + if client_leader.is_empty() { + None + } else { + Some(client_leader[0]) + } + }) + } + + #[inline] + fn get_icccm_window_hints( + &self, + window: Window, + ) -> Option<Hints> { + let hints = properties::WmHints::get(self.conn, window) + .ok()? + .reply() + .ok()?; + + let urgent = hints.urgent; + let input = hints.input; + let group = hints.window_group; + let initial_state = hints.initial_state.map(|state| match state { + properties::WmHintsState::Normal => IcccmWindowState::Normal, + properties::WmHintsState::Iconic => IcccmWindowState::Iconic, + }); + + Some(Hints { + input, + urgent, + group, + initial_state, + }) + } + + #[inline] + fn get_icccm_window_size_hints( + &self, + window: Window, + min_window_dim: Option<Dim>, + current_size_hints: &Option<SizeHints>, + ) -> (bool, Option<SizeHints>) { + let size_hints = + properties::WmSizeHints::get_normal_hints(self.conn, window) + .ok() + .map_or(None, |cookie| cookie.reply().ok()); + + if size_hints.is_none() { + return (current_size_hints.is_none(), None); + } + + let size_hints = size_hints.unwrap(); + + let (by_user, pos) = + size_hints.position.map_or((false, None), |(spec, x, y)| { + ( + match spec { + properties::WmSizeHintsSpecification::UserSpecified => { + true + }, + _ => false, + }, + if x > 0 || y > 0 { + Some(Pos { + x, + y, + }) + } else { + None + }, + ) + }); + + let (sh_min_width, sh_min_height) = + size_hints.min_size.map_or((None, None), |(width, height)| { + ( + if width > 0 { Some(width as u32) } else { None }, + if height > 0 { + Some(height as u32) + } else { + None + }, + ) + }); + + let (sh_base_width, sh_base_height) = + size_hints + .base_size + .map_or((None, None), |(width, height)| { + ( + if width > 0 { Some(width as u32) } else { None }, + if height > 0 { + Some(height as u32) + } else { + None + }, + ) + }); + + let (max_width, max_height) = + size_hints.max_size.map_or((None, None), |(width, height)| { + ( + if width > 0 { Some(width as u32) } else { None }, + if height > 0 { + Some(height as u32) + } else { + None + }, + ) + }); + + let min_width = if sh_min_width.is_some() { + sh_min_width + } else { + sh_base_width + }; + let min_height = if sh_min_height.is_some() { + sh_min_height + } else { + sh_base_height + }; + + let base_width = if sh_base_width.is_some() { + sh_base_width + } else { + sh_min_width + }; + let base_height = if sh_base_height.is_some() { + sh_base_height + } else { + sh_min_height + }; + + let min_width = if let Some(min_width) = min_width { + if let Some(min_window_dim) = min_window_dim { + if min_width >= min_window_dim.w { + Some(min_width) + } else { + Some(min_window_dim.w) + } + } else if min_width > 0 { + Some(min_width) + } else { + None + } + } else { + None + }; + + let min_height = if let Some(min_height) = min_height { + if let Some(min_window_dim) = min_window_dim { + if min_height >= min_window_dim.h { + Some(min_height) + } else { + Some(min_window_dim.h) + } + } else if min_height > 0 { + Some(min_height) + } else { + None + } + } else { + None + }; + + let (inc_width, inc_height) = size_hints.size_increment.map_or( + (None, None), + |(inc_width, inc_height)| { + ( + if inc_width > 0 && inc_width < 0xFFFF { + Some(inc_width as u32) + } else { + None + }, + if inc_height > 0 && inc_height < 0xFFFF { + Some(inc_height as u32) + } else { + None + }, + ) + }, + ); + + let ((min_ratio, max_ratio), (min_ratio_vulgar, max_ratio_vulgar)) = + size_hints.aspect.map_or( + ((None, None), (None, None)), + |(min_ratio, max_ratio)| { + ( + ( + if min_ratio.numerator > 0 + && min_ratio.denominator > 0 + { + Some( + min_ratio.numerator as f64 + / min_ratio.denominator as f64, + ) + } else { + None + }, + if max_ratio.numerator > 0 + && max_ratio.denominator > 0 + { + Some( + max_ratio.numerator as f64 + / max_ratio.denominator as f64, + ) + } else { + None + }, + ), + ( + Some(Ratio { + numerator: min_ratio.numerator as i32, + denominator: min_ratio.denominator as i32, + }), + Some(Ratio { + numerator: max_ratio.numerator as i32, + denominator: max_ratio.denominator as i32, + }), + ), + ) + }, + ); + + let size_hints = Some(SizeHints { + by_user, + pos, + min_width, + min_height, + max_width, + max_height, + base_width, + base_height, + inc_width, + inc_height, + min_ratio, + max_ratio, + min_ratio_vulgar, + max_ratio_vulgar, + }); + + (*current_size_hints == size_hints, size_hints) + } + + fn init_wm_properties( + &self, + wm_name: &str, + desktop_names: &[&str], + ) { + let wm_instance_class_names = &[wm_name, wm_name]; + let wm_class = wm_instance_class_names.join("\0"); + + drop(self.conn.change_property8( + xproto::PropMode::REPLACE, + self.check_window, + self.atoms._NET_WM_NAME, + self.atoms.UTF8_STRING, + wm_name.as_bytes(), + )); + + drop(self.conn.change_property8( + xproto::PropMode::REPLACE, + self.check_window, + self.atoms.WM_CLASS, + self.atoms.UTF8_STRING, + wm_class.as_bytes(), + )); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.check_window, + self.atoms._NET_WM_PID, + self.atoms.CARDINAL, + &[std::process::id() as u32], + )); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_SUPPORTING_WM_CHECK, + self.atoms.WINDOW, + &[self.check_window], + )); + + drop(self.conn.change_property8( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_WM_NAME, + self.atoms.UTF8_STRING, + wm_name.as_bytes(), + )); + + drop(self.conn.change_property8( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms.WM_CLASS, + self.atoms.UTF8_STRING, + wm_class.as_bytes(), + )); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.check_window, + self.atoms._NET_SUPPORTING_WM_CHECK, + self.atoms.WINDOW, + &[self.check_window], + )); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_SUPPORTED, + self.atoms.ATOM, + &[ + self.atoms._NET_ACTIVE_WINDOW, + self.atoms._NET_CLIENT_LIST, + self.atoms._NET_CLIENT_LIST_STACKING, + self.atoms._NET_CLOSE_WINDOW, + self.atoms._NET_CURRENT_DESKTOP, + self.atoms._NET_DESKTOP_NAMES, + self.atoms._NET_DESKTOP_VIEWPORT, + self.atoms._NET_MOVERESIZE_WINDOW, + self.atoms._NET_NUMBER_OF_DESKTOPS, + self.atoms._NET_SUPPORTED, + self.atoms._NET_SUPPORTING_WM_CHECK, + self.atoms._NET_WM_DESKTOP, + self.atoms._NET_MOVERESIZE_WINDOW, + self.atoms._NET_WM_MOVERESIZE, + self.atoms._NET_WM_NAME, + self.atoms._NET_WM_STATE, + self.atoms._NET_WM_STATE_DEMANDS_ATTENTION, + self.atoms._NET_WM_STATE_FOCUSED, + self.atoms._NET_WM_STATE_FULLSCREEN, + self.atoms._NET_WM_STATE_HIDDEN, + self.atoms._NET_WM_STATE_MODAL, + self.atoms._NET_WM_STATE_STICKY, + self.atoms._NET_WM_STRUT_PARTIAL, + self.atoms._NET_WM_VISIBLE_NAME, + self.atoms._NET_WM_WINDOW_TYPE, + self.atoms._NET_WM_WINDOW_TYPE_DIALOG, + self.atoms._NET_WM_WINDOW_TYPE_DOCK, + self.atoms._NET_WM_WINDOW_TYPE_DROPDOWN_MENU, + self.atoms._NET_WM_WINDOW_TYPE_MENU, + self.atoms._NET_WM_WINDOW_TYPE_NORMAL, + self.atoms._NET_WM_WINDOW_TYPE_NOTIFICATION, + self.atoms._NET_WM_WINDOW_TYPE_POPUP_MENU, + self.atoms._NET_WM_WINDOW_TYPE_SPLASH, + self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR, + self.atoms._NET_WM_WINDOW_TYPE_TOOLTIP, + self.atoms._NET_WM_WINDOW_TYPE_UTILITY, + ], + )); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_WM_PID, + self.atoms.CARDINAL, + &[std::process::id() as u32], + )); + + drop( + self.conn + .delete_property(self.screen.root, self.atoms._NET_CLIENT_LIST), + ); + + self.update_desktops(desktop_names); + } + + #[inline] + fn set_current_desktop( + &self, + index: usize, + ) { + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_CURRENT_DESKTOP, + self.atoms.CARDINAL, + &[index as u32], + )); + } + + #[inline] + fn set_root_window_name( + &self, + name: &str, + ) { + drop(self.conn.change_property8( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms.WM_NAME, + self.atoms.UTF8_STRING, + name.as_bytes(), + )); + } + + #[inline] + fn set_window_desktop( + &self, + window: Window, + index: usize, + ) { + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + window, + self.atoms._NET_WM_DESKTOP, + self.atoms.CARDINAL, + &[index as u32], + )); + } + + #[inline] + fn set_window_above( + &self, + window: Window, + on: bool, + ) { + } + + #[inline] + fn set_window_fullscreen( + &self, + window: Window, + on: bool, + ) { + } + + #[inline] + fn set_window_below( + &self, + window: Window, + on: bool, + ) { + } + + #[inline] + fn set_window_state( + &self, + window: Window, + state: WindowState, + on: bool, + ) { + self.set_window_state_atom( + window, + match state { + WindowState::Modal => self.atoms._NET_WM_STATE_MODAL, + WindowState::Sticky => self.atoms._NET_WM_STATE_STICKY, + WindowState::MaximizedVert => { + self.atoms._NET_WM_STATE_MAXIMIZED_VERT + }, + WindowState::MaximizedHorz => { + self.atoms._NET_WM_STATE_MAXIMIZED_HORZ + }, + WindowState::Shaded => self.atoms._NET_WM_STATE_SHADED, + WindowState::SkipTaskbar => { + self.atoms._NET_WM_STATE_SKIP_TASKBAR + }, + WindowState::SkipPager => self.atoms._NET_WM_STATE_SKIP_PAGER, + WindowState::Hidden => self.atoms._NET_WM_STATE_HIDDEN, + WindowState::Fullscreen => self.atoms._NET_WM_STATE_FULLSCREEN, + WindowState::Above => self.atoms._NET_WM_STATE_ABOVE, + WindowState::Below => self.atoms._NET_WM_STATE_BELOW, + WindowState::DemandsAttention => { + self.atoms._NET_WM_STATE_DEMANDS_ATTENTION + }, + }, + on, + ); + } + + #[inline] + fn set_window_frame_extents( + &self, + window: Window, + extents: Extents, + ) { + let mut frame_extents: Vec<u32> = Vec::with_capacity(4); + + frame_extents.push(extents.left); + frame_extents.push(extents.right); + frame_extents.push(extents.top); + frame_extents.push(extents.bottom); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + window, + self.atoms._NET_FRAME_EXTENTS, + self.atoms.CARDINAL, + &frame_extents[..], + )); + } + + #[inline] + fn set_desktop_geometry( + &self, + geometries: &[&Region], + ) { + let mut areas = Vec::with_capacity(geometries.len()); + + geometries.iter().for_each(|geometry| { + areas.push(geometry.pos.x as u32); + areas.push(geometry.pos.y as u32); + areas.push(geometry.dim.w); + areas.push(geometry.dim.h); + }); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_DESKTOP_GEOMETRY, + self.atoms.CARDINAL, + &areas[..], + )); + } + + #[inline] + fn set_desktop_viewport( + &self, + viewports: &[&Region], + ) { + let mut areas = Vec::with_capacity(viewports.len()); + + viewports.iter().for_each(|viewport| { + areas.push(viewport.pos.x as u32); + areas.push(viewport.pos.y as u32); + areas.push(viewport.dim.w); + areas.push(viewport.dim.h); + }); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_DESKTOP_VIEWPORT, + self.atoms.CARDINAL, + &areas[..], + )); + } + + #[inline] + fn set_workarea( + &self, + workareas: &[&Region], + ) { + let mut areas = Vec::with_capacity(workareas.len()); + + workareas.iter().for_each(|workarea| { + areas.push(workarea.pos.x as u32); + areas.push(workarea.pos.y as u32); + areas.push(workarea.dim.w); + areas.push(workarea.dim.h); + }); + + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_WORKAREA, + self.atoms.CARDINAL, + &areas[..], + )); + } + + #[inline] + fn update_desktops( + &self, + desktop_names: &[&str], + ) { + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_NUMBER_OF_DESKTOPS, + self.atoms.CARDINAL, + &[desktop_names.len() as u32], + )); + + drop(self.conn.change_property8( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_DESKTOP_NAMES, + self.atoms.UTF8_STRING, + desktop_names.join("\0").as_bytes(), + )); + } + + #[inline] + fn update_client_list( + &self, + clients: &[Window], + ) { + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_CLIENT_LIST, + self.atoms.WINDOW, + clients, + )); + } + + #[inline] + fn update_client_list_stacking( + &self, + clients: &[Window], + ) { + drop(self.conn.change_property32( + xproto::PropMode::REPLACE, + self.screen.root, + self.atoms._NET_CLIENT_LIST_STACKING, + self.atoms.WINDOW, + clients, + )); + } + + #[inline] + fn get_window_strut( + &self, + window: Window, + ) -> Option<Vec<Option<Strut>>> { + if let Some(strut) = self.get_window_strut_partial(window) { + return Some(strut); + } + + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_STRUT, + self.atoms.CARDINAL, + 0, + std::u32::MAX, + ) + .ok()? + .reply() + .map_or(None, |strut| { + let widths: Vec<u32> = strut.value32()?.collect(); + + if widths.is_empty() { + return None; + } + + let mut struts = Vec::with_capacity(1); + + for (i, &width) in widths.iter().enumerate() { + if i == 4 { + break; + } + + struts.push(if width != 0 { + Some(Strut { + window, + width, + }) + } else { + None + }); + } + + Some(struts) + }) + } + + #[inline] + fn get_window_strut_partial( + &self, + window: Window, + ) -> Option<Vec<Option<Strut>>> { + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_STRUT_PARTIAL, + self.atoms.CARDINAL, + 0, + std::u32::MAX, + ) + .ok()? + .reply() + .map_or(None, |strut_partial| { + let widths: Vec<u32> = strut_partial.value32()?.collect(); + + if widths.is_empty() { + return None; + } + + let mut struts = Vec::with_capacity(1); + + for (i, &width) in widths.iter().enumerate() { + if i == 4 { + break; + } + + struts.push(if width != 0 { + Some(Strut { + window, + width, + }) + } else { + None + }); + } + + Some(struts) + }) + } + + #[inline] + fn get_window_desktop( + &self, + window: Window, + ) -> Option<usize> { + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_DESKTOP, + self.atoms.CARDINAL, + 0, + std::u32::MAX, + ) + .ok()? + .reply() + .map_or(None, |desktop| { + let desktop: Vec<u32> = desktop.value32()?.collect(); + + if desktop.is_empty() { + None + } else { + Some(desktop[0] as usize) + } + }) + } + + #[inline] + fn get_window_preferred_type( + &self, + window: Window, + ) -> WindowType { + self.get_window_types(window) + .get(0) + .map_or(WindowType::Normal, |&type_| type_) + } + + fn get_window_types( + &self, + window: Window, + ) -> Vec<WindowType> { + let mut window_types = Vec::new(); + + drop( + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_WINDOW_TYPE, + self.atoms.ATOM, + 0, + std::u32::MAX, + ) + .ok() + .and_then(|cookie| cookie.reply().ok()) + .map(|types| { + let types: Vec<u32> = types + .value32() + .map_or(Vec::new(), |value| value.collect()); + + for type_ in types { + if let Some(type_) = + self.get_window_type_from_atom(type_) + { + window_types.push(type_); + } + } + }), + ); + + window_types + } + + #[inline] + fn get_window_preferred_state( + &self, + window: Window, + ) -> Option<WindowState> { + self.get_window_states(window).get(0).map(|&state| state) + } + + fn get_window_states( + &self, + window: Window, + ) -> Vec<WindowState> { + let mut window_states = Vec::new(); + + drop( + self.conn + .get_property( + false, + window, + self.atoms._NET_WM_STATE, + self.atoms.ATOM, + 0, + std::u32::MAX, + ) + .ok() + .and_then(|cookie| cookie.reply().ok()) + .map(|states| { + let states: Vec<u32> = states + .value32() + .map_or(Vec::new(), |value| value.collect()); + + for state in states { + if let Some(state) = + self.get_window_state_from_atom(state) + { + window_states.push(state); + } + } + }), + ); + + window_states + } + + #[inline] + fn window_is_fullscreen( + &self, + window: Window, + ) -> bool { + self.window_is_any_of_state(window, &[self + .atoms + ._NET_WM_STATE_FULLSCREEN]) + } + + #[inline] + fn window_is_above( + &self, + window: Window, + ) -> bool { + self.window_is_any_of_state(window, &[self.atoms._NET_WM_STATE_ABOVE]) + } + + #[inline] + fn window_is_below( + &self, + window: Window, + ) -> bool { + self.window_is_any_of_state(window, &[self.atoms._NET_WM_STATE_BELOW]) + } + + #[inline] + fn window_is_sticky( + &self, + window: Window, + ) -> bool { + let has_sticky_state = self + .window_is_any_of_state(window, &[self.atoms._NET_WM_STATE_STICKY]); + + if has_sticky_state { + return true; + } + + if let Some(desktop) = self.get_window_desktop(window) { + desktop == 0xFFFFFFFF + } else { + false + } + } +} diff --git a/xinitrc b/xinitrc @@ -0,0 +1,5 @@ +st -g 14x4+8+470 & +st & +st -g 10x10+500+10 & + +exec cargo run