commit d080c681e162e7093ca35cce625846f2a9cad41e
Author: deurzen <m.deurzen@tum.de>
Date: Sat, 13 Feb 2021 01:31:35 +0100
initial commit
Diffstat:
48 files changed, 13991 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,3403 @@
+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!(), ":");
+ const PREFIX_LEN: usize = PREFIX.len();
+
+ let mut rules: Rules = Default::default();
+
+ match (instance.get(..PREFIX_LEN), instance.get(PREFIX_LEN..)) {
+ (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(®ion),
+ LayoutMethod::Tile => client.set_tile_region(®ion),
+ LayoutMethod::Tree => client.set_tree_region(®ion),
+ };
+ }
+ }
+
+ 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(|®ion| 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