From 771cd6e5f440ca16fa517fcb909665b3b655c8e9 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 22:16:07 -0500 Subject: [PATCH 01/27] derive deserialize for config, with some intermediates for fn ptrs --- Cargo.lock | 200 +++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/config.rs | 98 ++++++++++------------ src/config/key.rs | 20 ++++- src/handlers.rs | 9 +- src/key_handlers.rs | 6 ++ src/lib.rs | 50 ++++++++--- src/main.rs | 21 +++-- src/tests.rs | 1 + 9 files changed, 322 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e22859..8ca90ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,12 +60,43 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bstr" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "cc" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -87,6 +118,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "env_filter" version = "0.1.2" @@ -146,6 +183,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" @@ -154,9 +201,34 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mlua" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea43c3ffac2d0798bd7128815212dd78c98316b299b7a902dabef13dc7b6b8d" +dependencies = [ + "bstr", + "either", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a11d485edf0f3f04a508615d36c7d50d299cf61a7ee6d3e2530651e0a31771" +dependencies = [ + "cc", + "cfg-if", + "pkg-config", +] [[package]] name = "nom" @@ -167,18 +239,59 @@ dependencies = [ "memchr", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.0", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + [[package]] name = "quick-xml" version = "0.30.0" @@ -188,6 +301,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "regex" version = "1.10.2" @@ -217,6 +348,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rwm" version = "0.1.0" @@ -225,6 +362,8 @@ dependencies = [ "fig", "libc", "log", + "mlua", + "serde", "x11", "xcb", "yeslogic-fontconfig-sys", @@ -238,6 +377,61 @@ dependencies = [ "x11", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.216" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + [[package]] name = "utf8parse" version = "0.2.2" @@ -392,7 +586,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1e2f212bb1a92cd8caac8051b829a6582ede155ccb60b5d5908b81b100952be" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "quick-xml", ] diff --git a/Cargo.toml b/Cargo.toml index 2a64c59..6551eab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ yeslogic-fontconfig-sys = "6.0.0" env_logger = "0.11.3" log = "0.4.21" fig = { git = "https://github.com/ntBre/fig" } +mlua = { version = "0.10.2", features = ["lua54"] } +serde = { version = "1.0.216", features = ["derive"] } [target.'cfg(target_os = "linux")'.dependencies] xcb = { version = "1.5.0", features = [ "res" ] } diff --git a/src/config.rs b/src/config.rs index b1411d2..26de1b4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,13 +3,11 @@ use std::{ error::Error, ffi::{c_float, c_int, c_uint, CString}, path::Path, - ptr::null, sync::LazyLock, }; use fig::{Fig, FigError, Value}; use key::{get_arg, FUNC_MAP}; -use libc::c_char; use x11::keysym::{ XK_Return, XK_Tab, XK_b, XK_c, XK_comma, XK_d, XK_f, XK_grave, XK_h, XK_i, XK_j, XK_k, XK_l, XK_m, XK_p, XK_period, XK_q, XK_space, XK_t, XK_0, XK_1, @@ -24,7 +22,7 @@ use crate::{ key_handlers::*, layouts::{monocle, tile}, }; -use rwm::{Arg, Button, Layout, Monitor, Rule, State}; +use rwm::{Arg, Button, ButtonFn, Layout, LayoutFn, Monitor, Rule, State}; mod fig_env; pub mod key; @@ -61,6 +59,7 @@ impl Default for Config { } } +#[derive(serde::Deserialize)] pub struct Config { /// Border pixel of windows pub borderpx: c_uint, @@ -179,19 +178,6 @@ fn get_rules(v: &mut HashMap) -> Result, FigError> { }; let rules: Vec = conv(rules.as_list())?; - // NOTE each into_raw call is a leak, but these should live as long as the - // program runs anyway - let maybe_cstring = |val: Value| -> Result<*const c_char, FigError> { - if let Ok(Ok(s)) = String::try_from(val.clone()).map(CString::new) { - Ok(s.into_raw()) - } else if val.is_nil() { - Ok(null()) - } else { - log::error!("expected Str or Nil"); - Err(FigError::Conversion) - } - }; - let maybe_string = |val: Value| -> Result { if let Ok(s) = String::try_from(val.clone()) { Ok(s) @@ -211,8 +197,8 @@ fn get_rules(v: &mut HashMap) -> Result, FigError> { return err; } ret.push(Rule { - class: maybe_cstring(rule[0].clone())?, - instance: maybe_cstring(rule[1].clone())?, + class: maybe_string(rule[0].clone())?, + instance: maybe_string(rule[1].clone())?, title: maybe_string(rule[2].clone())?, tags: i64::try_from(rule[3].clone())? as u32, isfloating: rule[4].clone().try_into()?, @@ -262,7 +248,7 @@ fn get_buttons( click: i64::try_from(button[0].clone())? as u32, mask: i64::try_from(button[1].clone())? as u32, button: i64::try_from(button[2].clone())? as u32, - func, + func: ButtonFn(func), arg: get_arg(conv(button[4].as_map())?)?, }); } @@ -303,7 +289,7 @@ fn get_layouts( } }; - ret.push(Layout { symbol, arrange }); + ret.push(Layout { symbol, arrange: LayoutFn(arrange) }); } Ok(ret) @@ -424,46 +410,48 @@ fn default_colors() -> [[CString; 3]; 2] { ret } -const RULES: [Rule; 3] = [ - Rule { - class: c"Gimp".as_ptr(), - instance: null(), - title: String::new(), - tags: 0, - isfloating: true, - isterminal: false, - noswallow: false, - monitor: -1, - }, - Rule { - class: c"Firefox".as_ptr(), - instance: null(), - title: String::new(), - tags: 1 << 8, - isfloating: false, - isterminal: false, - noswallow: true, - monitor: -1, - }, - Rule { - class: c"st-256color".as_ptr(), - instance: null(), - title: String::new(), - tags: 0, - isfloating: false, - isterminal: true, - noswallow: false, - monitor: -1, - }, -]; +static RULES: LazyLock<[Rule; 3]> = LazyLock::new(|| { + [ + Rule { + class: "Gimp".into(), + instance: String::new(), + title: String::new(), + tags: 0, + isfloating: true, + isterminal: false, + noswallow: false, + monitor: -1, + }, + Rule { + class: "Firefox".into(), + instance: String::new(), + title: String::new(), + tags: 1 << 8, + isfloating: false, + isterminal: false, + noswallow: true, + monitor: -1, + }, + Rule { + class: "st-256color".into(), + instance: String::new(), + title: String::new(), + tags: 0, + isfloating: false, + isterminal: true, + noswallow: false, + monitor: -1, + }, + ] +}); // layouts static LAYOUTS: LazyLock<[Layout; 3]> = LazyLock::new(|| { [ - Layout { symbol: "[]=".to_string(), arrange: Some(tile) }, - Layout { symbol: "><>".to_string(), arrange: None }, - Layout { symbol: "[M]".to_string(), arrange: Some(monocle) }, + Layout { symbol: "[]=".to_string(), arrange: LayoutFn(Some(tile)) }, + Layout { symbol: "><>".to_string(), arrange: LayoutFn(None) }, + Layout { symbol: "[M]".to_string(), arrange: LayoutFn(Some(monocle)) }, ] }); diff --git a/src/config/key.rs b/src/config/key.rs index ebbb7d4..d5e86d9 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -4,12 +4,24 @@ use fig::{FigError, Value}; use rwm::{Arg, State}; use x11::xlib::KeySym; +#[derive(Clone, serde::Deserialize)] +#[serde(try_from = "String")] +pub struct KeyFn(pub Option); + +impl TryFrom for KeyFn { + type Error = String; + + fn try_from(_value: String) -> Result { + todo!() + } +} + #[repr(C)] -#[derive(Clone)] +#[derive(Clone, serde::Deserialize)] pub struct Key { pub mod_: c_uint, pub keysym: KeySym, - pub func: Option, + pub func: KeyFn, pub arg: Arg, } @@ -20,7 +32,7 @@ impl Key { func: fn(&mut State, *const Arg), arg: Arg, ) -> Self { - Self { mod_, keysym: keysym as KeySym, func: Some(func), arg } + Self { mod_, keysym: keysym as KeySym, func: KeyFn(Some(func)), arg } } } @@ -86,7 +98,7 @@ impl TryFrom for Key { Ok(Self { mod_: conv(l[0].as_int())? as u32, keysym: conv(l[1].as_int())? as u64, - func: Some(func), + func: KeyFn(Some(func)), arg, }) } diff --git a/src/handlers.rs b/src/handlers.rs index 4cf9367..09d52ae 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -99,11 +99,11 @@ pub(crate) fn buttonpress(state: &mut State, e: *mut XEvent) { } for button in &CONFIG.buttons { if click as u32 == button.click - && button.func.is_some() + && button.func.0.is_some() && button.button == ev.button && cleanmask(state, button.mask) == cleanmask(state, ev.state) { - let f = button.func.unwrap(); + let f = button.func.0.unwrap(); let a = if click == Clk::TagBar && button.arg.i() == 0 { &arg } else { @@ -290,6 +290,7 @@ pub(crate) fn configurerequest(state: &mut State, e: *mut XEvent) { } else if (*c).isfloating || (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_none() { let m = (*c).mon; @@ -480,9 +481,9 @@ pub(crate) fn keypress(state: &mut State, e: *mut XEvent) { for key in &CONFIG.keys { if keysym == key.keysym && cleanmask(state, key.mod_) == cleanmask(state, ev.state) - && key.func.is_some() + && key.func.0.is_some() { - key.func.unwrap()(state, &(key.arg)); + key.func.0.unwrap()(state, &(key.arg)); } } } diff --git a/src/key_handlers.rs b/src/key_handlers.rs index 3f811ce..3347cca 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -118,6 +118,7 @@ pub(crate) fn setmfact(state: &mut State, arg: *const Arg) { if arg.is_null() || (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_none() { return; @@ -145,6 +146,7 @@ pub(crate) fn zoom(state: &mut State, _arg: *const Arg) { let mut c = (*state.selmon).sel; if (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_none() || c.is_null() || (*c).isfloating @@ -564,6 +566,7 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { if !c.isfloating && (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_some() && ((nx - c.x).abs() > CONFIG.snap as c_int || (ny - c.y).abs() > CONFIG.snap as c_int) @@ -572,6 +575,7 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { } if (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_none() || c.isfloating { @@ -664,6 +668,7 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { && !c.isfloating && (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_some() && ((nw - c.w).abs() > CONFIG.snap as c_int || (nh - c.h).abs() > CONFIG.snap as c_int) @@ -672,6 +677,7 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { } if (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_none() || c.isfloating { diff --git a/src/lib.rs b/src/lib.rs index 5cf045f..e344149 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_char, c_int, c_uint}; +use std::ffi::{c_int, c_uint}; use enums::Clk; use x11::xft::XftColor; @@ -15,7 +15,7 @@ pub type Window = u64; pub type Clr = XftColor; #[repr(C)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, serde::Deserialize)] pub enum Arg { I(c_int), Ui(c_uint), @@ -47,13 +47,25 @@ impl Arg { } } +#[derive(Clone, serde::Deserialize)] +#[serde(try_from = "String")] +pub struct ButtonFn(pub Option); + +impl TryFrom for ButtonFn { + type Error = String; + + fn try_from(_value: String) -> Result { + todo!() + } +} + #[repr(C)] -#[derive(Clone)] +#[derive(Clone, serde::Deserialize)] pub struct Button { pub click: c_uint, pub mask: c_uint, pub button: c_uint, - pub func: Option, + pub func: ButtonFn, pub arg: Arg, } @@ -65,7 +77,13 @@ impl Button { func: fn(&mut State, *const Arg), arg: Arg, ) -> Self { - Self { click: click as c_uint, mask, button, func: Some(func), arg } + Self { + click: click as c_uint, + mask, + button, + func: ButtonFn(Some(func)), + arg, + } } } @@ -76,10 +94,10 @@ pub struct Cursor { } #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Deserialize)] pub struct Rule { - pub class: *const c_char, - pub instance: *const c_char, + pub class: String, + pub instance: String, pub title: String, pub tags: c_uint, pub isfloating: bool, @@ -93,11 +111,23 @@ pub struct Systray { pub icons: *mut Client, } +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(try_from = "String")] +pub struct LayoutFn(pub Option); + +impl TryFrom for LayoutFn { + type Error = String; + + fn try_from(_value: String) -> Result { + todo!() + } +} + #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Deserialize)] pub struct Layout { pub symbol: String, - pub arrange: Option, + pub arrange: LayoutFn, } #[derive(Clone, Debug)] diff --git a/src/main.rs b/src/main.rs index 614b914..a7dd6c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,8 @@ use x11::xlib::{ use xcb::Connection; use rwm::{ - drw, util, Arg, Client, Layout, Monitor, Pertag, State, Systray, Window, + drw, util, Arg, Client, Layout, LayoutFn, Monitor, Pertag, State, Systray, + Window, }; use config::CONFIG; @@ -625,7 +626,7 @@ fn arrangemon(state: &mut State, m: *mut Monitor) { log::trace!("arrangemon"); unsafe { (*m).ltsymbol = (*(*m).lt[(*m).sellt as usize]).symbol.clone(); - let arrange = (*(*m).lt[(*m).sellt as usize]).arrange; + let arrange = (*(*m).lt[(*m).sellt as usize]).arrange.0; if let Some(arrange) = arrange { (arrange)(state, m); } @@ -640,11 +641,11 @@ fn restack(state: &mut State, m: *mut Monitor) { return; } if (*(*m).sel).isfloating - || (*(*m).lt[(*m).sellt as usize]).arrange.is_none() + || (*(*m).lt[(*m).sellt as usize]).arrange.0.is_none() { xlib::XRaiseWindow(state.dpy, (*(*m).sel).win); } - if (*(*m).lt[(*m).sellt as usize]).arrange.is_some() { + if (*(*m).lt[(*m).sellt as usize]).arrange.0.is_some() { let mut wc = xlib::XWindowChanges { stack_mode: Below, sibling: (*m).barwin, @@ -685,6 +686,7 @@ fn showhide(state: &mut State, c: *mut Client) { xlib::XMoveWindow(state.dpy, (*c).win, (*c).x, (*c).y); if ((*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) .arrange + .0 .is_none() || (*c).isfloating) && !(*c).isfullscreen @@ -854,6 +856,7 @@ fn applysizehints( || (*c).isfloating || (*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) .arrange + .0 .is_none() { if (*c).hintsvalid == 0 { @@ -2085,7 +2088,7 @@ fn cleanup(mut state: State) { let a = Arg::Ui(!0); view(&mut state, &a); (*state.selmon).lt[(*state.selmon).sellt as usize] = - &Layout { symbol: String::new(), arrange: None }; + &Layout { symbol: String::new(), arrange: LayoutFn(None) }; let mut m = state.mons; while !m.is_null() { @@ -2805,10 +2808,10 @@ fn applyrules(state: &mut State, c: *mut Client) { for r in &CONFIG.rules { if (r.title.is_empty() || (*c).name.contains(&r.title)) - && (r.class.is_null() - || !libc::strstr(class.as_ptr(), r.class).is_null()) - && (r.instance.is_null() - || !libc::strstr(instance.as_ptr(), r.instance).is_null()) + && (r.class.is_empty() + || class.to_string_lossy().contains(&r.class)) + && (r.instance.is_empty() + || instance.to_string_lossy().contains(&r.instance)) { (*c).isterminal = r.isterminal; (*c).noswallow = r.noswallow; diff --git a/src/tests.rs b/src/tests.rs index 08780ba..93395a9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -56,6 +56,7 @@ fn main() { unsafe { assert!((*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange + .0 .is_none()); } From 7151f1dfae9a223db1b99607a78028d3c585cab9 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 22:54:02 -0500 Subject: [PATCH 02/27] add insta, load a Config from lua --- Cargo.lock | 87 +++++++++++++++++++ Cargo.toml | 5 +- example.lua | 0 src/config.lua | 32 +++++++ src/config.rs | 63 +++++++++++++- src/config/key.rs | 4 +- src/lib.rs | 4 +- src/main.rs | 4 +- .../rwm__config__tests__from_lua.snap | 60 +++++++++++++ 9 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 example.lua create mode 100644 src/config.lua create mode 100644 src/snapshots/rwm__config__tests__from_lua.snap diff --git a/Cargo.lock b/Cargo.lock index 8ca90ee..32e8fb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,6 +109,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "dlib" version = "0.5.2" @@ -124,6 +136,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "env_filter" version = "0.1.2" @@ -147,6 +165,16 @@ dependencies = [ "log", ] +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "fig" version = "0.1.0" @@ -161,12 +189,30 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "insta" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.158" @@ -183,6 +229,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lock_api" version = "0.4.12" @@ -213,10 +265,13 @@ checksum = "9ea43c3ffac2d0798bd7128815212dd78c98316b299b7a902dabef13dc7b6b8d" dependencies = [ "bstr", "either", + "erased-serde", "mlua-sys", "num-traits", "parking_lot", "rustc-hash", + "serde", + "serde-value", ] [[package]] @@ -254,6 +309,15 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -360,6 +424,7 @@ version = "0.1.0" dependencies = [ "env_logger", "fig", + "insta", "libc", "log", "mlua", @@ -392,6 +457,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.216" @@ -409,6 +484,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "similar" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" + [[package]] name = "smallvec" version = "1.13.2" @@ -426,6 +507,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 6551eab..7689465 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,11 @@ yeslogic-fontconfig-sys = "6.0.0" env_logger = "0.11.3" log = "0.4.21" fig = { git = "https://github.com/ntBre/fig" } -mlua = { version = "0.10.2", features = ["lua54"] } +mlua = { version = "0.10.2", features = ["lua54", "serialize"] } serde = { version = "1.0.216", features = ["derive"] } [target.'cfg(target_os = "linux")'.dependencies] xcb = { version = "1.5.0", features = [ "res" ] } + +[dev-dependencies] +insta = "1.41.1" diff --git a/example.lua b/example.lua new file mode 100644 index 0000000..e69de29 diff --git a/src/config.lua b/src/config.lua new file mode 100644 index 0000000..6df9b49 --- /dev/null +++ b/src/config.lua @@ -0,0 +1,32 @@ +dmenufont = "monospace:size=10" + +rwm = { + borderpx = 3, + snap = 32, + showbar = true, + topbar = true, + mfact = 0.5, + nmaster = 1, + resize_hints = false, + lock_fullscreen = true, + fonts = {"monospace:size=10"}, + tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"}, + colors = { + -- gray3, gray1, gray2 + norm = {"#bbbbbb", "#222222", "#444444"}, + -- gray4, cyan, cyan + sel = {"#eeeeee", "#005577", "#005577"}, + }, + keys = {}, -- TODO + dmenucmd = {"dmenu_run", "-fn", dmenufont, "-nb"}, -- TODO + rules = {}, -- TODO + swallowfloating = false, + systraypinning = 0, + systrayonleft = false, + systrayspacing = 2, + systraypinningfailfirst = true, + showsystray = true, + buttons = {}, -- TODO + layouts = {}, -- TODO + scratchpadname = "scratchpad", +} diff --git a/src/config.rs b/src/config.rs index 26de1b4..f2a1d78 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,12 +2,14 @@ use std::{ collections::HashMap, error::Error, ffi::{c_float, c_int, c_uint, CString}, + fs::read_to_string, path::Path, sync::LazyLock, }; use fig::{Fig, FigError, Value}; use key::{get_arg, FUNC_MAP}; +use mlua::{Lua, LuaSerdeExt as _}; use x11::keysym::{ XK_Return, XK_Tab, XK_b, XK_c, XK_comma, XK_d, XK_f, XK_grave, XK_h, XK_i, XK_j, XK_k, XK_l, XK_m, XK_p, XK_period, XK_q, XK_space, XK_t, XK_0, XK_1, @@ -42,7 +44,7 @@ impl Default for Config { tags: ["1", "2", "3", "4", "5", "6", "7", "8", "9"] .map(String::from) .to_vec(), - colors: default_colors(), + colors: ColorMap(default_colors()), keys: default_keys().to_vec(), dmenucmd: DMENUCMD.to_vec(), rules: RULES.to_vec(), @@ -59,7 +61,41 @@ impl Default for Config { } } -#[derive(serde::Deserialize)] +#[derive(Debug, serde::Deserialize)] +#[serde(try_from = "HashMap>")] +pub struct ColorMap(pub [[CString; 3]; 2]); + +impl TryFrom>> for ColorMap { + type Error = Box; + + fn try_from( + value: HashMap>, + ) -> Result { + let mut ret = default_colors(); + let norm = value.get("norm").ok_or_else(|| "missing key norm")?; + let sel = value.get("sel").ok_or_else(|| "missing key sel")?; + let [n0, n1, n2] = &norm[..] else { + return Err("not enough colors for SchemeNorm".into()); + }; + ret[Scheme::Norm as usize] = [ + CString::new(n0.clone())?, + CString::new(n1.clone())?, + CString::new(n2.clone())?, + ]; + let [s0, s1, s2] = &sel[..] else { + return Err("not enough colors for SchemeSel".into()); + }; + ret[Scheme::Sel as usize] = [ + CString::new(s0.clone())?, + CString::new(s1.clone())?, + CString::new(s2.clone())?, + ]; + + Ok(Self(ret)) + } +} + +#[derive(Debug, serde::Deserialize)] pub struct Config { /// Border pixel of windows pub borderpx: c_uint, @@ -89,7 +125,7 @@ pub struct Config { pub tags: Vec, - pub colors: [[CString; 3]; 2], + pub colors: ColorMap, pub keys: Vec, @@ -326,7 +362,7 @@ impl TryFrom for Config { lock_fullscreen: bool(get(&mut v, "lock_fullscreen")?)?, fonts: cstr_list(get(&mut v, "fonts")?)?, tags: str_list(get(&mut v, "tags")?)?, - colors: get_colors(&mut v)?, + colors: ColorMap(get_colors(&mut v)?), keys: get_keys(&mut v)?, dmenucmd: get(&mut v, "dmenucmd")?.try_into()?, rules: get_rules(&mut v)?, @@ -354,6 +390,17 @@ impl Config { f.parse(&s)?; Self::try_from(f) } + + #[allow(dependency_on_unit_never_type_fallback)] + pub fn from_lua(path: impl AsRef) -> Result> { + let lua = Lua::new(); + let globals = lua.globals(); + + lua.load(include_str!("config.lua")).eval()?; + lua.load(read_to_string(path)?).eval()?; + + Ok(lua.from_value(globals.get("rwm")?)?) + } } /// Attempt to load a config file on first usage from `$XDG_CONFIG_HOME`, then @@ -588,6 +635,8 @@ static BUTTONS: LazyLock<[Button; 11]> = LazyLock::new(|| { #[cfg(test)] mod tests { + use insta::assert_debug_snapshot; + use super::*; #[test] @@ -597,4 +646,10 @@ mod tests { assert_eq!(conf.tags.len(), 9); } + + #[test] + fn from_lua() { + let got = Config::from_lua("example.lua").unwrap(); + assert_debug_snapshot!(got); + } } diff --git a/src/config/key.rs b/src/config/key.rs index d5e86d9..c15c43f 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -4,7 +4,7 @@ use fig::{FigError, Value}; use rwm::{Arg, State}; use x11::xlib::KeySym; -#[derive(Clone, serde::Deserialize)] +#[derive(Clone, Debug, serde::Deserialize)] #[serde(try_from = "String")] pub struct KeyFn(pub Option); @@ -17,7 +17,7 @@ impl TryFrom for KeyFn { } #[repr(C)] -#[derive(Clone, serde::Deserialize)] +#[derive(Debug, Clone, serde::Deserialize)] pub struct Key { pub mod_: c_uint, pub keysym: KeySym, diff --git a/src/lib.rs b/src/lib.rs index e344149..7db4278 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ impl Arg { } } -#[derive(Clone, serde::Deserialize)] +#[derive(Clone, Debug, serde::Deserialize)] #[serde(try_from = "String")] pub struct ButtonFn(pub Option); @@ -60,7 +60,7 @@ impl TryFrom for ButtonFn { } #[repr(C)] -#[derive(Clone, serde::Deserialize)] +#[derive(Clone, Debug, serde::Deserialize)] pub struct Button { pub click: c_uint, pub mask: c_uint, diff --git a/src/main.rs b/src/main.rs index a7dd6c3..bb53885 100644 --- a/src/main.rs +++ b/src/main.rs @@ -329,10 +329,10 @@ fn setup(dpy: *mut Display) -> State { XInternAtom(state.dpy, c"_XEMBED_INFO".as_ptr(), False); /* init appearance */ - for i in 0..CONFIG.colors.len() { + for i in 0..CONFIG.colors.0.len() { state.scheme.push(drw::scm_create( &state.drw, - &CONFIG.colors[i], + &CONFIG.colors.0[i], 3, )); } diff --git a/src/snapshots/rwm__config__tests__from_lua.snap b/src/snapshots/rwm__config__tests__from_lua.snap new file mode 100644 index 0000000..717bc66 --- /dev/null +++ b/src/snapshots/rwm__config__tests__from_lua.snap @@ -0,0 +1,60 @@ +--- +source: src/config.rs +expression: got +snapshot_kind: text +--- +Config { + borderpx: 3, + snap: 32, + showbar: true, + topbar: true, + mfact: 0.5, + nmaster: 1, + resize_hints: false, + lock_fullscreen: true, + fonts: [ + "monospace:size=10", + ], + tags: [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ], + colors: ColorMap( + [ + [ + "#bbbbbb", + "#222222", + "#444444", + ], + [ + "#eeeeee", + "#005577", + "#005577", + ], + ], + ), + keys: [], + dmenucmd: [ + "dmenu_run", + "-fn", + "monospace:size=10", + "-nb", + ], + rules: [], + swallowfloating: false, + systraypinning: 0, + systrayonleft: false, + systrayspacing: 2, + systraypinningfailfirst: true, + showsystray: true, + buttons: [], + layouts: [], + scratchpadname: "scratchpad", +} From a5df1b41c22e974c1709d641ee71f4ea108aadd2 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 22:55:03 -0500 Subject: [PATCH 03/27] ok_or --- src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index f2a1d78..a669a4c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -72,8 +72,8 @@ impl TryFrom>> for ColorMap { value: HashMap>, ) -> Result { let mut ret = default_colors(); - let norm = value.get("norm").ok_or_else(|| "missing key norm")?; - let sel = value.get("sel").ok_or_else(|| "missing key sel")?; + let norm = value.get("norm").ok_or("missing key norm")?; + let sel = value.get("sel").ok_or("missing key sel")?; let [n0, n1, n2] = &norm[..] else { return Err("not enough colors for SchemeNorm".into()); }; From 54dcd76c72eb14f2cbf321a1cfbf617aa7eab97e Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 22:57:53 -0500 Subject: [PATCH 04/27] move CONFIG init to Config::load_home --- src/config.rs | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/config.rs b/src/config.rs index a669a4c..eb76273 100644 --- a/src/config.rs +++ b/src/config.rs @@ -401,29 +401,31 @@ impl Config { Ok(lua.from_value(globals.get("rwm")?)?) } -} -/// Attempt to load a config file on first usage from `$XDG_CONFIG_HOME`, then -/// `$HOME`, before falling back to the default config. -pub static CONFIG: LazyLock = LazyLock::new(|| { - let mut home = std::env::var("XDG_CONFIG_HOME"); - if home.is_err() { - home = std::env::var("HOME"); - } - if home.is_err() { - log::warn!("unable to determine config directory"); - return Config::default(); + /// Attempt to load a config file on first usage from `$XDG_CONFIG_HOME`, + /// then `$HOME`, before falling back to the default config. + pub fn load_home() -> Self { + let mut home = std::env::var("XDG_CONFIG_HOME"); + if home.is_err() { + home = std::env::var("HOME"); + } + if home.is_err() { + log::warn!("unable to determine config directory"); + return Config::default(); + } + let config_path = Path::new(&home.unwrap()) + .join(".config") + .join("rwm") + .join("config.fig"); + + Config::load(config_path).unwrap_or_else(|e| { + log::error!("failed to read config file: {e:?}"); + Config::default() + }) } - let config_path = Path::new(&home.unwrap()) - .join(".config") - .join("rwm") - .join("config.fig"); - - Config::load(config_path).unwrap_or_else(|e| { - log::error!("failed to read config file: {e:?}"); - Config::default() - }) -}); +} + +pub static CONFIG: LazyLock = LazyLock::new(Config::load_home); // appearance From db466d1b4eecd257e44fb9571bfe12c330488d2c Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 22:58:17 -0500 Subject: [PATCH 05/27] allow unused for now --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index eb76273..844f822 100644 --- a/src/config.rs +++ b/src/config.rs @@ -391,7 +391,7 @@ impl Config { Self::try_from(f) } - #[allow(dependency_on_unit_never_type_fallback)] + #[allow(dependency_on_unit_never_type_fallback, unused)] pub fn from_lua(path: impl AsRef) -> Result> { let lua = Lua::new(); let globals = lua.globals(); From 59293a84e25496bc7ee2a1ea4a74d34dc1e5fdb2 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 23:23:17 -0500 Subject: [PATCH 06/27] move config to lib and CONFIG to state --- src/config.rs | 4 +- src/config/fig_env.rs | 2 +- src/config/key.rs | 2 +- src/handlers.rs | 49 +- src/key_handlers.rs | 62 +- src/layouts.rs | 4 +- src/lib.rs | 19 + src/main.rs | 2959 +---------------------------------------- src/main_.rs | 2947 ++++++++++++++++++++++++++++++++++++++++ src/state.rs | 10 + src/tests.rs | 11 +- src/xembed.rs | 20 +- 12 files changed, 3062 insertions(+), 3027 deletions(-) create mode 100644 src/main_.rs diff --git a/src/config.rs b/src/config.rs index 844f822..4973d79 100644 --- a/src/config.rs +++ b/src/config.rs @@ -24,7 +24,7 @@ use crate::{ key_handlers::*, layouts::{monocle, tile}, }; -use rwm::{Arg, Button, ButtonFn, Layout, LayoutFn, Monitor, Rule, State}; +use crate::{Arg, Button, ButtonFn, Layout, LayoutFn, Monitor, Rule, State}; mod fig_env; pub mod key; @@ -425,8 +425,6 @@ impl Config { } } -pub static CONFIG: LazyLock = LazyLock::new(Config::load_home); - // appearance /// Swallow floating windows by default diff --git a/src/config/fig_env.rs b/src/config/fig_env.rs index 3442bd5..b0e0a4a 100644 --- a/src/config/fig_env.rs +++ b/src/config/fig_env.rs @@ -3,8 +3,8 @@ use std::{collections::HashMap, sync::LazyLock}; +use crate::enums::Clk; use fig::Value; -use rwm::enums::Clk; use x11::xlib::{ Button1, Button2, Button3, ControlMask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, ShiftMask, diff --git a/src/config/key.rs b/src/config/key.rs index c15c43f..04527ef 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, ffi::c_uint, sync::LazyLock}; +use crate::{Arg, State}; use fig::{FigError, Value}; -use rwm::{Arg, State}; use x11::xlib::KeySym; #[derive(Clone, Debug, serde::Deserialize)] diff --git a/src/handlers.rs b/src/handlers.rs index 09d52ae..cc656ba 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -14,7 +14,7 @@ use x11::xlib::{ XA_WM_TRANSIENT_FOR, }; -use rwm::{ +use crate::{ drw, enums::{Col, Scheme, XEmbed}, util::ecalloc, @@ -22,9 +22,7 @@ use rwm::{ }; use crate::{ - arrange, cleanmask, - config::CONFIG, - configure, drawbar, drawbars, + arrange, cleanmask, configure, drawbar, drawbars, enums::{Clk, Net}, focus, getsystraywidth, grabkeys, height, is_visible, manage, recttomon, removesystrayicon, resizebarwin, resizeclient, restack, sendevent, @@ -41,7 +39,7 @@ use crate::{ NORMAL_STATE, WITHDRAWN_STATE, }; -pub(crate) fn buttonpress(state: &mut State, e: *mut XEvent) { +pub fn buttonpress(state: &mut State, e: *mut XEvent) { unsafe { let mut arg = Arg::I(0); let ev = &(*e).button; @@ -58,17 +56,17 @@ pub(crate) fn buttonpress(state: &mut State, e: *mut XEvent) { let mut x = 0; // emulating do-while loop { - x += textw(&mut state.drw, &CONFIG.tags[i], state.lrpad); + x += textw(&mut state.drw, &state.CONFIG.tags[i], state.lrpad); // condition if ev.x < x { break; } i += 1; - if i >= CONFIG.tags.len() { + if i >= state.CONFIG.tags.len() { break; } } - if i < CONFIG.tags.len() { + if i < state.CONFIG.tags.len() { click = Clk::TagBar; arg = Arg::Ui(1 << i); } else if ev.x @@ -97,17 +95,20 @@ pub(crate) fn buttonpress(state: &mut State, e: *mut XEvent) { click = Clk::ClientWin; } } - for button in &CONFIG.buttons { - if click as u32 == button.click - && button.func.0.is_some() - && button.button == ev.button - && cleanmask(state, button.mask) == cleanmask(state, ev.state) + for i in 0..state.CONFIG.buttons.len() { + if click as u32 == state.CONFIG.buttons[i].click + && state.CONFIG.buttons[i].func.0.is_some() + && state.CONFIG.buttons[i].button == ev.button + && cleanmask(state, state.CONFIG.buttons[i].mask) + == cleanmask(state, ev.state) { - let f = button.func.0.unwrap(); - let a = if click == Clk::TagBar && button.arg.i() == 0 { + let f = state.CONFIG.buttons[i].func.0.unwrap(); + let a = if click == Clk::TagBar + && state.CONFIG.buttons[i].arg.i() == 0 + { &arg } else { - &button.arg + &state.CONFIG.buttons[i].arg }; f(state, a) } @@ -120,7 +121,7 @@ pub(crate) fn clientmessage(state: &mut State, e: *mut XEvent) { let cme = &(*e).client_message; let mut c = wintoclient(state, cme.window); - if CONFIG.showsystray + if state.CONFIG.showsystray && cme.window == state.systray().win && cme.message_type == state.netatom[Net::SystemTrayOP as usize] { @@ -478,12 +479,16 @@ pub(crate) fn keypress(state: &mut State, e: *mut XEvent) { let ev = &mut (*e).key; let keysym = xlib::XKeycodeToKeysym(state.dpy, ev.keycode as KeyCode, 0); - for key in &CONFIG.keys { - if keysym == key.keysym - && cleanmask(state, key.mod_) == cleanmask(state, ev.state) - && key.func.0.is_some() + for i in 0..state.CONFIG.keys.len() { + if keysym == state.CONFIG.keys[i].keysym + && cleanmask(state, state.CONFIG.keys[i].mod_) + == cleanmask(state, ev.state) + && state.CONFIG.keys[i].func.0.is_some() { - key.func.0.unwrap()(state, &(key.arg)); + state.CONFIG.keys[i].func.0.unwrap()( + state, + &(state.CONFIG.keys[i].arg), + ); } } } diff --git a/src/key_handlers.rs b/src/key_handlers.rs index 3347cca..7747a79 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -12,16 +12,15 @@ use x11::xlib::{ XUngrabServer, XWarpPointer, XWindowChanges, CWY, }; -use crate::config::CONFIG; use crate::enums::WM; -use crate::{ +use crate::main_::{ arrange, attach, attachstack, detach, detachstack, drawbar, focus, getrootptr, height, is_visible, nexttiled, pop, recttomon, resize, resizebarwin, restack, sendevent, setfullscreen, unfocus, updatebarpos, - width, xerror, xerrordummy, HANDLER, MOUSEMASK, SCRATCHTAG, TAGMASK, XNONE, + width, xerror, xerrordummy, HANDLER, MOUSEMASK, XNONE, }; -use rwm::State; -use rwm::{Arg, Client, Monitor}; +use crate::{cfor, State}; +use crate::{Arg, Client, Monitor}; pub(crate) fn togglebar(state: &mut State, _arg: *const Arg) { unsafe { @@ -32,7 +31,7 @@ pub(crate) fn togglebar(state: &mut State, _arg: *const Arg) { [(*state.selmon).pertag.curtag as usize]; updatebarpos(state, state.selmon); resizebarwin(state, state.selmon); - if CONFIG.showsystray { + if state.CONFIG.showsystray { let mut wc = XWindowChanges { x: 0, y: 0, @@ -67,7 +66,8 @@ pub(crate) fn focusstack(state: &mut State, arg: *const Arg) { let mut i: *mut Client; if (*state.selmon).sel.is_null() - || ((*(*state.selmon).sel).isfullscreen && CONFIG.lock_fullscreen) + || ((*(*state.selmon).sel).isfullscreen + && state.CONFIG.lock_fullscreen) { return; } @@ -167,7 +167,7 @@ pub(crate) fn zoom(state: &mut State, _arg: *const Arg) { pub(crate) fn view(state: &mut State, arg: *const Arg) { log::trace!("view"); unsafe { - if (*arg).ui() & *TAGMASK + if (*arg).ui() & state.tagmask() == (*state.selmon).tagset[(*state.selmon).seltags as usize] { return; @@ -176,9 +176,9 @@ pub(crate) fn view(state: &mut State, arg: *const Arg) { // Safety: we were gonna dereference it anyway let pertag = &mut (*state.selmon).pertag; - if ((*arg).ui() & *TAGMASK) != 0 { + if ((*arg).ui() & state.tagmask()) != 0 { (*state.selmon).tagset[(*state.selmon).seltags as usize] = - (*arg).ui() & *TAGMASK; + (*arg).ui() & state.tagmask(); pertag.prevtag = pertag.curtag; if (*arg).ui() == !0 { @@ -245,7 +245,7 @@ pub(crate) fn setlayout(state: &mut State, arg: *const Arg) { if arg.is_null() || (*arg).l().is_none() || !std::ptr::eq( - &CONFIG.layouts[(*arg).l().unwrap()], + &state.CONFIG.layouts[(*arg).l().unwrap()], (*state.selmon).lt[(*state.selmon).sellt as usize], ) { @@ -258,7 +258,7 @@ pub(crate) fn setlayout(state: &mut State, arg: *const Arg) { (*state.selmon).pertag.ltidxs [(*state.selmon).pertag.curtag as usize] [(*state.selmon).sellt as usize] = - &CONFIG.layouts[(*arg).l().unwrap()]; + &state.CONFIG.layouts[(*arg).l().unwrap()]; (*state.selmon).lt[(*state.selmon).sellt as usize] = (*state.selmon).pertag.ltidxs [(*state.selmon).pertag.curtag as usize] @@ -358,8 +358,9 @@ pub(crate) fn pushstack(state: &mut State, arg: *const Arg) { pub(crate) fn tag(state: &mut State, arg: *const Arg) { unsafe { - if !(*state.selmon).sel.is_null() && (*arg).ui() & *TAGMASK != 0 { - (*(*state.selmon).sel).tags = (*arg).ui() & *TAGMASK; + if !(*state.selmon).sel.is_null() && (*arg).ui() & state.tagmask() != 0 + { + (*(*state.selmon).sel).tags = (*arg).ui() & state.tagmask(); focus(state, null_mut()); arrange(state, state.selmon); } @@ -431,7 +432,7 @@ pub(crate) fn toggleview(state: &mut State, arg: *const Arg) { unsafe { let newtagset = (*state.selmon).tagset [(*state.selmon).seltags as usize] - ^ ((*arg).ui() & *TAGMASK); + ^ ((*arg).ui() & state.tagmask()); if newtagset != 0 { (*state.selmon).tagset[(*state.selmon).seltags as usize] = @@ -544,21 +545,25 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { lasttime = ev.motion.time; let mut nx = ocx + (ev.motion.x - x); let mut ny = ocy + (ev.motion.y - y); - if ((*state.selmon).wx - nx).abs() < CONFIG.snap as c_int { + if ((*state.selmon).wx - nx).abs() + < state.CONFIG.snap as c_int + { nx = (*state.selmon).wx; } else if (((*state.selmon).wx + (*state.selmon).ww) - (nx + width(c))) .abs() - < CONFIG.snap as c_int + < state.CONFIG.snap as c_int { nx = (*state.selmon).wx + (*state.selmon).ww - width(c); } - if ((*state.selmon).wy - ny).abs() < CONFIG.snap as c_int { + if ((*state.selmon).wy - ny).abs() + < state.CONFIG.snap as c_int + { ny = (*state.selmon).wy; } else if (((*state.selmon).wy + (*state.selmon).wh) - (ny + height(c))) .abs() - < CONFIG.snap as c_int + < state.CONFIG.snap as c_int { ny = (*state.selmon).wy + (*state.selmon).wh - height(c); @@ -568,8 +573,8 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { .arrange .0 .is_some() - && ((nx - c.x).abs() > CONFIG.snap as c_int - || (ny - c.y).abs() > CONFIG.snap as c_int) + && ((nx - c.x).abs() > state.CONFIG.snap as c_int + || (ny - c.y).abs() > state.CONFIG.snap as c_int) { togglefloating(state, null_mut()); } @@ -670,8 +675,8 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { .arrange .0 .is_some() - && ((nw - c.w).abs() > CONFIG.snap as c_int - || (nh - c.h).abs() > CONFIG.snap as c_int) + && ((nw - c.w).abs() > state.CONFIG.snap as c_int + || (nh - c.h).abs() > state.CONFIG.snap as c_int) { togglefloating(state, null_mut()); } @@ -716,14 +721,14 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { pub(crate) fn spawn(state: &mut State, arg: *const Arg) { unsafe { let mut argv = (*arg).v(); - if argv == *CONFIG.dmenucmd { + if argv == *state.CONFIG.dmenucmd { log::trace!("spawn: dmenucmd on monitor {}", (*state.selmon).num); argv.push("-m".into()); argv.push((*state.selmon).num.to_string()); } (*state.selmon).tagset[(*state.selmon).seltags as usize] &= - !*SCRATCHTAG; + !state.scratchtag(); let mut cmd = Command::new(argv[0].clone()); let cmd = if argv.len() > 1 { cmd.args(&argv[1..]) } else { &mut cmd }; @@ -740,7 +745,8 @@ pub(crate) fn toggletag(state: &mut State, arg: *const Arg) { if (*state.selmon).sel.is_null() { return; } - let newtags = (*(*state.selmon).sel).tags ^ ((*arg).ui() & *TAGMASK); + let newtags = + (*(*state.selmon).sel).tags ^ ((*arg).ui() & state.tagmask()); if newtags != 0 { (*(*state.selmon).sel).tags = newtags; focus(state, null_mut()); @@ -774,7 +780,7 @@ pub(crate) fn togglescratch(state: &mut State, arg: *const Arg) { c = (*state.selmon).clients; !c.is_null(); c = (*c).next) { - found = ((*c).tags & *SCRATCHTAG) != 0; + found = ((*c).tags & state.scratchtag()) != 0; if found { break; } @@ -782,7 +788,7 @@ pub(crate) fn togglescratch(state: &mut State, arg: *const Arg) { if found { let newtagset = (*state.selmon).tagset [(*state.selmon).seltags as usize] - ^ *SCRATCHTAG; + ^ state.scratchtag(); if newtagset != 0 { (*state.selmon).tagset[(*state.selmon).seltags as usize] = newtagset; diff --git a/src/layouts.rs b/src/layouts.rs index d5b7c27..496fe67 100644 --- a/src/layouts.rs +++ b/src/layouts.rs @@ -2,8 +2,8 @@ use std::{cmp::min, ffi::c_uint}; use libc::c_int; -use crate::{height, is_visible, nexttiled, resize}; -use rwm::{Monitor, State}; +use crate::{cfor, height, is_visible, nexttiled, resize}; +use crate::{Monitor, State}; pub(crate) fn monocle(state: &mut State, m: *mut Monitor) { unsafe { diff --git a/src/lib.rs b/src/lib.rs index 7db4278..66ff088 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,35 @@ +#![allow(clippy::missing_safety_doc, clippy::not_unsafe_ptr_arg_deref)] + use std::ffi::{c_int, c_uint}; use enums::Clk; use x11::xft::XftColor; +pub mod config; pub mod drw; pub mod enums; pub mod events; +pub mod handlers; +pub mod key_handlers; +pub mod layouts; pub mod util; +pub mod xembed; + +pub use main_::*; +mod main_; pub use state::*; mod state; +/// most applications want to start this way +pub const NORMAL_STATE: usize = 1; +/// application wants to start as an icon +pub const ICONIC_STATE: usize = 3; + +// from Xutil.h +/// for windows that are not mapped +pub const WITHDRAWN_STATE: usize = 0; + pub type Window = u64; pub type Clr = XftColor; diff --git a/src/main.rs b/src/main.rs index bb53885..a4ccff1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,2971 +1,20 @@ //! tiling window manager based on dwm -use std::cmp::max; -use std::ffi::{c_int, c_uint, c_ulong, CStr}; -use std::io::Read; -use std::mem::{size_of, MaybeUninit}; -use std::ptr::null_mut; -use std::sync::LazyLock; - -use key_handlers::view; -use libc::{c_long, c_uchar, pid_t, sigaction}; -use rwm::enums::XEmbed; -use x11::keysym::XK_Num_Lock; -use x11::xlib::{ - self, Above, AnyButton, AnyKey, AnyModifier, BadAccess, BadDrawable, - BadMatch, BadWindow, Below, ButtonPressMask, ButtonReleaseMask, - CWBackPixel, CWBackPixmap, CWBorderWidth, CWCursor, CWEventMask, CWHeight, - CWOverrideRedirect, CWSibling, CWStackMode, CWWidth, ClientMessage, - ControlMask, CopyFromParent, CurrentTime, Display, EnterWindowMask, - ExposureMask, False, FocusChangeMask, GrabModeAsync, GrabModeSync, - InputHint, IsViewable, LeaveWindowMask, LockMask, Mod1Mask, Mod2Mask, - Mod3Mask, Mod4Mask, Mod5Mask, NoEventMask, PAspect, PBaseSize, PMaxSize, - PMinSize, PResizeInc, PSize, ParentRelative, PointerMotionMask, - PointerRoot, PropModeAppend, PropModeReplace, PropertyChangeMask, - RevertToPointerRoot, ShiftMask, StructureNotifyMask, - SubstructureNotifyMask, SubstructureRedirectMask, Success, True, - XChangeProperty, XChangeWindowAttributes, XConfigureWindow, - XCreateSimpleWindow, XDestroyWindow, XErrorEvent, XFillRectangle, XFree, - XGetSelectionOwner, XInternAtom, XMapRaised, XMapSubwindows, XMapWindow, - XMoveResizeWindow, XPropertyEvent, XSelectInput, XSetErrorHandler, - XSetForeground, XSetSelectionOwner, XSetWindowAttributes, XSync, - XUnmapWindow, XWindowChanges, CWX, CWY, XA_ATOM, XA_CARDINAL, XA_STRING, - XA_WINDOW, XA_WM_NAME, -}; - +use rwm::{cleanup, run, scan, setup}; #[cfg(target_os = "linux")] use xcb::Connection; -use rwm::{ - drw, util, Arg, Client, Layout, LayoutFn, Monitor, Pertag, State, Systray, - Window, -}; - -use config::CONFIG; -use enums::{Clk, Col, Net, Scheme, WM}; -use rwm::drw::{fontset_create, Drw}; -use rwm::util::{die, ecalloc}; - -macro_rules! cfor { - ((; $cond:expr; $step:expr) $body:block ) => { - cfor!(({}; $cond; $step) $body) - }; - (($init:expr; $cond:expr; $step:expr) $body:block ) => { - $init; - while $cond { - $body; - $step; - } - }; -} - -/// function to be called on a startup error -extern "C" fn xerrorstart(_: *mut Display, _: *mut XErrorEvent) -> c_int { - panic!("another window manager is already running") -} - -// from Xproto.h -const X_SET_INPUT_FOCUS: u8 = 42; -const X_POLY_TEXT_8: u8 = 74; -const X_POLY_FILL_RECTANGLE: u8 = 70; -const X_POLY_SEGMENT: u8 = 66; -const X_CONFIGURE_WINDOW: u8 = 12; -const X_GRAB_BUTTON: u8 = 28; -const X_GRAB_KEY: u8 = 33; -const X_COPY_AREA: u8 = 62; - -// from cursorfont.h -const XC_LEFT_PTR: u8 = 68; -const XC_SIZING: u8 = 120; -const XC_FLEUR: u8 = 52; - -// from X.h -// const BUTTON_RELEASE: i32 = 5; -const XNONE: c_long = 0; - -// from Xutil.h -/// for windows that are not mapped -const WITHDRAWN_STATE: usize = 0; -/// most applications want to start this way -const NORMAL_STATE: usize = 1; -/// application wants to start as an icon -const ICONIC_STATE: usize = 3; - -/// Unfortunately this can't be packed into the State struct since it needs to -/// be accessed here in `xerror`, where I don't get a chance to pass State. -/// -/// What's going on here is that `checkotherwm` calls `XSetErrorHandler` to set -/// the handler temporarily to `xerrorstart`. `XSetErrorHandler` returns the -/// previous handler fn, which we store here. At the end of `checkotherwm`, we -/// then set the error handler to `xerror`, which, via `XERRORLIB`, is just a -/// wrapper around the original X error handler with a little logging and an -/// early return to allow certain kinds of errors. -/// -/// Obviously it would be nice to handle this with a closure in `checkotherwm`, -/// but `XSetErrorHandler` requires an `unsafe extern "C" fn`, not any old Fn. -static mut XERRORXLIB: Option< - unsafe extern "C" fn(*mut Display, *mut XErrorEvent) -> i32, -> = None; - -extern "C" fn xerror(mdpy: *mut Display, ee: *mut XErrorEvent) -> c_int { - unsafe { - let e = *ee; - if e.error_code == BadWindow - || (e.request_code == X_SET_INPUT_FOCUS && e.error_code == BadMatch) - || (e.request_code == X_POLY_TEXT_8 && e.error_code == BadDrawable) - || (e.request_code == X_POLY_FILL_RECTANGLE - && e.error_code == BadDrawable) - || (e.request_code == X_POLY_SEGMENT && e.error_code == BadDrawable) - || (e.request_code == X_CONFIGURE_WINDOW - && e.error_code == BadMatch) - || (e.request_code == X_GRAB_BUTTON && e.error_code == BadAccess) - || (e.request_code == X_GRAB_KEY && e.error_code == BadAccess) - || (e.request_code == X_COPY_AREA && e.error_code == BadDrawable) - { - return 0; - } - eprintln!( - "rwm: fatal error: request code={}, error code={}", - e.request_code, e.error_code - ); - (XERRORXLIB.unwrap())(mdpy, ee) - } -} - -extern "C" fn xerrordummy( - _dpy: *mut Display, - _ee: *mut xlib::XErrorEvent, -) -> c_int { - 0 -} - -const BROKEN: &CStr = c"broken"; - -type Atom = c_ulong; - -fn createmon() -> *mut Monitor { - log::trace!("createmon"); - - // I thought about trying to create a Monitor directly, followed by - // Box::into_raw(Box::new(m)), but we use libc::free to free the Monitors - // later. I'd have to replace that with Box::from_raw and allow it to drop - // for that to work I think. - let m: *mut Monitor = ecalloc(1, size_of::()).cast(); - - unsafe { - (*m).tagset[0] = 1; - (*m).tagset[1] = 1; - (*m).mfact = CONFIG.mfact; - (*m).nmaster = CONFIG.nmaster; - (*m).showbar = CONFIG.showbar; - (*m).topbar = CONFIG.topbar; - (*m).lt[0] = &CONFIG.layouts[0]; - (*m).lt[1] = &CONFIG.layouts[1 % CONFIG.layouts.len()]; - (*m).ltsymbol = CONFIG.layouts[0].symbol.clone(); - - (*m).pertag = Pertag { - curtag: 1, - prevtag: 1, - nmasters: vec![(*m).nmaster; CONFIG.tags.len() + 1], - mfacts: vec![(*m).mfact; CONFIG.tags.len() + 1], - sellts: vec![(*m).sellt; CONFIG.tags.len() + 1], - ltidxs: vec![(*m).lt; CONFIG.tags.len() + 1], - showbars: vec![(*m).showbar; CONFIG.tags.len() + 1], - }; - } - - m -} - -fn checkotherwm(dpy: *mut Display) { - log::trace!("checkotherwm"); - unsafe { - XERRORXLIB = XSetErrorHandler(Some(xerrorstart)); - xlib::XSelectInput( - dpy, - xlib::XDefaultRootWindow(dpy), - SubstructureRedirectMask, - ); - XSetErrorHandler(Some(xerror)); - xlib::XSync(dpy, False); - } -} - -fn setup(dpy: *mut Display) -> State { - log::trace!("setup"); - unsafe { - let mut wa = xlib::XSetWindowAttributes { - background_pixmap: 0, - background_pixel: 0, - border_pixmap: 0, - border_pixel: 0, - bit_gravity: 0, - win_gravity: 0, - backing_store: 0, - backing_planes: 0, - backing_pixel: 0, - save_under: 0, - event_mask: 0, - do_not_propagate_mask: 0, - override_redirect: 0, - colormap: 0, - cursor: 0, - }; - let mut sa = sigaction { - sa_sigaction: libc::SIG_IGN, - sa_mask: std::mem::zeroed(), - sa_flags: libc::SA_NOCLDSTOP - | libc::SA_NOCLDWAIT - | libc::SA_RESTART, - #[cfg(not(target_os = "macos"))] - sa_restorer: None, - }; - libc::sigemptyset(&mut sa.sa_mask); - libc::sigaction(libc::SIGCHLD, &sa, null_mut()); - - while libc::waitpid(-1, null_mut(), libc::WNOHANG) > 0 {} - - let screen = xlib::XDefaultScreen(dpy); - let sh = xlib::XDisplayHeight(dpy, screen); - let root = xlib::XRootWindow(dpy, screen); - let sw = xlib::XDisplayWidth(dpy, screen); - let mut drw = drw::create(dpy, screen, root, sw as u32, sh as u32); - if fontset_create(&mut drw, &CONFIG.fonts).is_err() - || drw.fonts.is_empty() - { - panic!("no fonts could be loaded"); - } - - /* init cursors */ - let cursors = rwm::Cursors { - normal: drw::cur_create(&drw, XC_LEFT_PTR as i32), - resize: drw::cur_create(&drw, XC_SIZING as i32), - move_: drw::cur_create(&drw, XC_FLEUR as i32), - }; - let mut state = State { - bh: drw.fonts[0].h as i32 + 2, - sw, - sh, - cursors, - wmatom: Default::default(), - netatom: Default::default(), - xatom: Default::default(), - dpy, - lrpad: drw.fonts[0].h as i32, - drw, - selmon: null_mut(), - mons: null_mut(), - stext: String::new(), - scheme: Default::default(), - screen, - root, - wmcheckwin: xlib::XCreateSimpleWindow( - dpy, root, 0, 0, 1, 1, 0, 0, 0, - ), - numlockmask: 0, - running: true, - systray: None, - - #[cfg(target_os = "linux")] - xcon: null_mut(), - }; - - updategeom(&mut state); - - /* init atoms */ - let utf8string = XInternAtom(state.dpy, c"UTF8_STRING".as_ptr(), False); - state.wmatom[WM::Protocols as usize] = - XInternAtom(state.dpy, c"WM_PROTOCOLS".as_ptr(), False); - state.wmatom[WM::Delete as usize] = - XInternAtom(state.dpy, c"WM_DELETE_WINDOW".as_ptr(), False); - state.wmatom[WM::State as usize] = - XInternAtom(state.dpy, c"WM_STATE".as_ptr(), False); - state.wmatom[WM::TakeFocus as usize] = - XInternAtom(state.dpy, c"WM_TAKE_FOCUS".as_ptr(), False); - - state.netatom[Net::ActiveWindow as usize] = - XInternAtom(state.dpy, c"_NET_ACTIVE_WINDOW".as_ptr(), False); - state.netatom[Net::Supported as usize] = - XInternAtom(state.dpy, c"_NET_SUPPORTED".as_ptr(), False); - - state.netatom[Net::SystemTray as usize] = - XInternAtom(state.dpy, c"_NET_SYSTEM_TRAY_S0".as_ptr(), False); - state.netatom[Net::SystemTrayOP as usize] = - XInternAtom(state.dpy, c"_NET_SYSTEM_TRAY_OPCODE".as_ptr(), False); - state.netatom[Net::SystemTrayOrientation as usize] = XInternAtom( - state.dpy, - c"_NET_SYSTEM_TRAY_ORIENTATION".as_ptr(), - False, - ); - state.netatom[Net::SystemTrayOrientationHorz as usize] = XInternAtom( - state.dpy, - c"_NET_SYSTEM_TRAY_ORIENTATION_HORZ".as_ptr(), - False, - ); - - state.netatom[Net::WMName as usize] = - XInternAtom(state.dpy, c"_NET_WM_NAME".as_ptr(), False); - state.netatom[Net::WMState as usize] = - XInternAtom(state.dpy, c"_NET_WM_STATE".as_ptr(), False); - state.netatom[Net::WMCheck as usize] = - XInternAtom(state.dpy, c"_NET_SUPPORTING_WM_CHECK".as_ptr(), False); - state.netatom[Net::WMFullscreen as usize] = - XInternAtom(state.dpy, c"_NET_WM_STATE_FULLSCREEN".as_ptr(), False); - state.netatom[Net::WMWindowType as usize] = - XInternAtom(state.dpy, c"_NET_WM_WINDOW_TYPE".as_ptr(), False); - state.netatom[Net::WMWindowTypeDialog as usize] = XInternAtom( - state.dpy, - c"_NET_WM_WINDOW_TYPE_DIALOG".as_ptr(), - False, - ); - state.netatom[Net::ClientList as usize] = - XInternAtom(state.dpy, c"_NET_CLIENT_LIST".as_ptr(), False); - - state.xatom[XEmbed::Manager as usize] = - XInternAtom(state.dpy, c"MANAGER".as_ptr(), False); - state.xatom[XEmbed::XEmbed as usize] = - XInternAtom(state.dpy, c"_XEMBED".as_ptr(), False); - state.xatom[XEmbed::XEmbedInfo as usize] = - XInternAtom(state.dpy, c"_XEMBED_INFO".as_ptr(), False); - - /* init appearance */ - for i in 0..CONFIG.colors.0.len() { - state.scheme.push(drw::scm_create( - &state.drw, - &CONFIG.colors.0[i], - 3, - )); - } - - // init system tray - updatesystray(&mut state); - - /* init bars */ - updatebars(&mut state); - updatestatus(&mut state); - - xlib::XChangeProperty( - state.dpy, - state.wmcheckwin, - state.netatom[Net::WMCheck as usize], - XA_WINDOW, - 32, - PropModeReplace, - &raw mut state.wmcheckwin as *mut c_uchar, - 1, - ); - xlib::XChangeProperty( - state.dpy, - state.wmcheckwin, - state.netatom[Net::WMName as usize], - utf8string, - 8, - PropModeReplace, - c"rwm".as_ptr() as *mut c_uchar, - 3, - ); - xlib::XChangeProperty( - state.dpy, - root, - state.netatom[Net::WMCheck as usize], - XA_WINDOW, - 32, - PropModeReplace, - &raw mut state.wmcheckwin as *mut c_uchar, - 1, - ); - /* EWMH support per view */ - xlib::XChangeProperty( - state.dpy, - root, - state.netatom[Net::Supported as usize], - XA_ATOM, - 32, - PropModeReplace, - &raw mut state.netatom as *mut c_uchar, - Net::Last as i32, - ); - xlib::XDeleteProperty( - state.dpy, - root, - state.netatom[Net::ClientList as usize], - ); - - // /* select events */ - wa.cursor = state.cursors.normal.cursor; - wa.event_mask = SubstructureRedirectMask - | SubstructureNotifyMask - | ButtonPressMask - | PointerMotionMask - | EnterWindowMask - | LeaveWindowMask - | StructureNotifyMask - | PropertyChangeMask; - xlib::XChangeWindowAttributes( - state.dpy, - root, - CWEventMask | CWCursor, - &mut wa, - ); - xlib::XSelectInput(state.dpy, root, wa.event_mask); - grabkeys(&mut state); - focus(&mut state, null_mut()); - - state - } -} - -fn focus(state: &mut State, mut c: *mut Client) { - log::trace!("focus: c = {c:?}"); - unsafe { - if c.is_null() || !is_visible(c) { - c = (*state.selmon).stack; - while !c.is_null() && !is_visible(c) { - c = (*c).snext; - } - } - if !(*state.selmon).sel.is_null() && (*state.selmon).sel != c { - unfocus(state, (*state.selmon).sel, false); - } - if !c.is_null() { - if (*c).mon != state.selmon { - state.selmon = (*c).mon; - } - if (*c).isurgent != 0 { - seturgent(state, c, false); - } - detachstack(c); - attachstack(c); - grabbuttons(state, c, true); - let color = state.scheme[(Scheme::Sel, Col::Border)].pixel; - xlib::XSetWindowBorder(state.dpy, (*c).win, color); - setfocus(state, c); - } else { - xlib::XSetInputFocus( - state.dpy, - state.root, - RevertToPointerRoot, - CurrentTime, - ); - xlib::XDeleteProperty( - state.dpy, - state.root, - state.netatom[Net::ActiveWindow as usize], - ); - } - (*state.selmon).sel = c; - drawbars(state); - } -} - -fn drawbars(state: &mut State) { - log::trace!("drawbars"); - unsafe { - let mut m = state.mons; - while !m.is_null() { - drawbar(state, m); - m = (*m).next; - } - } -} - -fn setfocus(state: &mut State, c: *mut Client) { - log::trace!("setfocus"); - unsafe { - if (*c).neverfocus == 0 { - xlib::XSetInputFocus( - state.dpy, - (*c).win, - RevertToPointerRoot, - CurrentTime, - ); - xlib::XChangeProperty( - state.dpy, - state.root, - state.netatom[Net::ActiveWindow as usize], - XA_WINDOW, - 32, - PropModeReplace, - (&mut (*c).win) as *mut u64 as *mut c_uchar, - 1, - ); - } - sendevent( - state, - (*c).win, - state.wmatom[WM::TakeFocus as usize], - NoEventMask as i32, - state.wmatom[WM::TakeFocus as usize] as i64, - CurrentTime as i64, - 0, - 0, - 0, - ); - } -} - -#[allow(clippy::too_many_arguments)] -fn sendevent( - state: &mut State, - w: Window, - proto: Atom, - mask: c_int, - d0: c_long, - d1: c_long, - d2: c_long, - d3: c_long, - d4: c_long, -) -> c_int { - log::trace!("sendevent"); - let mut n = 0; - let mut protocols = std::ptr::null_mut(); - let mt; - let mut exists = 0; - unsafe { - if proto == state.wmatom[WM::TakeFocus as usize] - || proto == state.wmatom[WM::Delete as usize] - { - mt = state.wmatom[WM::Protocols as usize]; - if xlib::XGetWMProtocols(state.dpy, w, &mut protocols, &mut n) != 0 - { - while exists == 0 && n > 0 { - exists = (*protocols.offset(n as isize) == proto) as c_int; - n -= 1; - } - XFree(protocols.cast()); - } - } else { - exists = 1; - mt = proto; - } - if exists != 0 { - let mut ev = xlib::XEvent { type_: ClientMessage }; - ev.client_message.window = w; - ev.client_message.message_type = mt; - ev.client_message.format = 32; - ev.client_message.data.set_long(0, d0); - ev.client_message.data.set_long(1, d1); - ev.client_message.data.set_long(2, d2); - ev.client_message.data.set_long(3, d3); - ev.client_message.data.set_long(4, d4); - xlib::XSendEvent(state.dpy, w, False, mask as i64, &mut ev); - } - exists - } -} - -fn grabbuttons(state: &mut State, c: *mut Client, focused: bool) { - log::trace!("grabbuttons"); - unsafe { - updatenumlockmask(state); - let modifiers = - [0, LockMask, state.numlockmask, state.numlockmask | LockMask]; - xlib::XUngrabButton(state.dpy, AnyButton as u32, AnyModifier, (*c).win); - if !focused { - xlib::XGrabButton( - state.dpy, - AnyButton as u32, - AnyModifier, - (*c).win, - False, - BUTTONMASK as u32, - GrabModeSync, - GrabModeSync, - XNONE as u64, - XNONE as u64, - ); - } - for button in &CONFIG.buttons { - if button.click == Clk::ClientWin as u32 { - for mod_ in modifiers { - xlib::XGrabButton( - state.dpy, - button.button, - button.mask | mod_, - (*c).win, - False, - BUTTONMASK as u32, - GrabModeAsync, - GrabModeSync, - XNONE as u64, - XNONE as u64, - ); - } - } - } - } -} - -fn arrange(state: &mut State, mut m: *mut Monitor) { - log::trace!("arrange"); - unsafe { - if !m.is_null() { - showhide(state, (*m).stack); - } else { - m = state.mons; - while !m.is_null() { - showhide(state, (*m).stack); - m = (*m).next; - } - } - - if !m.is_null() { - arrangemon(state, m); - restack(state, m); - } else { - m = state.mons; - while !m.is_null() { - arrangemon(state, m); - m = (*m).next; - } - } - } -} - -fn arrangemon(state: &mut State, m: *mut Monitor) { - log::trace!("arrangemon"); - unsafe { - (*m).ltsymbol = (*(*m).lt[(*m).sellt as usize]).symbol.clone(); - let arrange = (*(*m).lt[(*m).sellt as usize]).arrange.0; - if let Some(arrange) = arrange { - (arrange)(state, m); - } - } -} - -fn restack(state: &mut State, m: *mut Monitor) { - log::trace!("restack"); - drawbar(state, m); - unsafe { - if (*m).sel.is_null() { - return; - } - if (*(*m).sel).isfloating - || (*(*m).lt[(*m).sellt as usize]).arrange.0.is_none() - { - xlib::XRaiseWindow(state.dpy, (*(*m).sel).win); - } - if (*(*m).lt[(*m).sellt as usize]).arrange.0.is_some() { - let mut wc = xlib::XWindowChanges { - stack_mode: Below, - sibling: (*m).barwin, - x: Default::default(), - y: Default::default(), - width: Default::default(), - height: Default::default(), - border_width: Default::default(), - }; - let mut c = (*m).stack; - while !c.is_null() { - if !(*c).isfloating && is_visible(c) { - xlib::XConfigureWindow( - state.dpy, - (*c).win, - (CWSibling | CWStackMode) as c_uint, - &mut wc as *mut _, - ); - wc.sibling = (*c).win; - } - c = (*c).snext; - } - } - xlib::XSync(state.dpy, False); - let mut ev = xlib::XEvent { type_: 0 }; - while xlib::XCheckMaskEvent(state.dpy, EnterWindowMask, &mut ev) != 0 {} - } -} - -fn showhide(state: &mut State, c: *mut Client) { - log::trace!("showhide"); - unsafe { - if c.is_null() { - return; - } - if is_visible(c) { - // show clients top down - xlib::XMoveWindow(state.dpy, (*c).win, (*c).x, (*c).y); - if ((*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) - .arrange - .0 - .is_none() - || (*c).isfloating) - && !(*c).isfullscreen - { - resize(state, c, (*c).x, (*c).y, (*c).w, (*c).h, 0); - } - showhide(state, (*c).snext); - } else { - // hide clients bottom up - showhide(state, (*c).snext); - xlib::XMoveWindow(state.dpy, (*c).win, width(c) * -2, (*c).y); - } - } -} - -fn resize( - state: &mut State, - c: *mut Client, - mut x: i32, - mut y: i32, - mut w: i32, - mut h: i32, - interact: c_int, -) { - log::trace!("resize"); - if applysizehints(state, c, &mut x, &mut y, &mut w, &mut h, interact) != 0 { - resizeclient(state, c, x, y, w, h); - } -} - -fn resizeclient( - state: &mut State, - c: *mut Client, - x: i32, - y: i32, - w: i32, - h: i32, -) { - log::trace!("resizeclient"); - unsafe { - (*c).oldx = (*c).x; - (*c).oldy = (*c).y; - (*c).oldw = (*c).w; - (*c).oldh = (*c).h; - (*c).x = x; - (*c).y = y; - (*c).w = w; - (*c).h = h; - let mut wc = xlib::XWindowChanges { - x, - y, - width: w, - height: h, - border_width: (*c).bw, - sibling: 0, - stack_mode: 0, - }; - xlib::XConfigureWindow( - state.dpy, - (*c).win, - (CWX | CWY | CWWidth | CWHeight | CWBorderWidth) as u32, - &mut wc, - ); - configure(state, c); - xlib::XSync(state.dpy, False); - } -} - -fn resizebarwin(state: &mut State, m: *mut Monitor) { - unsafe { - let mut w = (*m).ww; - if CONFIG.showsystray - && m == systraytomon(state, m) - && !CONFIG.systrayonleft - { - w -= getsystraywidth(state) as i32; - } - XMoveResizeWindow( - state.dpy, - (*m).barwin, - (*m).wx, - (*m).by, - w as u32, - state.bh as u32, - ); - } -} - -fn configure(state: &mut State, c: *mut Client) { - log::trace!("configure"); - unsafe { - let mut ce = xlib::XConfigureEvent { - type_: x11::xlib::ConfigureNotify, - serial: 0, - send_event: 0, - display: state.dpy, - event: (*c).win, - window: (*c).win, - x: (*c).x, - y: (*c).y, - width: (*c).w, - height: (*c).h, - border_width: (*c).bw, - above: XNONE as u64, - override_redirect: False, - }; - xlib::XSendEvent( - state.dpy, - (*c).win, - False, - StructureNotifyMask, - &mut ce as *mut xlib::XConfigureEvent as *mut xlib::XEvent, - ); - } -} - -fn applysizehints( - state: &mut State, - c: *mut Client, - x: &mut i32, - y: &mut i32, - w: &mut i32, - h: &mut i32, - interact: c_int, -) -> c_int { - log::trace!("applysizehints"); - unsafe { - let m = (*c).mon; - let interact = interact != 0; - // set minimum possible - *w = 1.max(*w); - *h = 1.max(*h); - if interact { - if *x > state.sw { - *x = state.sw - width(c); - } - if *y > state.sh { - *y = state.sh - height(c); - } - if *x + *w + 2 * (*c).bw < 0 { - *x = 0; - } - if *y + *h + 2 * (*c).bw < 0 { - *y = 0; - } - } else { - if *x >= ((*m).wx + (*m).ww) { - *x = (*m).wx + (*m).ww - width(c); - } - if *y >= ((*m).wy + (*m).wh) { - *y = (*m).wy + (*m).wh - height(c); - } - if *x + *w + 2 * (*c).bw <= (*m).wx { - *x = (*m).wx; - } - if *y + *h + 2 * (*c).bw <= (*m).wy { - *y = (*m).wy; - } - } - if *h < state.bh { - *h = state.bh; - } - if *w < state.bh { - *w = state.bh; - } - if CONFIG.resize_hints - || (*c).isfloating - || (*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) - .arrange - .0 - .is_none() - { - if (*c).hintsvalid == 0 { - updatesizehints(state, c); - } - /* see last two sentences in ICCCM 4.1.2.3 */ - let baseismin = (*c).basew == (*c).minw && (*c).baseh == (*c).minh; - if !baseismin { - /* temporarily remove base dimensions */ - *w -= (*c).basew; - *h -= (*c).baseh; - } - /* adjust for aspect limits */ - if (*c).mina > 0.0 && (*c).maxa > 0.0 { - if (*c).maxa < *w as f32 / *h as f32 { - *w = (*h as f32 * (*c).maxa + 0.5) as i32; - } else if (*c).mina < *h as f32 / *w as f32 { - *h = (*w as f32 * (*c).mina + 0.5) as i32; - } - } - if baseismin { - /* increment calculation requires this */ - *w -= (*c).basew; - *h -= (*c).baseh; - } - /* adjust for increment value */ - if (*c).incw != 0 { - *w -= *w % (*c).incw; - } - if (*c).inch != 0 { - *h -= *h % (*c).inch; - } - /* restore base dimensions */ - *w = max(*w + (*c).basew, (*c).minw); - *h = max(*h + (*c).baseh, (*c).minh); - if (*c).maxw != 0 { - *w = std::cmp::min(*w, (*c).maxw); - } - if (*c).maxh != 0 { - *h = std::cmp::min(*h, (*c).maxh); - } - } - (*x != (*c).x || *y != (*c).y || *w != (*c).w || *h != (*c).h) as c_int - } -} - -fn updatesizehints(state: &mut State, c: *mut Client) { - log::trace!("updatesizehints"); - let mut msize: i64 = 0; - let mut size = xlib::XSizeHints { - flags: Default::default(), - x: Default::default(), - y: Default::default(), - width: Default::default(), - height: Default::default(), - min_width: Default::default(), - min_height: Default::default(), - max_width: Default::default(), - max_height: Default::default(), - width_inc: Default::default(), - height_inc: Default::default(), - min_aspect: xlib::AspectRatio { x: 0, y: 0 }, - max_aspect: xlib::AspectRatio { x: 0, y: 0 }, - base_width: Default::default(), - base_height: Default::default(), - win_gravity: Default::default(), - }; - unsafe { - if xlib::XGetWMNormalHints(state.dpy, (*c).win, &mut size, &mut msize) - == 0 - { - /* size is uninitialized, ensure that size.flags aren't used */ - size.flags = PSize; - } - if size.flags & PBaseSize != 0 { - (*c).basew = size.base_width; - (*c).baseh = size.base_height; - } else if size.flags & PMinSize != 0 { - (*c).basew = size.min_width; - (*c).baseh = size.min_height; - } else { - (*c).basew = 0; - (*c).baseh = 0; - } - - if size.flags & PResizeInc != 0 { - (*c).incw = size.width_inc; - (*c).inch = size.height_inc; - } else { - (*c).incw = 0; - (*c).inch = 0; - } - - if size.flags & PMaxSize != 0 { - (*c).maxw = size.max_width; - (*c).maxh = size.max_height; - } else { - (*c).maxw = 0; - (*c).maxh = 0; - } - - if size.flags & PMinSize != 0 { - (*c).minw = size.min_width; - (*c).minh = size.min_height; - } else if size.flags & PBaseSize != 0 { - (*c).minw = size.base_width; - (*c).minh = size.base_height; - } else { - (*c).minw = 0; - (*c).minh = 0; - } - - if size.flags & PAspect != 0 { - (*c).mina = size.min_aspect.y as f32 / size.min_aspect.x as f32; - (*c).maxa = size.max_aspect.x as f32 / size.max_aspect.y as f32; - } else { - (*c).mina = 0.0; - (*c).maxa = 0.0; - } - - (*c).isfixed = ((*c).maxw != 0 - && (*c).maxh != 0 - && (*c).maxw == (*c).minw - && (*c).maxh == (*c).minh) as c_int; - (*c).hintsvalid = 1; - } -} - -fn pop(state: &mut State, c: *mut Client) { - log::trace!("pop"); - detach(c); - attach(c); - focus(state, c); - unsafe { - arrange(state, (*c).mon); - } -} - -fn detach(c: *mut Client) { - log::trace!("detach"); - unsafe { - let mut tc: *mut *mut Client = &mut (*(*c).mon).clients; - while !(*tc).is_null() && *tc != c { - tc = &mut (*(*tc)).next; - } - *tc = (*c).next; - } -} - -fn nexttiled(mut c: *mut Client) -> *mut Client { - log::trace!("nexttiled"); - unsafe { - while !c.is_null() && ((*c).isfloating || !is_visible(c)) { - c = (*c).next; - } - c - } -} - -fn grabkeys(state: &mut State) { - log::trace!("grabkeys"); - unsafe { - updatenumlockmask(state); - let modifiers = - [0, LockMask, state.numlockmask, state.numlockmask | LockMask]; - let (mut start, mut end, mut skip): (i32, i32, i32) = (0, 0, 0); - xlib::XUngrabKey(state.dpy, AnyKey, AnyModifier, state.root); - xlib::XDisplayKeycodes(state.dpy, &mut start, &mut end); - let syms = xlib::XGetKeyboardMapping( - state.dpy, - start as u8, - end - start + 1, - &mut skip, - ); - if syms.is_null() { - return; - } - for k in start..=end { - for key in &CONFIG.keys { - // skip modifier codes, we do that ourselves - if key.keysym - == (*syms.offset(((k - start) * skip) as isize)) as u64 - { - for m in modifiers { - xlib::XGrabKey( - state.dpy, - k, - key.mod_ | m, - state.root, - True, - GrabModeAsync, - GrabModeAsync, - ); - } - } - } - } - XFree(syms.cast()); - } -} - -fn updatenumlockmask(state: &mut State) { - log::trace!("updatenumlockmask"); - unsafe { - state.numlockmask = 0; - let modmap = xlib::XGetModifierMapping(state.dpy); - for i in 0..8 { - for j in 0..(*modmap).max_keypermod { - if *(*modmap) - .modifiermap - .offset((i * (*modmap).max_keypermod + j) as isize) - == xlib::XKeysymToKeycode(state.dpy, XK_Num_Lock as u64) - { - state.numlockmask = 1 << i; - } - } - } - xlib::XFreeModifiermap(modmap); - } -} - -fn seturgent(state: &mut State, c: *mut Client, urg: bool) { - log::trace!("seturgent"); - unsafe { - (*c).isurgent = urg as c_int; - let wmh = xlib::XGetWMHints(state.dpy, (*c).win); - if wmh.is_null() { - return; - } - (*wmh).flags = if urg { - (*wmh).flags | xlib::XUrgencyHint - } else { - (*wmh).flags & !{ xlib::XUrgencyHint } - }; - xlib::XSetWMHints(state.dpy, (*c).win, wmh); - XFree(wmh.cast()); - } -} - -fn unfocus(state: &mut State, c: *mut Client, setfocus: bool) { - log::trace!("unfocus"); - if c.is_null() { - return; - } - grabbuttons(state, c, false); - unsafe { - // scheme[SchemeNorm][ColBorder].pixel - let color = state.scheme[(Scheme::Norm, Col::Border)].pixel; - xlib::XSetWindowBorder(state.dpy, (*c).win, color); - if setfocus { - xlib::XSetInputFocus( - state.dpy, - state.root, - RevertToPointerRoot, - CurrentTime, - ); - xlib::XDeleteProperty( - state.dpy, - state.root, - state.netatom[Net::ActiveWindow as usize], - ); - } - } -} - -fn updatestatus(state: &mut State) { - log::trace!("updatestatus"); - if gettextprop(state.dpy, state.root, XA_WM_NAME, &mut state.stext) == 0 { - state.stext = "rwm-1.0".to_string(); - } - drawbar(state, state.selmon); - updatesystray(state); -} - -fn updatesystrayicongeom( - state: &mut State, - i: *mut Client, - w: c_int, - h: c_int, -) { - if i.is_null() { - return; - } - unsafe { - let i = &mut *i; - i.h = state.bh; - if w == h { - i.w = state.bh; - } else if h == state.bh { - i.w = w; - } else { - i.w = (state.bh as f32 * (w as f32 / h as f32)) as i32; - } - applysizehints(state, i, &mut i.x, &mut i.y, &mut i.w, &mut i.h, False); - // force icons into the systray dimensions if they don't want to - if i.h > state.bh { - if i.w == i.h { - i.w = state.bh; - } else { - i.w = (state.bh as f32 * (i.w as f32 / i.h as f32)) as i32; - } - i.h = state.bh; - } - } -} - -fn updatesystrayiconstate( - state: &mut State, - i: *mut Client, - ev: *mut XPropertyEvent, -) { - unsafe { - let mut flags: Atom = 0; - let code; - if !CONFIG.showsystray - || i.is_null() - || (*ev).atom != state.xatom[XEmbed::XEmbedInfo as usize] - { - flags = - getatomprop(state, i, state.xatom[XEmbed::XEmbedInfo as usize]); - if flags == 0 { - return; - } - } - let i = &mut *i; - if flags & XEMBED_MAPPED != 0 && i.tags == 0 { - i.tags = 1; - code = XEMBED_WINDOW_ACTIVATE; - XMapRaised(state.dpy, i.win); - setclientstate(state, i, NORMAL_STATE); - } else if (flags & XEMBED_MAPPED) == 0 && i.tags != 0 { - i.tags = 0; - code = XEMBED_WINDOW_DEACTIVATE; - XUnmapWindow(state.dpy, i.win); - setclientstate(state, i, WITHDRAWN_STATE); - } else { - return; - } - sendevent( - state, - i.win, - state.xatom[XEmbed::XEmbed as usize], - StructureNotifyMask as i32, - CurrentTime as i64, - code as i64, - 0, - state.systray().win as i64, - XEMBED_EMBEDDED_VERSION as i64, - ); - } -} - -const fn default_window_attributes() -> XSetWindowAttributes { - XSetWindowAttributes { - background_pixmap: 0, - background_pixel: 0, - border_pixmap: 0, - border_pixel: 0, - bit_gravity: 0, - win_gravity: 0, - backing_store: 0, - backing_planes: 0, - backing_pixel: 0, - save_under: 0, - event_mask: 0, - do_not_propagate_mask: 0, - override_redirect: 0, - colormap: 0, - cursor: 0, - } -} - -fn updatesystray(state: &mut State) { - unsafe { - let mut wa = default_window_attributes(); - let mut wc: XWindowChanges; - let mut i: *mut Client; - let m: *mut Monitor = systraytomon(state, null_mut()); - let mut x: c_int = (*m).mx + (*m).mw; - let sw = textw(&mut state.drw, &state.stext, 0) - + CONFIG.systrayspacing as i32; - let mut w = 1; - - if !CONFIG.showsystray { - return; - } - if CONFIG.systrayonleft { - x -= sw + state.lrpad / 2; - } - if state.systray.is_none() { - // init systray - let win = XCreateSimpleWindow( - state.dpy, - state.root, - x, - (*m).by, - w, - state.bh as u32, - 0, - 0, - state.scheme[(Scheme::Sel, Col::Bg)].pixel, - ); - wa.event_mask = ButtonPressMask | ExposureMask; - wa.override_redirect = True; - wa.background_pixel = state.scheme[(Scheme::Norm, Col::Bg)].pixel; - XSelectInput(state.dpy, win, SubstructureNotifyMask); - XChangeProperty( - state.dpy, - win, - state.netatom[Net::SystemTrayOrientation as usize], - XA_CARDINAL, - 32, - PropModeReplace, - &state.netatom[Net::SystemTrayOrientationHorz as usize] - as *const _ as *const _, - 1, - ); - XChangeWindowAttributes( - state.dpy, - win, - CWEventMask | CWOverrideRedirect | CWBackPixel, - &mut wa, - ); - XMapRaised(state.dpy, win); - XSetSelectionOwner( - state.dpy, - state.netatom[Net::SystemTray as usize], - win, - CurrentTime, - ); - if XGetSelectionOwner( - state.dpy, - state.netatom[Net::SystemTray as usize], - ) == win - { - sendevent( - state, - state.root, - state.xatom[XEmbed::Manager as usize], - StructureNotifyMask as i32, - CurrentTime as i64, - state.netatom[Net::SystemTray as usize] as i64, - win as i64, - 0_i64, - 0_i64, - ); - XSync(state.dpy, False); - state.systray = Some(Systray { win, icons: null_mut() }); - } else { - log::error!("unable to obtain system tray"); - return; - } - } // end if !SYSTRAY - cfor!(((w, i) = (0, state.systray().icons); - !i.is_null(); - i = (*i).next) { - // make sure the background color stays the same - wa.background_pixel = state.scheme[(Scheme::Norm , Col::Bg )].pixel; - XChangeWindowAttributes(state.dpy, (*i).win, CWBackPixel, &mut wa); - XMapRaised(state.dpy, (*i).win); - w += CONFIG.systrayspacing; - (*i).x = w as i32; - XMoveResizeWindow(state.dpy, (*i).win, (*i).x, 0, (*i).w as u32, (*i).h as u32); - w += (*i).w as u32; - if (*i).mon != m { - (*i).mon = m; - } - }); - w = if w != 0 { w + CONFIG.systrayspacing } else { 1 }; - x -= w as i32; - XMoveResizeWindow( - state.dpy, - state.systray().win, - x, - (*m).by, - w, - state.bh as u32, - ); - wc = XWindowChanges { - x, - y: (*m).by, - width: w as i32, - height: state.bh, - border_width: 0, - sibling: (*m).barwin, - stack_mode: Above, - }; - XConfigureWindow( - state.dpy, - state.systray().win, - (CWX | CWY | CWWidth | CWHeight | CWSibling | CWStackMode) as u32, - &mut wc, - ); - XMapWindow(state.dpy, state.systray().win); - XMapSubwindows(state.dpy, state.systray().win); - // redraw background - XSetForeground( - state.dpy, - state.drw.gc, - state.scheme[(Scheme::Norm, Col::Bg)].pixel, - ); - XFillRectangle( - state.dpy, - state.systray().win, - state.drw.gc, - 0, - 0, - w, - state.bh as u32, - ); - XSync(state.dpy, False); - } // end unsafe -} - -fn wintosystrayicon(state: &State, w: Window) -> *mut Client { - unsafe { - let mut i = null_mut(); - if !CONFIG.showsystray || w == 0 { - return i; - } - cfor!((i = state.systray().icons; !i.is_null() && (*i).win != w; - i = (*i).next) {}); - - i - } -} - -fn systraytomon(state: &State, m: *mut Monitor) -> *mut Monitor { - unsafe { - let mut t: *mut Monitor; - let mut i; - let mut n; - if CONFIG.systraypinning == 0 { - if m.is_null() { - return state.selmon; - } - if m == state.selmon { - return m; - } else { - return null_mut(); - } - } - cfor!(((n, t) = (1, state.mons); - !t.is_null() && !(*t).next.is_null(); - (n, t) = (n+1, (*t).next)) {}); - cfor!(((i, t) = (1, state.mons); - !t.is_null() && !(*t).next.is_null() && i < CONFIG.systraypinning; - (i, t) = (i+1, (*t).next)) {}); - if CONFIG.systraypinningfailfirst && n < CONFIG.systraypinning { - return state.mons; - } - - t - } -} - -fn textw(drw: &mut Drw, x: &str, lrpad: c_int) -> c_int { - log::trace!("textw"); - unsafe { drw::fontset_getwidth(drw, x) as c_int + lrpad } -} - -fn drawbar(state: &mut State, m: *mut Monitor) { - log::trace!("drawbar"); - unsafe { - let mut tw = 0; - let mut stw = 0; - let boxs = state.drw.fonts[0].h / 9; - let boxw = state.drw.fonts[0].h / 6 + 2; - let (mut occ, mut urg) = (0, 0); - - if CONFIG.showsystray - && m == systraytomon(state, m) - && !CONFIG.systrayonleft - { - stw = getsystraywidth(state); - } - - if !(*m).showbar { - return; - } - - // draw status first so it can be overdrawn by tags later - if m == state.selmon { - // status is only drawn on selected monitor - drw::setscheme(&mut state.drw, state.scheme[Scheme::Norm].clone()); - tw = textw(&mut state.drw, &state.stext, state.lrpad / 2) + 2; // 2px right padding - log::trace!("drawbar: text"); - drw::text( - &mut state.drw, - (*m).ww - tw - stw as i32, - 0, - tw as u32, - state.bh as u32, - (state.lrpad / 2 - 2) as u32, - &state.stext, - 0, - ); - } - - resizebarwin(state, m); - - let mut c = (*m).clients; - while !c.is_null() { - occ |= (*c).tags; - if (*c).isurgent != 0 { - urg |= (*c).tags; - } - c = (*c).next; - } - - let mut x = 0; - for (i, tag) in CONFIG.tags.iter().enumerate() { - let text = tag.to_owned(); - let w = textw(&mut state.drw, &text, state.lrpad); - drw::setscheme( - &mut state.drw, - state.scheme[if ((*m).tagset[(*m).seltags as usize] & 1 << i) - != 0 - { - Scheme::Sel - } else { - Scheme::Norm - }] - .clone(), - ); - log::trace!("drawbar: text 2"); - drw::text( - &mut state.drw, - x, - 0, - w as u32, - state.bh as u32, - state.lrpad as u32 / 2, - &text, - (urg as i32) & 1 << i, - ); - - if (occ & 1 << i) != 0 { - drw::rect( - &mut state.drw, - x + boxs as i32, - boxs as i32, - boxw, - boxw, - (m == state.selmon - && !(*state.selmon).sel.is_null() - && ((*(*state.selmon).sel).tags & 1 << i) != 0) - as c_int, - (urg & 1 << i) != 0, - ); - } - x += w as i32; - } - - let w = textw(&mut state.drw, &(*m).ltsymbol, state.lrpad); - drw::setscheme(&mut state.drw, state.scheme[Scheme::Norm].clone()); - log::trace!("drawbar: text 3"); - x = drw::text( - &mut state.drw, - x, - 0, - w as u32, - state.bh as u32, - state.lrpad as u32 / 2, - &(*m).ltsymbol, - 0, - ) as i32; - log::trace!("finished drawbar text 3"); - - let w = (*m).ww - tw - stw as i32 - x; - if w > state.bh { - if !(*m).sel.is_null() { - drw::setscheme( - &mut state.drw, - state.scheme[if m == state.selmon { - Scheme::Sel - } else { - Scheme::Norm - }] - .clone(), - ); - log::trace!("drawbar: text 4"); - drw::text( - &mut state.drw, - x, - 0, - w as u32, - state.bh as u32, - state.lrpad as u32 / 2, - &(*(*m).sel).name, - 0, - ); - if (*(*m).sel).isfloating { - drw::rect( - &mut state.drw, - x + boxs as i32, - boxs as i32, - boxw, - boxw, - (*(*m).sel).isfixed, - false, - ); - } - } else { - drw::setscheme( - &mut state.drw, - state.scheme[Scheme::Norm].clone(), - ); - drw::rect( - &mut state.drw, - x, - 0, - w as u32, - state.bh as u32, - 1, - true, - ); - } - } - drw::map( - &state.drw, - (*m).barwin, - 0, - 0, - (*m).ww as u32 - stw, - state.bh as u32, - ); - } -} - -fn gettextprop( - dpy: *mut Display, - w: Window, - atom: Atom, - text: &mut String, -) -> c_int { - log::trace!("gettextprop"); - unsafe { - let mut name = xlib::XTextProperty { - value: std::ptr::null_mut(), - encoding: 0, - format: 0, - nitems: 0, - }; - let c = xlib::XGetTextProperty(dpy, w, &mut name, atom); - if c == 0 || name.nitems == 0 { - return 0; - } - - let mut n = 0; - let mut list: *mut *mut i8 = std::ptr::null_mut(); - if name.encoding == XA_STRING { - let name_val = CStr::from_ptr(name.value.cast()); - *text = name_val.to_string_lossy().to_string(); - } else if xlib::XmbTextPropertyToTextList( - dpy, - &name, - &mut list, - &mut n as *mut _, - ) >= Success as i32 - && n > 0 - && !(*list).is_null() - { - // TODO handle this properly. *list is a "string" in some encoding I - // don't understand. the main test case I noticed an issue with was - // a browser tab with a ·, which was initially taking the value -73 - // as an i8, which is the correct character 183 as a u8. This - // solution works for characters like that that fit in a u8 but - // doesn't work for larger characters like ў (cyrillic short u). - // actually `list` doesn't even contain the right characters for the - // short u. it just starts at the space after it, as demonstrated by - // using libc::printf to try to print it. - // - // Looks like my encoding is different. Getting 238 in Rust vs 287 - // in C. Using XGetAtomName shows 238 is UTF8_STRING, while 287 is - // _NET_WM_WINDOW_TYPE_POPUP_MENU (??). In dwm in the VM, 287 is - // also UTF8_STRING - *text = String::new(); - let mut c = *list; - while *c != 0 { - text.push(char::from(*c as u8)); - c = c.offset(1); - } - xlib::XFreeStringList(list); - } - xlib::XFree(name.value as *mut _); - } - 1 -} - -fn updatebars(state: &mut State) { - log::trace!("updatebars"); - let mut wa = xlib::XSetWindowAttributes { - override_redirect: True, - background_pixmap: ParentRelative as u64, - event_mask: ButtonPressMask | ExposureMask, - // everything else should be uninit I guess - background_pixel: 0, - border_pixmap: 0, - border_pixel: 0, - bit_gravity: 0, - win_gravity: 0, - backing_store: 0, - backing_planes: 0, - backing_pixel: 0, - save_under: 0, - do_not_propagate_mask: 0, - colormap: 0, - cursor: 0, - }; - let mut ch = xlib::XClassHint { - res_name: c"rwm".as_ptr().cast_mut(), - res_class: c"rwm".as_ptr().cast_mut(), - }; - - unsafe { - let mut m = state.mons; - while !m.is_null() { - if (*m).barwin != 0 { - continue; - } - let mut w = (*m).ww; - if CONFIG.showsystray && m == systraytomon(state, m) { - w -= getsystraywidth(state) as i32; - } - (*m).barwin = xlib::XCreateWindow( - state.dpy, - state.root, - (*m).wx as c_int, - (*m).by as c_int, - w as c_uint, - state.bh as c_uint, - 0, - xlib::XDefaultDepth(state.dpy, state.screen), - CopyFromParent as c_uint, - xlib::XDefaultVisual(state.dpy, state.screen), - CWOverrideRedirect | CWBackPixmap | CWEventMask, - &mut wa, - ); - xlib::XDefineCursor( - state.dpy, - (*m).barwin, - state.cursors.normal.cursor, - ); - if CONFIG.showsystray && m == systraytomon(state, m) { - xlib::XMapRaised(state.dpy, state.systray().win); - } - xlib::XMapRaised(state.dpy, (*m).barwin); - xlib::XSetClassHint(state.dpy, (*m).barwin, &mut ch); - m = (*m).next; - } - } -} - -fn updategeom(state: &mut State) -> i32 { - log::trace!("updategeom"); - unsafe { - let mut dirty = 0; - if x11::xinerama::XineramaIsActive(state.dpy) != 0 { - log::trace!("updategeom: xinerama active"); - - let mut nn = 0; - let info = x11::xinerama::XineramaQueryScreens( - state.dpy as *mut _, - &mut nn, - ); - - let mut n = 0; - let mut m = state.mons; - while !m.is_null() { - m = (*m).next; - n += 1; - } - - // only consider unique geometries as separate screens - let unique: *mut x11::xinerama::XineramaScreenInfo = ecalloc( - nn as usize, - size_of::(), - ) - .cast(); - // Safety: we obviously just constructed this with this size. don't - // forget to free it later! - let unique = std::slice::from_raw_parts_mut(unique, nn as usize); - let mut i = 0; - let mut j = 0; - while i < nn { - if isuniquegeom(unique, j, info.offset(i as isize) as *mut _) - != 0 - { - libc::memcpy( - (&mut unique[j]) as *mut _ as *mut _, - info.offset(i as isize).cast(), - size_of::(), - ); - j += 1; - } - i += 1; - } - xlib::XFree(info.cast()); - nn = j as i32; - - // new monitors if nn > n - if nn > n { - log::trace!("updategeom: adding monitors"); - } - for _ in n..nn { - let mut m = state.mons; - while !m.is_null() && !(*m).next.is_null() { - m = (*m).next; - } - if !m.is_null() { - (*m).next = createmon(); - } else { - state.mons = createmon(); - } - } - - let mut i = 0; - let mut m = state.mons; - while i < nn && !m.is_null() { - if i >= n - || unique[i as usize].x_org != (*m).mx as i16 - || unique[i as usize].y_org != (*m).my as i16 - || unique[i as usize].width != (*m).mw as i16 - || unique[i as usize].height != (*m).mh as i16 - { - dirty = 1; - (*m).num = i; - - (*m).mx = unique[i as usize].x_org as i32; - (*m).wx = unique[i as usize].x_org as i32; - - (*m).my = unique[i as usize].y_org as i32; - (*m).wy = unique[i as usize].y_org as i32; - - (*m).mw = unique[i as usize].width as i32; - (*m).ww = unique[i as usize].width as i32; - - (*m).mh = unique[i as usize].height as i32; - (*m).wh = unique[i as usize].height as i32; - - updatebarpos(state, m); - } - m = (*m).next; - i += 1; - } - - // removed monitors if n > nn - if n > nn { - log::trace!("updategeom: removing monitors"); - } - for _ in nn..n { - let mut m = state.mons; - while !m.is_null() && !(*m).next.is_null() { - m = (*m).next; - } - let mut c = (*m).clients; - while !c.is_null() { - dirty = 1; - (*m).clients = (*c).next; - detachstack(c); - (*c).mon = state.mons; - attach(c); - attachstack(c); - c = (*m).clients; - } - if m == state.selmon { - state.selmon = state.mons; - } - cleanupmon(m, state); - } - libc::free(unique.as_mut_ptr().cast()); - } else { - log::trace!("updategeom: default monitor setup"); - - // default monitor setup - if state.mons.is_null() { - state.mons = createmon(); - } - if (*state.mons).mw != state.sw || (*state.mons).mh != state.sh { - dirty = 1; - (*state.mons).mw = state.sw; - (*state.mons).ww = state.sw; - (*state.mons).mh = state.sh; - (*state.mons).wh = state.sh; - updatebarpos(state, state.mons); - } - } - if dirty != 0 { - state.selmon = state.mons; - state.selmon = wintomon(state, state.root); - } - dirty - } -} - -fn wintomon(state: &mut State, w: Window) -> *mut Monitor { - log::trace!("wintomon"); - unsafe { - let mut x = 0; - let mut y = 0; - if w == state.root && getrootptr(state, &mut x, &mut y) != 0 { - return recttomon(state, x, y, 1, 1); - } - let mut m = state.mons; - while !m.is_null() { - if w == (*m).barwin { - return m; - } - m = (*m).next; - } - let c = wintoclient(state, w); - if !c.is_null() { - return (*c).mon; - } - state.selmon - } -} - -fn wintoclient(state: &mut State, w: u64) -> *mut Client { - log::trace!("wintoclient"); - unsafe { - let mut m = state.mons; - while !m.is_null() { - let mut c = (*m).clients; - while !c.is_null() { - if (*c).win == w { - return c; - } - c = (*c).next; - } - m = (*m).next; - } - } - std::ptr::null_mut() -} - -fn recttomon( - state: &State, - x: c_int, - y: c_int, - w: c_int, - h: c_int, -) -> *mut Monitor { - log::trace!("recttomon"); - unsafe { - let mut r = state.selmon; - let mut area = 0; - let mut m = state.mons; - while !m.is_null() { - let a = intersect(x, y, w, h, m); - if a > area { - area = a; - r = m; - } - m = (*m).next; - } - r - } -} - -fn removesystrayicon(state: &mut State, i: *mut Client) { - unsafe { - if !CONFIG.showsystray || i.is_null() { - return; - } - let mut ii: *mut *mut Client; - cfor!(( - ii = &mut state.systray_mut().icons as *mut _; - !ii.is_null() && *ii != i; - ii = &mut (*(*ii)).next) {}); - if !ii.is_null() { - *ii = (*i).next; - } - libc::free(i.cast()); - } -} - -// "macros" - -#[inline] -fn intersect(x: c_int, y: c_int, w: c_int, h: c_int, m: *mut Monitor) -> c_int { - use std::cmp::{max, min}; - unsafe { - max(0, min((x) + (w), (*m).wx + (*m).ww) - max(x, (*m).wx)) - * max(0, min((y) + (h), (*m).wy + (*m).wh) - max(y, (*m).wy)) - } -} - -#[inline] -fn width(x: *mut Client) -> i32 { - unsafe { (*x).w + 2 * (*x).bw } -} - -#[inline] -fn height(x: *mut Client) -> i32 { - unsafe { (*x).h + 2 * (*x).bw } -} - -#[inline] -fn cleanmask(state: &State, mask: u32) -> u32 { - mask & !(state.numlockmask | LockMask) - & (ShiftMask - | ControlMask - | Mod1Mask - | Mod2Mask - | Mod3Mask - | Mod4Mask - | Mod5Mask) -} - -fn getrootptr(state: &mut State, x: *mut c_int, y: *mut c_int) -> c_int { - unsafe { - let mut di = 0; - let mut dui = 0; - let mut dummy = 0; - xlib::XQueryPointer( - state.dpy, state.root, &mut dummy, &mut dummy, x, y, &mut di, - &mut di, &mut dui, - ) - } -} - -/// remove `mon` from the linked list of `Monitor`s in `state.MONS` and free it. -fn cleanupmon(mon: *mut Monitor, state: &mut State) { - unsafe { - if mon == state.mons { - state.mons = (*state.mons).next; - } else { - let mut m = state.mons; - while !m.is_null() && (*m).next != mon { - m = (*m).next; - } - (*m).next = (*mon).next; - } - xlib::XUnmapWindow(state.dpy, (*mon).barwin); - xlib::XDestroyWindow(state.dpy, (*mon).barwin); - libc::free(mon.cast()); - } -} - -fn attachstack(c: *mut Client) { - log::trace!("attachstack"); - unsafe { - (*c).snext = (*(*c).mon).stack; - (*(*c).mon).stack = c; - } -} - -fn attach(c: *mut Client) { - log::trace!("attach"); - unsafe { - (*c).next = (*(*c).mon).clients; - (*(*c).mon).clients = c; - } -} - -fn detachstack(c: *mut Client) { - log::trace!("detachstack"); - unsafe { - let mut tc: *mut *mut Client = &mut (*(*c).mon).stack; - while !(*tc).is_null() && *tc != c { - tc = &mut (*(*tc)).snext; - } - *tc = (*c).snext; - - if c == (*(*c).mon).sel { - let mut t = (*(*c).mon).stack; - while !t.is_null() && !is_visible(t) { - t = (*t).snext; - } - (*(*c).mon).sel = t; - } - } -} - -/// this is actually a macro in the C code, but an inline function is probably -/// as close as I can get -#[inline] -fn is_visible(c: *const Client) -> bool { - unsafe { - ((*c).tags & (*(*c).mon).tagset[(*(*c).mon).seltags as usize]) != 0 - } -} - -fn updatebarpos(state: &mut State, m: *mut Monitor) { - log::trace!("updatebarpos"); - - unsafe { - (*m).wy = (*m).my; - (*m).wh = (*m).mh; - if (*m).showbar { - (*m).wh -= state.bh; - (*m).by = if (*m).topbar { (*m).wy } else { (*m).wy + (*m).wh }; - (*m).wy = if (*m).topbar { (*m).wy + state.bh } else { (*m).wy }; - } else { - (*m).by = -state.bh; - } - } -} - -fn isuniquegeom( - unique: &mut [x11::xinerama::XineramaScreenInfo], - mut n: usize, - info: *mut x11::xinerama::XineramaScreenInfo, -) -> c_int { - unsafe { - assert!(!info.is_null()); - let info = &*info; - while n > 0 { - n -= 1; // pretty sure this happens immediately in `while (n--)` - - if unique[n].x_org == info.x_org - && unique[n].y_org == info.y_org - && unique[n].width == info.width - && unique[n].height == info.height - { - return 0; - } - } - 1 - } -} - -fn cleanup(mut state: State) { - log::trace!("entering cleanup"); - - unsafe { - let a = Arg::Ui(!0); - view(&mut state, &a); - (*state.selmon).lt[(*state.selmon).sellt as usize] = - &Layout { symbol: String::new(), arrange: LayoutFn(None) }; - - let mut m = state.mons; - while !m.is_null() { - while !(*m).stack.is_null() { - unmanage(&mut state, (*m).stack, 0); - } - m = (*m).next; - } - - xlib::XUngrabKey(state.dpy, AnyKey, AnyModifier, state.root); - - while !state.mons.is_null() { - cleanupmon(state.mons, &mut state); - } - - if CONFIG.showsystray { - XUnmapWindow(state.dpy, state.systray().win); - XDestroyWindow(state.dpy, state.systray().win); - } - - xlib::XDestroyWindow(state.dpy, state.wmcheckwin); - xlib::XSync(state.dpy, False); - xlib::XSetInputFocus( - state.dpy, - PointerRoot as u64, - RevertToPointerRoot, - CurrentTime, - ); - xlib::XDeleteProperty( - state.dpy, - state.root, - state.netatom[Net::ActiveWindow as usize], - ); - - #[cfg(target_os = "linux")] - drop(Box::from_raw(state.xcon)); - - drop(state); - } - - log::trace!("finished cleanup"); -} - -fn unmanage(state: &mut State, c: *mut Client, destroyed: c_int) { - log::trace!("unmanage"); - unsafe { - let m = (*c).mon; - let mut wc = xlib::XWindowChanges { - x: 0, - y: 0, - width: 0, - height: 0, - border_width: 0, - sibling: 0, - stack_mode: 0, - }; - - if !(*c).swallowing.is_null() { - unswallow(state, c); - return; - } - - let s = swallowingclient(state, (*c).win); - if !s.is_null() { - libc::free((*s).swallowing.cast()); - (*s).swallowing = null_mut(); - arrange(state, m); - focus(state, null_mut()); - return; - } - - detach(c); - detachstack(c); - if destroyed == 0 { - wc.border_width = (*c).oldbw; - xlib::XGrabServer(state.dpy); /* avoid race conditions */ - xlib::XSetErrorHandler(Some(xerrordummy)); - xlib::XSelectInput(state.dpy, (*c).win, NoEventMask); - xlib::XConfigureWindow( - state.dpy, - (*c).win, - CWBorderWidth as u32, - &mut wc, - ); /* restore border */ - xlib::XUngrabButton( - state.dpy, - AnyButton as u32, - AnyModifier, - (*c).win, - ); - setclientstate(state, c, WITHDRAWN_STATE); - xlib::XSync(state.dpy, False); - xlib::XSetErrorHandler(Some(xerror)); - xlib::XUngrabServer(state.dpy); - } - libc::free(c.cast()); - - if s.is_null() { - arrange(state, m); - focus(state, null_mut()); - updateclientlist(state); - } - } -} - -/// I'm just using the OpenBSD version of the code in the patch rather than the -/// Linux version that uses XCB -fn winpid(state: &mut State, w: Window) -> pid_t { - #[cfg(target_os = "linux")] - unsafe { - log::trace!("winpid linux"); - - let mut result = 0; - - let spec = xcb::res::ClientIdSpec { - client: w as u32, - mask: xcb::res::ClientIdMask::LOCAL_CLIENT_PID, - }; - assert!(!state.xcon.is_null(), "xcon is null"); - let xcon = &*state.xcon; - let cookie = - xcon.send_request(&xcb::res::QueryClientIds { specs: &[spec] }); - let Ok(r) = xcon.wait_for_reply(cookie) else { - return 0; - }; - - for id in r.ids() { - let spec = id.spec(); - if !(spec.mask & xcb::res::ClientIdMask::LOCAL_CLIENT_PID) - .is_empty() - { - result = id.value()[0] as i32; - } - } - - result - } - - #[cfg(not(target_os = "linux"))] - unsafe { - use x11::xlib::{AnyPropertyType, XGetWindowProperty}; - - let mut type_: Atom = 0; - let mut format: c_int = 0; - let mut len: c_ulong = 0; - let mut bytes: c_ulong = 0; - let mut prop: *mut c_uchar = null_mut(); - if XGetWindowProperty( - state.dpy, - w, - XInternAtom(state.dpy, c"_NET_WM_PID".as_ptr(), 0), - 0, - 1, - False, - AnyPropertyType as u64, - &mut type_, - &mut format, - &mut len, - &mut bytes, - &mut prop, - ) != Success as i32 - || prop.is_null() - { - return 0; - } - let ret = *(prop as *mut pid_t); - XFree(prop.cast()); - - ret - } -} - -/// this looks insane... rust has std::os::unix::process::parent_id, but it -/// doesn't take any arguments. we need to get the parent of a specific process -/// here, so we read from /proc -fn getparentprocess(p: pid_t) -> pid_t { - let filename = format!("/proc/{p}/stat"); - let Ok(mut f) = std::fs::File::open(filename) else { - return 0; - }; - let mut buf = Vec::new(); - let Ok(_) = f.read_to_end(&mut buf) else { - return 0; - }; - let Ok(s) = String::from_utf8(buf) else { - return 0; - }; - // trying to emulate fscanf(f, "%*u %*s %*c %u", &v); which should give the - // 3rd field - match s.split_ascii_whitespace().nth(3).map(str::parse) { - Some(Ok(p)) => p, - _ => 0, - } -} - -fn isdescprocess(p: pid_t, mut c: pid_t) -> pid_t { - while p != c && c != 0 { - c = getparentprocess(c); - } - c -} - -fn termforwin(state: &mut State, w: *const Client) -> *mut Client { - unsafe { - let w = &*w; - - if w.pid == 0 || w.isterminal { - return null_mut(); - } - - let mut c; - let mut m; - - cfor!((m = state.mons; !m.is_null(); m = (*m).next) { - cfor!((c = (*m).clients; !c.is_null(); c = (*c).next) { - if (*c).isterminal && (*c).swallowing.is_null() - && (*c).pid != 0 && isdescprocess((*c).pid, w.pid) != 0 { - return c; - } - }); - }); - } - - null_mut() -} - -fn swallowingclient(state: &State, w: Window) -> *mut Client { - unsafe { - let mut c; - let mut m; - - cfor!((m = state.mons; !m.is_null(); m = (*m).next) { - cfor!((c = (*m).clients; !c.is_null(); c = (*c).next) { - if !(*c).swallowing.is_null() && (*(*c).swallowing).win == w { - return c; - } - }); - }); - - null_mut() - } -} - -fn updateclientlist(state: &mut State) { - unsafe { - xlib::XDeleteProperty( - state.dpy, - state.root, - state.netatom[Net::ClientList as usize], - ); - let mut m = state.mons; - while !m.is_null() { - let mut c = (*m).clients; - while !c.is_null() { - xlib::XChangeProperty( - state.dpy, - state.root, - state.netatom[Net::ClientList as usize], - XA_WINDOW, - 32, - PropModeAppend, - &((*c).win) as *const u64 as *const c_uchar, - 1, - ); - c = (*c).next; - } - m = (*m).next; - } - } -} - -fn setclientstate(s: &mut State, c: *mut Client, state: usize) { - let mut data: [c_long; 2] = [state as c_long, XNONE as c_long]; - let ptr: *mut c_uchar = data.as_mut_ptr().cast(); - unsafe { - xlib::XChangeProperty( - s.dpy, - (*c).win, - s.wmatom[WM::State as usize], - s.wmatom[WM::State as usize], - 32, - PropModeReplace, - ptr, - 2, - ); - } -} - -type HandlerFn = fn(&mut State, *mut xlib::XEvent); - -static HANDLER: LazyLock<[HandlerFn; x11::xlib::LASTEvent as usize]> = - LazyLock::new(|| { - fn dh(_state: &mut State, _ev: *mut xlib::XEvent) {} - let mut ret = [dh as fn(state: &mut State, *mut xlib::XEvent); - x11::xlib::LASTEvent as usize]; - ret[x11::xlib::ButtonPress as usize] = handlers::buttonpress; - ret[x11::xlib::ClientMessage as usize] = handlers::clientmessage; - ret[x11::xlib::ConfigureRequest as usize] = handlers::configurerequest; - ret[x11::xlib::ConfigureNotify as usize] = handlers::configurenotify; - ret[x11::xlib::DestroyNotify as usize] = handlers::destroynotify; - ret[x11::xlib::EnterNotify as usize] = handlers::enternotify; - ret[x11::xlib::Expose as usize] = handlers::expose; - ret[x11::xlib::FocusIn as usize] = handlers::focusin; - ret[x11::xlib::KeyPress as usize] = handlers::keypress; - ret[x11::xlib::MappingNotify as usize] = handlers::mappingnotify; - ret[x11::xlib::MapRequest as usize] = handlers::maprequest; - ret[x11::xlib::MotionNotify as usize] = handlers::motionnotify; - ret[x11::xlib::PropertyNotify as usize] = handlers::propertynotify; - ret[x11::xlib::ResizeRequest as usize] = handlers::resizerequest; - ret[x11::xlib::UnmapNotify as usize] = handlers::unmapnotify; - ret - }); - -/// main event loop -fn run(state: &mut State) { - unsafe { - xlib::XSync(state.dpy, False); - let mut ev: MaybeUninit = MaybeUninit::uninit(); - while state.running && xlib::XNextEvent(state.dpy, ev.as_mut_ptr()) == 0 - { - let mut ev: xlib::XEvent = ev.assume_init(); - if let Some(handler) = HANDLER.get(ev.type_ as usize) { - handler(state, &mut ev); - } - } - } -} - -fn scan(state: &mut State) { - let mut num = 0; - let mut d1 = 0; - let mut d2 = 0; - let mut wins: *mut Window = std::ptr::null_mut(); - let mut wa: MaybeUninit = MaybeUninit::uninit(); - unsafe { - if xlib::XQueryTree( - state.dpy, - state.root, - &mut d1, - &mut d2, - &mut wins as *mut _, - &mut num, - ) != 0 - { - for i in 0..num { - if xlib::XGetWindowAttributes( - state.dpy, - *wins.offset(i as isize), - wa.as_mut_ptr(), - ) == 0 - || (*wa.as_mut_ptr()).override_redirect != 0 - || xlib::XGetTransientForHint( - state.dpy, - *wins.offset(i as isize), - &mut d1, - ) != 0 - { - continue; - } - if (*wa.as_mut_ptr()).map_state == IsViewable - || getstate(state, *wins.offset(i as isize)) - == ICONIC_STATE as i64 - { - manage(state, *wins.offset(i as isize), wa.as_mut_ptr()); - } - } - for i in 0..num { - // now the transients - if xlib::XGetWindowAttributes( - state.dpy, - *wins.offset(i as isize), - wa.as_mut_ptr(), - ) == 0 - { - continue; - } - if xlib::XGetTransientForHint( - state.dpy, - *wins.offset(i as isize), - &mut d1, - ) != 0 - && ((*wa.as_mut_ptr()).map_state == IsViewable - || getstate(state, *wins.offset(i as isize)) - == ICONIC_STATE as i64) - { - manage(state, *wins.offset(i as isize), wa.as_mut_ptr()); - } - } - if !wins.is_null() { - XFree(wins.cast()); - } - } - } -} - -fn manage(state: &mut State, w: Window, wa: *mut xlib::XWindowAttributes) { - log::trace!("manage"); - let mut trans = 0; - unsafe { - let wa = *wa; - let c: *mut Client = util::ecalloc(1, size_of::()) as *mut _; - (*c).win = w; - (*c).pid = winpid(state, w); - (*c).x = wa.x; - (*c).oldx = wa.x; - (*c).y = wa.y; - (*c).oldy = wa.y; - (*c).w = wa.width; - (*c).oldw = wa.width; - (*c).h = wa.height; - (*c).oldh = wa.height; - (*c).oldbw = wa.border_width; - (*c).name = String::new(); - - let mut term: *mut Client = null_mut(); - - updatetitle(state, c); - log::trace!("manage: XGetTransientForHint"); - if xlib::XGetTransientForHint(state.dpy, w, &mut trans) != 0 { - let t = wintoclient(state, trans); - if !t.is_null() { - (*c).mon = (*t).mon; - (*c).tags = (*t).tags; - } else { - // NOTE must keep in sync with else below - (*c).mon = state.selmon; - applyrules(state, c); - term = termforwin(state, c); - } - } else { - // copied else case from above because the condition is supposed - // to be xgettransientforhint && (t = wintoclient) - (*c).mon = state.selmon; - applyrules(state, c); - term = termforwin(state, c); - } - if (*c).x + width(c) > ((*(*c).mon).wx + (*(*c).mon).ww) as i32 { - (*c).x = ((*(*c).mon).wx + (*(*c).mon).ww) as i32 - width(c); - } - if (*c).y + height(c) > ((*(*c).mon).wy + (*(*c).mon).wh) as i32 { - (*c).y = ((*(*c).mon).wy + (*(*c).mon).wh) as i32 - height(c); - } - (*c).x = max((*c).x, (*(*c).mon).wx as i32); - (*c).y = max((*c).y, (*(*c).mon).wy as i32); - (*c).bw = CONFIG.borderpx as i32; - - // TODO pretty sure this doesn't work with pertags, which explains some - // behavior I saw before in dwm. probably need to operate on - // selmon.pertag.tags[selmon.pertag.curtag]. - // - // TODO I'm also pretty sure this is _not_ the right way to be handling - // this. checking the name of the window and applying these rules seems - // like something meant to be handled by RULES - (*state.selmon).tagset[(*state.selmon).seltags as usize] &= - !*SCRATCHTAG; - if (*c).name == CONFIG.scratchpadname { - (*c).tags = *SCRATCHTAG; - (*(*c).mon).tagset[(*(*c).mon).seltags as usize] |= (*c).tags; - (*c).isfloating = true; - (*c).x = (*(*c).mon).wx + (*(*c).mon).ww / 2 - width(c) / 2; - (*c).y = (*(*c).mon).wy + (*(*c).mon).wh / 2 - height(c) / 2; - } - - log::trace!("manage: XWindowChanges"); - let mut wc = xlib::XWindowChanges { - x: 0, - y: 0, - width: 0, - height: 0, - border_width: (*c).bw, - sibling: 0, - stack_mode: 0, - }; - log::trace!("manage: XConfigureWindow"); - xlib::XConfigureWindow(state.dpy, w, CWBorderWidth as u32, &mut wc); - log::trace!( - "manage: XSetWindowBorder with state.dpy = {:?} and w = {w:?}", - &raw const state.dpy - ); - log::trace!("scheme: {:?}", &raw const state.scheme); - let scheme_norm = &state.scheme[Scheme::Norm]; - log::trace!("scheme[SchemeNorm]: {scheme_norm:?}"); - let border = scheme_norm[Col::Border as usize]; - log::trace!("scheme[SchemeNorm][ColBorder]: {border:?}"); - let pixel = border.pixel; - log::trace!("pixel = {pixel:?}"); - xlib::XSetWindowBorder(state.dpy, w, pixel); - configure(state, c); // propagates border width, if size doesn't change - updatewindowtype(state, c); - updatesizehints(state, c); - updatewmhints(state, c); - xlib::XSelectInput( - state.dpy, - w, - EnterWindowMask - | FocusChangeMask - | PropertyChangeMask - | StructureNotifyMask, - ); - grabbuttons(state, c, false); - if !(*c).isfloating { - (*c).oldstate = trans != 0 || (*c).isfixed != 0; - (*c).isfloating = (*c).oldstate; - } - if (*c).isfloating { - xlib::XRaiseWindow(state.dpy, (*c).win); - } - attach(c); - attachstack(c); - xlib::XChangeProperty( - state.dpy, - state.root, - state.netatom[Net::ClientList as usize], - XA_WINDOW, - 32, - PropModeAppend, - &((*c).win as c_uchar), - 1, - ); - // some windows require this - xlib::XMoveResizeWindow( - state.dpy, - (*c).win, - (*c).x + 2 * state.sw, - (*c).y, - (*c).w as u32, - (*c).h as u32, - ); - setclientstate(state, c, NORMAL_STATE); - if (*c).mon == state.selmon { - unfocus(state, (*state.selmon).sel, false); - } - (*(*c).mon).sel = c; - arrange(state, (*c).mon); - xlib::XMapWindow(state.dpy, (*c).win); - if !term.is_null() { - swallow(state, term, c); - } - focus(state, std::ptr::null_mut()); - } -} - -fn updatewmhints(state: &mut State, c: *mut Client) { - log::trace!("updatewmhints"); - const URGENT: i64 = xlib::XUrgencyHint; - unsafe { - let wmh = xlib::XGetWMHints(state.dpy, (*c).win); - if !wmh.is_null() { - if c == (*state.selmon).sel && (*wmh).flags & URGENT != 0 { - (*wmh).flags &= !URGENT; - xlib::XSetWMHints(state.dpy, (*c).win, wmh); - } else { - (*c).isurgent = ((*wmh).flags & URGENT != 0) as bool as c_int; - } - if (*wmh).flags & InputHint != 0 { - (*c).neverfocus = ((*wmh).input == 0) as c_int; - } else { - (*c).neverfocus = 0; - } - xlib::XFree(wmh.cast()); - } - } -} - -fn updatewindowtype(state: &mut State, c: *mut Client) { - log::trace!("updatewindowtype"); - unsafe { - let s = getatomprop(state, c, state.netatom[Net::WMState as usize]); - let wtype = - getatomprop(state, c, state.netatom[Net::WMWindowType as usize]); - if s == state.netatom[Net::WMFullscreen as usize] { - setfullscreen(state, c, true); - } - if wtype == state.netatom[Net::WMWindowTypeDialog as usize] { - (*c).isfloating = true; - } - } -} - -fn setfullscreen(state: &mut State, c: *mut Client, fullscreen: bool) { - unsafe { - if fullscreen && !(*c).isfullscreen { - xlib::XChangeProperty( - state.dpy, - (*c).win, - state.netatom[Net::WMState as usize], - XA_ATOM, - 32, - PropModeReplace, - // trying to emulate (unsigned char*)&netatom[NetWMFullscreen], - // so take a reference and then cast - &state.netatom[Net::WMFullscreen as usize] as *const u64 - as *const c_uchar, - 1, - ); - (*c).isfullscreen = true; - (*c).oldstate = (*c).isfloating; - (*c).oldbw = (*c).bw; - (*c).bw = 0; - (*c).isfloating = true; - resizeclient( - state, - c, - (*(*c).mon).mx, - (*(*c).mon).my, - (*(*c).mon).mw, - (*(*c).mon).mh, - ); - xlib::XRaiseWindow(state.dpy, (*c).win); - } else if !fullscreen && (*c).isfullscreen { - xlib::XChangeProperty( - state.dpy, - (*c).win, - state.netatom[Net::WMState as usize], - XA_ATOM, - 32, - PropModeReplace, - std::ptr::null_mut::(), - 0, - ); - (*c).isfullscreen = false; - (*c).isfloating = (*c).oldstate; - (*c).bw = (*c).oldbw; - (*c).x = (*c).oldx; - (*c).y = (*c).oldy; - (*c).w = (*c).oldw; - (*c).h = (*c).oldh; - resizeclient(state, c, (*c).x, (*c).y, (*c).w, (*c).h); - arrange(state, (*c).mon); - } - } -} - -fn getatomprop(state: &mut State, c: *mut Client, prop: Atom) -> Atom { - let mut di = 0; - let mut dl = 0; - let mut p = std::ptr::null_mut(); - let mut da = 0; - let mut atom: Atom = 0; - unsafe { - // FIXME (systray author) getatomprop should return the number of items - // and a pointer to the stored data instead of this workaround - let mut req = XA_ATOM; - if prop == state.xatom[XEmbed::XEmbedInfo as usize] { - req = state.xatom[XEmbed::XEmbedInfo as usize]; - } - if xlib::XGetWindowProperty( - state.dpy, - (*c).win, - prop, - 0, - std::mem::size_of::() as i64, - False, - req, - &mut da, - &mut di, - &mut dl, - &mut dl, - &mut p, - ) == Success as i32 - && !p.is_null() - { - // the C code is *(Atom *)p. is that different from (Atom) *p? - // that's closer to what I had before - atom = *(p as *mut Atom); - if da == state.xatom[XEmbed::XEmbedInfo as usize] && dl == 2 { - atom = *(p as *mut Atom).add(1); - } - XFree(p.cast()); - } - } - atom -} - -// TODO this should really just be a method on Systray and called like -// state.systray.width() -fn getsystraywidth(state: &State) -> c_uint { - unsafe { - let mut w = 0; - let mut i; - if CONFIG.showsystray { - cfor!(( - i = state.systray().icons; - !i.is_null(); - (w, i) = (w + (*i).w + config::CONFIG.systrayspacing as i32, (*i).next)) - {}); - } - if w != 0 { - w as c_uint + CONFIG.systrayspacing - } else { - 1 - } - } -} - -fn applyrules(state: &mut State, c: *mut Client) { - log::trace!("applyrules"); - unsafe { - let mut ch = xlib::XClassHint { - res_name: std::ptr::null_mut(), - res_class: std::ptr::null_mut(), - }; - // rule matching - (*c).isfloating = false; - (*c).tags = 0; - xlib::XGetClassHint(state.dpy, (*c).win, &mut ch); - let class = if !ch.res_class.is_null() { - CStr::from_ptr(ch.res_class) - } else { - BROKEN - }; - let instance = if !ch.res_name.is_null() { - CStr::from_ptr(ch.res_name) - } else { - BROKEN - }; - - for r in &CONFIG.rules { - if (r.title.is_empty() || (*c).name.contains(&r.title)) - && (r.class.is_empty() - || class.to_string_lossy().contains(&r.class)) - && (r.instance.is_empty() - || instance.to_string_lossy().contains(&r.instance)) - { - (*c).isterminal = r.isterminal; - (*c).noswallow = r.noswallow; - (*c).isfloating = r.isfloating; - (*c).tags |= r.tags; - let mut m = state.mons; - while !m.is_null() && (*m).num != r.monitor { - m = (*m).next; - } - if !m.is_null() { - (*c).mon = m; - } - } - } - if !ch.res_class.is_null() { - xlib::XFree(ch.res_class.cast()); - } - if !ch.res_name.is_null() { - xlib::XFree(ch.res_name.cast()); - } - (*c).tags = if (*c).tags & *TAGMASK != 0 { - (*c).tags & *TAGMASK - } else { - (*(*c).mon).tagset[(*(*c).mon).seltags as usize] - }; - } -} - -fn swallow(state: &mut State, p: *mut Client, c: *mut Client) { - unsafe { - let c = &mut *c; - if c.noswallow || c.isterminal { - return; - } - if c.noswallow && !CONFIG.swallowfloating && c.isfloating { - return; - } - detach(c); - detachstack(c); - - setclientstate(state, c, WITHDRAWN_STATE); - let p = &mut *p; - XUnmapWindow(state.dpy, p.win); - p.swallowing = c; - c.mon = p.mon; - - std::mem::swap(&mut p.win, &mut c.win); - updatetitle(state, p); - XMoveResizeWindow(state.dpy, p.win, p.x, p.y, p.w as u32, p.h as u32); - arrange(state, p.mon); - configure(state, p); - updateclientlist(state); - } -} - -fn unswallow(state: &mut State, c: *mut Client) { - unsafe { - let c = &mut *c; - - c.win = (*c.swallowing).win; - - libc::free(c.swallowing.cast()); - c.swallowing = null_mut(); - - // unfullscreen the client - setfullscreen(state, c, false); - updatetitle(state, c); - arrange(state, c.mon); - XMapWindow(state.dpy, c.win); - XMoveResizeWindow(state.dpy, c.win, c.x, c.y, c.w as u32, c.h as u32); - setclientstate(state, c, NORMAL_STATE); - focus(state, null_mut()); - arrange(state, c.mon); - } -} - -// #define TAGMASK ((1 << LENGTH(tags)) - 1) -static TAGMASK: LazyLock = LazyLock::new(|| (1 << CONFIG.tags.len()) - 1); -const BUTTONMASK: i64 = ButtonPressMask | ButtonReleaseMask; -const MOUSEMASK: i64 = BUTTONMASK | PointerMotionMask; - -static SCRATCHTAG: LazyLock = LazyLock::new(|| 1 << CONFIG.tags.len()); - -fn updatetitle(state: &mut State, c: *mut Client) { - log::trace!("updatetitle"); - unsafe { - if gettextprop( - state.dpy, - (*c).win, - state.netatom[Net::WMName as usize], - &mut (*c).name, - ) == 0 - { - gettextprop(state.dpy, (*c).win, XA_WM_NAME, &mut (*c).name); - } - if (*c).name.is_empty() { - /* hack to mark broken clients */ - (*c).name = BROKEN.to_string_lossy().to_string(); - } - } -} - -fn getstate(state: &mut State, w: Window) -> c_long { - let mut format = 0; - let mut result: c_long = -1; - let mut p: *mut c_uchar = std::ptr::null_mut(); - let mut n = 0; - let mut extra = 0; - let mut real = 0; - unsafe { - let cond = xlib::XGetWindowProperty( - state.dpy, - w, - state.wmatom[WM::State as usize], - 0, - 2, - False, - state.wmatom[WM::State as usize], - &mut real, - &mut format, - &mut n, - &mut extra, - (&mut p) as *mut *mut c_uchar, - ); - if cond != Success as i32 { - return -1; - } - if n != 0 { - result = *p as c_long; - } - XFree(p.cast()); - result - } -} +use rwm::checkotherwm; +use rwm::util::die; -mod config; pub use rwm::enums; -use xembed::{ - XEMBED_EMBEDDED_VERSION, XEMBED_MAPPED, XEMBED_WINDOW_ACTIVATE, - XEMBED_WINDOW_DEACTIVATE, -}; -mod handlers; -mod key_handlers; -mod layouts; -mod xembed; #[cfg(test)] mod tests; fn main() { env_logger::init(); - // a bit weird but demand config load at startup - let _ = CONFIG; - let dpy = unsafe { xlib::XOpenDisplay(std::ptr::null_mut()) }; + let dpy = unsafe { x11::xlib::XOpenDisplay(std::ptr::null_mut()) }; if dpy.is_null() { die("rwm: cannot open display"); } diff --git a/src/main_.rs b/src/main_.rs new file mode 100644 index 0000000..d34e64b --- /dev/null +++ b/src/main_.rs @@ -0,0 +1,2947 @@ +use std::cmp::max; +use std::ffi::{c_int, c_uint, c_ulong, CStr}; +use std::io::Read; +use std::mem::{size_of, MaybeUninit}; +use std::ptr::null_mut; +use std::sync::LazyLock; + +use crate::config::Config; +use crate::drw::{fontset_create, Drw}; +use crate::enums::{Clk, Col, Net, Scheme, XEmbed, WM}; +use crate::key_handlers::view; +use crate::util::{self, ecalloc}; +use crate::xembed::{ + XEMBED_EMBEDDED_VERSION, XEMBED_MAPPED, XEMBED_WINDOW_ACTIVATE, + XEMBED_WINDOW_DEACTIVATE, +}; +use crate::{ + drw, handlers, Arg, Client, Layout, LayoutFn, Monitor, Pertag, State, + Systray, Window, ICONIC_STATE, NORMAL_STATE, WITHDRAWN_STATE, +}; +use libc::{c_long, c_uchar, pid_t, sigaction}; +use x11::keysym::XK_Num_Lock; +use x11::xlib::{ + self, Above, AnyButton, AnyKey, AnyModifier, BadAccess, BadDrawable, + BadMatch, BadWindow, Below, ButtonPressMask, ButtonReleaseMask, + CWBackPixel, CWBackPixmap, CWBorderWidth, CWCursor, CWEventMask, CWHeight, + CWOverrideRedirect, CWSibling, CWStackMode, CWWidth, ClientMessage, + ControlMask, CopyFromParent, CurrentTime, Display, EnterWindowMask, + ExposureMask, False, FocusChangeMask, GrabModeAsync, GrabModeSync, + InputHint, IsViewable, LeaveWindowMask, LockMask, Mod1Mask, Mod2Mask, + Mod3Mask, Mod4Mask, Mod5Mask, NoEventMask, PAspect, PBaseSize, PMaxSize, + PMinSize, PResizeInc, PSize, ParentRelative, PointerMotionMask, + PointerRoot, PropModeAppend, PropModeReplace, PropertyChangeMask, + RevertToPointerRoot, ShiftMask, StructureNotifyMask, + SubstructureNotifyMask, SubstructureRedirectMask, Success, True, + XChangeProperty, XChangeWindowAttributes, XConfigureWindow, + XCreateSimpleWindow, XDestroyWindow, XErrorEvent, XFillRectangle, XFree, + XGetSelectionOwner, XInternAtom, XMapRaised, XMapSubwindows, XMapWindow, + XMoveResizeWindow, XPropertyEvent, XSelectInput, XSetErrorHandler, + XSetForeground, XSetSelectionOwner, XSetWindowAttributes, XSync, + XUnmapWindow, XWindowChanges, CWX, CWY, XA_ATOM, XA_CARDINAL, XA_STRING, + XA_WINDOW, XA_WM_NAME, +}; + +#[macro_export] +macro_rules! cfor { + ((; $cond:expr; $step:expr) $body:block ) => { + cfor!(({}; $cond; $step) $body) + }; + (($init:expr; $cond:expr; $step:expr) $body:block ) => { + $init; + while $cond { + $body; + $step; + } + }; +} + +/// function to be called on a startup error +pub extern "C" fn xerrorstart(_: *mut Display, _: *mut XErrorEvent) -> c_int { + panic!("another window manager is already running") +} + +// from Xproto.h +pub const X_SET_INPUT_FOCUS: u8 = 42; +pub const X_POLY_TEXT_8: u8 = 74; +pub const X_POLY_FILL_RECTANGLE: u8 = 70; +pub const X_POLY_SEGMENT: u8 = 66; +pub const X_CONFIGURE_WINDOW: u8 = 12; +pub const X_GRAB_BUTTON: u8 = 28; +pub const X_GRAB_KEY: u8 = 33; +pub const X_COPY_AREA: u8 = 62; + +// from cursorfont.h +pub const XC_LEFT_PTR: u8 = 68; +pub const XC_SIZING: u8 = 120; +pub const XC_FLEUR: u8 = 52; + +// from X.h +// const BUTTON_RELEASE: i32 = 5; +pub const XNONE: c_long = 0; + +/// Unfortunately this can't be packed into the State struct since it needs to +/// be accessed here in `xerror`, where I don't get a chance to pass State. +/// +/// What's going on here is that `checkotherwm` calls `XSetErrorHandler` to set +/// the handler temporarily to `xerrorstart`. `XSetErrorHandler` returns the +/// previous handler fn, which we store here. At the end of `checkotherwm`, we +/// then set the error handler to `xerror`, which, via `XERRORLIB`, is just a +/// wrapper around the original X error handler with a little logging and an +/// early return to allow certain kinds of errors. +/// +/// Obviously it would be nice to handle this with a closure in `checkotherwm`, +/// but `XSetErrorHandler` requires an `unsafe extern "C" fn`, not any old Fn. +pub static mut XERRORXLIB: Option< + unsafe extern "C" fn(*mut Display, *mut XErrorEvent) -> i32, +> = None; + +/// # Safety +pub unsafe extern "C" fn xerror( + mdpy: *mut Display, + ee: *mut XErrorEvent, +) -> c_int { + unsafe { + let e = *ee; + if e.error_code == BadWindow + || (e.request_code == X_SET_INPUT_FOCUS && e.error_code == BadMatch) + || (e.request_code == X_POLY_TEXT_8 && e.error_code == BadDrawable) + || (e.request_code == X_POLY_FILL_RECTANGLE + && e.error_code == BadDrawable) + || (e.request_code == X_POLY_SEGMENT && e.error_code == BadDrawable) + || (e.request_code == X_CONFIGURE_WINDOW + && e.error_code == BadMatch) + || (e.request_code == X_GRAB_BUTTON && e.error_code == BadAccess) + || (e.request_code == X_GRAB_KEY && e.error_code == BadAccess) + || (e.request_code == X_COPY_AREA && e.error_code == BadDrawable) + { + return 0; + } + eprintln!( + "rwm: fatal error: request code={}, error code={}", + e.request_code, e.error_code + ); + (XERRORXLIB.unwrap())(mdpy, ee) + } +} + +pub extern "C" fn xerrordummy( + _dpy: *mut Display, + _ee: *mut xlib::XErrorEvent, +) -> c_int { + 0 +} + +const BROKEN: &CStr = c"broken"; + +type Atom = c_ulong; + +pub fn createmon(state: &State) -> *mut Monitor { + log::trace!("createmon"); + + // I thought about trying to create a Monitor directly, followed by + // Box::into_raw(Box::new(m)), but we use libc::free to free the Monitors + // later. I'd have to replace that with Box::from_raw and allow it to drop + // for that to work I think. + let m: *mut Monitor = ecalloc(1, size_of::()).cast(); + + unsafe { + (*m).tagset[0] = 1; + (*m).tagset[1] = 1; + (*m).mfact = state.CONFIG.mfact; + (*m).nmaster = state.CONFIG.nmaster; + (*m).showbar = state.CONFIG.showbar; + (*m).topbar = state.CONFIG.topbar; + (*m).lt[0] = &state.CONFIG.layouts[0]; + (*m).lt[1] = &state.CONFIG.layouts[1 % state.CONFIG.layouts.len()]; + (*m).ltsymbol = state.CONFIG.layouts[0].symbol.clone(); + + (*m).pertag = Pertag { + curtag: 1, + prevtag: 1, + nmasters: vec![(*m).nmaster; state.CONFIG.tags.len() + 1], + mfacts: vec![(*m).mfact; state.CONFIG.tags.len() + 1], + sellts: vec![(*m).sellt; state.CONFIG.tags.len() + 1], + ltidxs: vec![(*m).lt; state.CONFIG.tags.len() + 1], + showbars: vec![(*m).showbar; state.CONFIG.tags.len() + 1], + }; + } + + m +} + +pub fn checkotherwm(dpy: *mut Display) { + log::trace!("checkotherwm"); + unsafe { + XERRORXLIB = XSetErrorHandler(Some(xerrorstart)); + xlib::XSelectInput( + dpy, + xlib::XDefaultRootWindow(dpy), + SubstructureRedirectMask, + ); + XSetErrorHandler(Some(xerror)); + xlib::XSync(dpy, False); + } +} + +pub fn setup(dpy: *mut Display) -> State { + log::trace!("setup"); + unsafe { + let mut wa = xlib::XSetWindowAttributes { + background_pixmap: 0, + background_pixel: 0, + border_pixmap: 0, + border_pixel: 0, + bit_gravity: 0, + win_gravity: 0, + backing_store: 0, + backing_planes: 0, + backing_pixel: 0, + save_under: 0, + event_mask: 0, + do_not_propagate_mask: 0, + override_redirect: 0, + colormap: 0, + cursor: 0, + }; + let mut sa = sigaction { + sa_sigaction: libc::SIG_IGN, + sa_mask: std::mem::zeroed(), + sa_flags: libc::SA_NOCLDSTOP + | libc::SA_NOCLDWAIT + | libc::SA_RESTART, + #[cfg(not(target_os = "macos"))] + sa_restorer: None, + }; + libc::sigemptyset(&mut sa.sa_mask); + libc::sigaction(libc::SIGCHLD, &sa, null_mut()); + + while libc::waitpid(-1, null_mut(), libc::WNOHANG) > 0 {} + + let screen = xlib::XDefaultScreen(dpy); + let sh = xlib::XDisplayHeight(dpy, screen); + let root = xlib::XRootWindow(dpy, screen); + let sw = xlib::XDisplayWidth(dpy, screen); + let mut drw = drw::create(dpy, screen, root, sw as u32, sh as u32); + let CONFIG = Config::load_home(); + if fontset_create(&mut drw, &CONFIG.fonts).is_err() + || drw.fonts.is_empty() + { + panic!("no fonts could be loaded"); + } + + /* init cursors */ + let cursors = crate::Cursors { + normal: drw::cur_create(&drw, XC_LEFT_PTR as i32), + resize: drw::cur_create(&drw, XC_SIZING as i32), + move_: drw::cur_create(&drw, XC_FLEUR as i32), + }; + let mut state = State { + bh: drw.fonts[0].h as i32 + 2, + sw, + sh, + cursors, + wmatom: Default::default(), + netatom: Default::default(), + xatom: Default::default(), + dpy, + lrpad: drw.fonts[0].h as i32, + drw, + selmon: null_mut(), + mons: null_mut(), + stext: String::new(), + scheme: Default::default(), + screen, + root, + wmcheckwin: xlib::XCreateSimpleWindow( + dpy, root, 0, 0, 1, 1, 0, 0, 0, + ), + numlockmask: 0, + running: true, + systray: None, + CONFIG: Config::load_home(), + + #[cfg(target_os = "linux")] + xcon: null_mut(), + }; + + updategeom(&mut state); + + /* init atoms */ + let utf8string = XInternAtom(state.dpy, c"UTF8_STRING".as_ptr(), False); + state.wmatom[WM::Protocols as usize] = + XInternAtom(state.dpy, c"WM_PROTOCOLS".as_ptr(), False); + state.wmatom[WM::Delete as usize] = + XInternAtom(state.dpy, c"WM_DELETE_WINDOW".as_ptr(), False); + state.wmatom[WM::State as usize] = + XInternAtom(state.dpy, c"WM_STATE".as_ptr(), False); + state.wmatom[WM::TakeFocus as usize] = + XInternAtom(state.dpy, c"WM_TAKE_FOCUS".as_ptr(), False); + + state.netatom[Net::ActiveWindow as usize] = + XInternAtom(state.dpy, c"_NET_ACTIVE_WINDOW".as_ptr(), False); + state.netatom[Net::Supported as usize] = + XInternAtom(state.dpy, c"_NET_SUPPORTED".as_ptr(), False); + + state.netatom[Net::SystemTray as usize] = + XInternAtom(state.dpy, c"_NET_SYSTEM_TRAY_S0".as_ptr(), False); + state.netatom[Net::SystemTrayOP as usize] = + XInternAtom(state.dpy, c"_NET_SYSTEM_TRAY_OPCODE".as_ptr(), False); + state.netatom[Net::SystemTrayOrientation as usize] = XInternAtom( + state.dpy, + c"_NET_SYSTEM_TRAY_ORIENTATION".as_ptr(), + False, + ); + state.netatom[Net::SystemTrayOrientationHorz as usize] = XInternAtom( + state.dpy, + c"_NET_SYSTEM_TRAY_ORIENTATION_HORZ".as_ptr(), + False, + ); + + state.netatom[Net::WMName as usize] = + XInternAtom(state.dpy, c"_NET_WM_NAME".as_ptr(), False); + state.netatom[Net::WMState as usize] = + XInternAtom(state.dpy, c"_NET_WM_STATE".as_ptr(), False); + state.netatom[Net::WMCheck as usize] = + XInternAtom(state.dpy, c"_NET_SUPPORTING_WM_CHECK".as_ptr(), False); + state.netatom[Net::WMFullscreen as usize] = + XInternAtom(state.dpy, c"_NET_WM_STATE_FULLSCREEN".as_ptr(), False); + state.netatom[Net::WMWindowType as usize] = + XInternAtom(state.dpy, c"_NET_WM_WINDOW_TYPE".as_ptr(), False); + state.netatom[Net::WMWindowTypeDialog as usize] = XInternAtom( + state.dpy, + c"_NET_WM_WINDOW_TYPE_DIALOG".as_ptr(), + False, + ); + state.netatom[Net::ClientList as usize] = + XInternAtom(state.dpy, c"_NET_CLIENT_LIST".as_ptr(), False); + + state.xatom[XEmbed::Manager as usize] = + XInternAtom(state.dpy, c"MANAGER".as_ptr(), False); + state.xatom[XEmbed::XEmbed as usize] = + XInternAtom(state.dpy, c"_XEMBED".as_ptr(), False); + state.xatom[XEmbed::XEmbedInfo as usize] = + XInternAtom(state.dpy, c"_XEMBED_INFO".as_ptr(), False); + + /* init appearance */ + for i in 0..state.CONFIG.colors.0.len() { + state.scheme.push(drw::scm_create( + &state.drw, + &state.CONFIG.colors.0[i], + 3, + )); + } + + // init system tray + updatesystray(&mut state); + + /* init bars */ + updatebars(&mut state); + updatestatus(&mut state); + + xlib::XChangeProperty( + state.dpy, + state.wmcheckwin, + state.netatom[Net::WMCheck as usize], + XA_WINDOW, + 32, + PropModeReplace, + &raw mut state.wmcheckwin as *mut c_uchar, + 1, + ); + xlib::XChangeProperty( + state.dpy, + state.wmcheckwin, + state.netatom[Net::WMName as usize], + utf8string, + 8, + PropModeReplace, + c"rwm".as_ptr() as *mut c_uchar, + 3, + ); + xlib::XChangeProperty( + state.dpy, + root, + state.netatom[Net::WMCheck as usize], + XA_WINDOW, + 32, + PropModeReplace, + &raw mut state.wmcheckwin as *mut c_uchar, + 1, + ); + /* EWMH support per view */ + xlib::XChangeProperty( + state.dpy, + root, + state.netatom[Net::Supported as usize], + XA_ATOM, + 32, + PropModeReplace, + &raw mut state.netatom as *mut c_uchar, + Net::Last as i32, + ); + xlib::XDeleteProperty( + state.dpy, + root, + state.netatom[Net::ClientList as usize], + ); + + // /* select events */ + wa.cursor = state.cursors.normal.cursor; + wa.event_mask = SubstructureRedirectMask + | SubstructureNotifyMask + | ButtonPressMask + | PointerMotionMask + | EnterWindowMask + | LeaveWindowMask + | StructureNotifyMask + | PropertyChangeMask; + xlib::XChangeWindowAttributes( + state.dpy, + root, + CWEventMask | CWCursor, + &mut wa, + ); + xlib::XSelectInput(state.dpy, root, wa.event_mask); + grabkeys(&mut state); + focus(&mut state, null_mut()); + + state + } +} + +pub unsafe fn focus(state: &mut State, mut c: *mut Client) { + log::trace!("focus: c = {c:?}"); + unsafe { + if c.is_null() || !is_visible(c) { + c = (*state.selmon).stack; + while !c.is_null() && !is_visible(c) { + c = (*c).snext; + } + } + if !(*state.selmon).sel.is_null() && (*state.selmon).sel != c { + unfocus(state, (*state.selmon).sel, false); + } + if !c.is_null() { + if (*c).mon != state.selmon { + state.selmon = (*c).mon; + } + if (*c).isurgent != 0 { + seturgent(state, c, false); + } + detachstack(c); + attachstack(c); + grabbuttons(state, c, true); + let color = state.scheme[(Scheme::Sel, Col::Border)].pixel; + xlib::XSetWindowBorder(state.dpy, (*c).win, color); + setfocus(state, c); + } else { + xlib::XSetInputFocus( + state.dpy, + state.root, + RevertToPointerRoot, + CurrentTime, + ); + xlib::XDeleteProperty( + state.dpy, + state.root, + state.netatom[Net::ActiveWindow as usize], + ); + } + (*state.selmon).sel = c; + drawbars(state); + } +} + +pub fn drawbars(state: &mut State) { + log::trace!("drawbars"); + unsafe { + let mut m = state.mons; + while !m.is_null() { + drawbar(state, m); + m = (*m).next; + } + } +} + +pub fn setfocus(state: &mut State, c: *mut Client) { + log::trace!("setfocus"); + unsafe { + if (*c).neverfocus == 0 { + xlib::XSetInputFocus( + state.dpy, + (*c).win, + RevertToPointerRoot, + CurrentTime, + ); + xlib::XChangeProperty( + state.dpy, + state.root, + state.netatom[Net::ActiveWindow as usize], + XA_WINDOW, + 32, + PropModeReplace, + (&mut (*c).win) as *mut u64 as *mut c_uchar, + 1, + ); + } + sendevent( + state, + (*c).win, + state.wmatom[WM::TakeFocus as usize], + NoEventMask as i32, + state.wmatom[WM::TakeFocus as usize] as i64, + CurrentTime as i64, + 0, + 0, + 0, + ); + } +} + +#[allow(clippy::too_many_arguments)] +pub fn sendevent( + state: &mut State, + w: Window, + proto: Atom, + mask: c_int, + d0: c_long, + d1: c_long, + d2: c_long, + d3: c_long, + d4: c_long, +) -> c_int { + log::trace!("sendevent"); + let mut n = 0; + let mut protocols = std::ptr::null_mut(); + let mt; + let mut exists = 0; + unsafe { + if proto == state.wmatom[WM::TakeFocus as usize] + || proto == state.wmatom[WM::Delete as usize] + { + mt = state.wmatom[WM::Protocols as usize]; + if xlib::XGetWMProtocols(state.dpy, w, &mut protocols, &mut n) != 0 + { + while exists == 0 && n > 0 { + exists = (*protocols.offset(n as isize) == proto) as c_int; + n -= 1; + } + XFree(protocols.cast()); + } + } else { + exists = 1; + mt = proto; + } + if exists != 0 { + let mut ev = xlib::XEvent { type_: ClientMessage }; + ev.client_message.window = w; + ev.client_message.message_type = mt; + ev.client_message.format = 32; + ev.client_message.data.set_long(0, d0); + ev.client_message.data.set_long(1, d1); + ev.client_message.data.set_long(2, d2); + ev.client_message.data.set_long(3, d3); + ev.client_message.data.set_long(4, d4); + xlib::XSendEvent(state.dpy, w, False, mask as i64, &mut ev); + } + exists + } +} + +pub fn grabbuttons(state: &mut State, c: *mut Client, focused: bool) { + log::trace!("grabbuttons"); + unsafe { + updatenumlockmask(state); + let modifiers = + [0, LockMask, state.numlockmask, state.numlockmask | LockMask]; + xlib::XUngrabButton(state.dpy, AnyButton as u32, AnyModifier, (*c).win); + if !focused { + xlib::XGrabButton( + state.dpy, + AnyButton as u32, + AnyModifier, + (*c).win, + False, + BUTTONMASK as u32, + GrabModeSync, + GrabModeSync, + XNONE as u64, + XNONE as u64, + ); + } + for button in &state.CONFIG.buttons { + if button.click == Clk::ClientWin as u32 { + for mod_ in modifiers { + xlib::XGrabButton( + state.dpy, + button.button, + button.mask | mod_, + (*c).win, + False, + BUTTONMASK as u32, + GrabModeAsync, + GrabModeSync, + XNONE as u64, + XNONE as u64, + ); + } + } + } + } +} + +pub fn arrange(state: &mut State, mut m: *mut Monitor) { + log::trace!("arrange"); + unsafe { + if !m.is_null() { + showhide(state, (*m).stack); + } else { + m = state.mons; + while !m.is_null() { + showhide(state, (*m).stack); + m = (*m).next; + } + } + + if !m.is_null() { + arrangemon(state, m); + restack(state, m); + } else { + m = state.mons; + while !m.is_null() { + arrangemon(state, m); + m = (*m).next; + } + } + } +} + +pub fn arrangemon(state: &mut State, m: *mut Monitor) { + log::trace!("arrangemon"); + unsafe { + (*m).ltsymbol = (*(*m).lt[(*m).sellt as usize]).symbol.clone(); + let arrange = (*(*m).lt[(*m).sellt as usize]).arrange.0; + if let Some(arrange) = arrange { + (arrange)(state, m); + } + } +} + +pub fn restack(state: &mut State, m: *mut Monitor) { + log::trace!("restack"); + drawbar(state, m); + unsafe { + if (*m).sel.is_null() { + return; + } + if (*(*m).sel).isfloating + || (*(*m).lt[(*m).sellt as usize]).arrange.0.is_none() + { + xlib::XRaiseWindow(state.dpy, (*(*m).sel).win); + } + if (*(*m).lt[(*m).sellt as usize]).arrange.0.is_some() { + let mut wc = xlib::XWindowChanges { + stack_mode: Below, + sibling: (*m).barwin, + x: Default::default(), + y: Default::default(), + width: Default::default(), + height: Default::default(), + border_width: Default::default(), + }; + let mut c = (*m).stack; + while !c.is_null() { + if !(*c).isfloating && is_visible(c) { + xlib::XConfigureWindow( + state.dpy, + (*c).win, + (CWSibling | CWStackMode) as c_uint, + &mut wc as *mut _, + ); + wc.sibling = (*c).win; + } + c = (*c).snext; + } + } + xlib::XSync(state.dpy, False); + let mut ev = xlib::XEvent { type_: 0 }; + while xlib::XCheckMaskEvent(state.dpy, EnterWindowMask, &mut ev) != 0 {} + } +} + +pub fn showhide(state: &mut State, c: *mut Client) { + log::trace!("showhide"); + unsafe { + if c.is_null() { + return; + } + if is_visible(c) { + // show clients top down + xlib::XMoveWindow(state.dpy, (*c).win, (*c).x, (*c).y); + if ((*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) + .arrange + .0 + .is_none() + || (*c).isfloating) + && !(*c).isfullscreen + { + resize(state, c, (*c).x, (*c).y, (*c).w, (*c).h, 0); + } + showhide(state, (*c).snext); + } else { + // hide clients bottom up + showhide(state, (*c).snext); + xlib::XMoveWindow(state.dpy, (*c).win, width(c) * -2, (*c).y); + } + } +} + +pub fn resize( + state: &mut State, + c: *mut Client, + mut x: i32, + mut y: i32, + mut w: i32, + mut h: i32, + interact: c_int, +) { + log::trace!("resize"); + if applysizehints(state, c, &mut x, &mut y, &mut w, &mut h, interact) != 0 { + resizeclient(state, c, x, y, w, h); + } +} + +pub fn resizeclient( + state: &mut State, + c: *mut Client, + x: i32, + y: i32, + w: i32, + h: i32, +) { + log::trace!("resizeclient"); + unsafe { + (*c).oldx = (*c).x; + (*c).oldy = (*c).y; + (*c).oldw = (*c).w; + (*c).oldh = (*c).h; + (*c).x = x; + (*c).y = y; + (*c).w = w; + (*c).h = h; + let mut wc = xlib::XWindowChanges { + x, + y, + width: w, + height: h, + border_width: (*c).bw, + sibling: 0, + stack_mode: 0, + }; + xlib::XConfigureWindow( + state.dpy, + (*c).win, + (CWX | CWY | CWWidth | CWHeight | CWBorderWidth) as u32, + &mut wc, + ); + configure(state, c); + xlib::XSync(state.dpy, False); + } +} + +pub fn resizebarwin(state: &mut State, m: *mut Monitor) { + unsafe { + let mut w = (*m).ww; + if state.CONFIG.showsystray + && m == systraytomon(state, m) + && !state.CONFIG.systrayonleft + { + w -= getsystraywidth(state) as i32; + } + XMoveResizeWindow( + state.dpy, + (*m).barwin, + (*m).wx, + (*m).by, + w as u32, + state.bh as u32, + ); + } +} + +pub fn configure(state: &mut State, c: *mut Client) { + log::trace!("configure"); + unsafe { + let mut ce = xlib::XConfigureEvent { + type_: x11::xlib::ConfigureNotify, + serial: 0, + send_event: 0, + display: state.dpy, + event: (*c).win, + window: (*c).win, + x: (*c).x, + y: (*c).y, + width: (*c).w, + height: (*c).h, + border_width: (*c).bw, + above: XNONE as u64, + override_redirect: False, + }; + xlib::XSendEvent( + state.dpy, + (*c).win, + False, + StructureNotifyMask, + &mut ce as *mut xlib::XConfigureEvent as *mut xlib::XEvent, + ); + } +} + +pub fn applysizehints( + state: &mut State, + c: *mut Client, + x: &mut i32, + y: &mut i32, + w: &mut i32, + h: &mut i32, + interact: c_int, +) -> c_int { + log::trace!("applysizehints"); + unsafe { + let m = (*c).mon; + let interact = interact != 0; + // set minimum possible + *w = 1.max(*w); + *h = 1.max(*h); + if interact { + if *x > state.sw { + *x = state.sw - width(c); + } + if *y > state.sh { + *y = state.sh - height(c); + } + if *x + *w + 2 * (*c).bw < 0 { + *x = 0; + } + if *y + *h + 2 * (*c).bw < 0 { + *y = 0; + } + } else { + if *x >= ((*m).wx + (*m).ww) { + *x = (*m).wx + (*m).ww - width(c); + } + if *y >= ((*m).wy + (*m).wh) { + *y = (*m).wy + (*m).wh - height(c); + } + if *x + *w + 2 * (*c).bw <= (*m).wx { + *x = (*m).wx; + } + if *y + *h + 2 * (*c).bw <= (*m).wy { + *y = (*m).wy; + } + } + if *h < state.bh { + *h = state.bh; + } + if *w < state.bh { + *w = state.bh; + } + if state.CONFIG.resize_hints + || (*c).isfloating + || (*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) + .arrange + .0 + .is_none() + { + if (*c).hintsvalid == 0 { + updatesizehints(state, c); + } + /* see last two sentences in ICCCM 4.1.2.3 */ + let baseismin = (*c).basew == (*c).minw && (*c).baseh == (*c).minh; + if !baseismin { + /* temporarily remove base dimensions */ + *w -= (*c).basew; + *h -= (*c).baseh; + } + /* adjust for aspect limits */ + if (*c).mina > 0.0 && (*c).maxa > 0.0 { + if (*c).maxa < *w as f32 / *h as f32 { + *w = (*h as f32 * (*c).maxa + 0.5) as i32; + } else if (*c).mina < *h as f32 / *w as f32 { + *h = (*w as f32 * (*c).mina + 0.5) as i32; + } + } + if baseismin { + /* increment calculation requires this */ + *w -= (*c).basew; + *h -= (*c).baseh; + } + /* adjust for increment value */ + if (*c).incw != 0 { + *w -= *w % (*c).incw; + } + if (*c).inch != 0 { + *h -= *h % (*c).inch; + } + /* restore base dimensions */ + *w = max(*w + (*c).basew, (*c).minw); + *h = max(*h + (*c).baseh, (*c).minh); + if (*c).maxw != 0 { + *w = std::cmp::min(*w, (*c).maxw); + } + if (*c).maxh != 0 { + *h = std::cmp::min(*h, (*c).maxh); + } + } + (*x != (*c).x || *y != (*c).y || *w != (*c).w || *h != (*c).h) as c_int + } +} + +pub fn updatesizehints(state: &mut State, c: *mut Client) { + log::trace!("updatesizehints"); + let mut msize: i64 = 0; + let mut size = xlib::XSizeHints { + flags: Default::default(), + x: Default::default(), + y: Default::default(), + width: Default::default(), + height: Default::default(), + min_width: Default::default(), + min_height: Default::default(), + max_width: Default::default(), + max_height: Default::default(), + width_inc: Default::default(), + height_inc: Default::default(), + min_aspect: xlib::AspectRatio { x: 0, y: 0 }, + max_aspect: xlib::AspectRatio { x: 0, y: 0 }, + base_width: Default::default(), + base_height: Default::default(), + win_gravity: Default::default(), + }; + unsafe { + if xlib::XGetWMNormalHints(state.dpy, (*c).win, &mut size, &mut msize) + == 0 + { + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + } + if size.flags & PBaseSize != 0 { + (*c).basew = size.base_width; + (*c).baseh = size.base_height; + } else if size.flags & PMinSize != 0 { + (*c).basew = size.min_width; + (*c).baseh = size.min_height; + } else { + (*c).basew = 0; + (*c).baseh = 0; + } + + if size.flags & PResizeInc != 0 { + (*c).incw = size.width_inc; + (*c).inch = size.height_inc; + } else { + (*c).incw = 0; + (*c).inch = 0; + } + + if size.flags & PMaxSize != 0 { + (*c).maxw = size.max_width; + (*c).maxh = size.max_height; + } else { + (*c).maxw = 0; + (*c).maxh = 0; + } + + if size.flags & PMinSize != 0 { + (*c).minw = size.min_width; + (*c).minh = size.min_height; + } else if size.flags & PBaseSize != 0 { + (*c).minw = size.base_width; + (*c).minh = size.base_height; + } else { + (*c).minw = 0; + (*c).minh = 0; + } + + if size.flags & PAspect != 0 { + (*c).mina = size.min_aspect.y as f32 / size.min_aspect.x as f32; + (*c).maxa = size.max_aspect.x as f32 / size.max_aspect.y as f32; + } else { + (*c).mina = 0.0; + (*c).maxa = 0.0; + } + + (*c).isfixed = ((*c).maxw != 0 + && (*c).maxh != 0 + && (*c).maxw == (*c).minw + && (*c).maxh == (*c).minh) as c_int; + (*c).hintsvalid = 1; + } +} + +pub fn pop(state: &mut State, c: *mut Client) { + log::trace!("pop"); + detach(c); + attach(c); + unsafe { + focus(state, c); + arrange(state, (*c).mon); + } +} + +pub fn detach(c: *mut Client) { + log::trace!("detach"); + unsafe { + let mut tc: *mut *mut Client = &mut (*(*c).mon).clients; + while !(*tc).is_null() && *tc != c { + tc = &mut (*(*tc)).next; + } + *tc = (*c).next; + } +} + +pub fn nexttiled(mut c: *mut Client) -> *mut Client { + log::trace!("nexttiled"); + unsafe { + while !c.is_null() && ((*c).isfloating || !is_visible(c)) { + c = (*c).next; + } + c + } +} + +pub fn grabkeys(state: &mut State) { + log::trace!("grabkeys"); + unsafe { + updatenumlockmask(state); + let modifiers = + [0, LockMask, state.numlockmask, state.numlockmask | LockMask]; + let (mut start, mut end, mut skip): (i32, i32, i32) = (0, 0, 0); + xlib::XUngrabKey(state.dpy, AnyKey, AnyModifier, state.root); + xlib::XDisplayKeycodes(state.dpy, &mut start, &mut end); + let syms = xlib::XGetKeyboardMapping( + state.dpy, + start as u8, + end - start + 1, + &mut skip, + ); + if syms.is_null() { + return; + } + for k in start..=end { + for key in &state.CONFIG.keys { + // skip modifier codes, we do that ourselves + if key.keysym + == (*syms.offset(((k - start) * skip) as isize)) as u64 + { + for m in modifiers { + xlib::XGrabKey( + state.dpy, + k, + key.mod_ | m, + state.root, + True, + GrabModeAsync, + GrabModeAsync, + ); + } + } + } + } + XFree(syms.cast()); + } +} + +pub fn updatenumlockmask(state: &mut State) { + log::trace!("updatenumlockmask"); + unsafe { + state.numlockmask = 0; + let modmap = xlib::XGetModifierMapping(state.dpy); + for i in 0..8 { + for j in 0..(*modmap).max_keypermod { + if *(*modmap) + .modifiermap + .offset((i * (*modmap).max_keypermod + j) as isize) + == xlib::XKeysymToKeycode(state.dpy, XK_Num_Lock as u64) + { + state.numlockmask = 1 << i; + } + } + } + xlib::XFreeModifiermap(modmap); + } +} + +pub fn seturgent(state: &mut State, c: *mut Client, urg: bool) { + log::trace!("seturgent"); + unsafe { + (*c).isurgent = urg as c_int; + let wmh = xlib::XGetWMHints(state.dpy, (*c).win); + if wmh.is_null() { + return; + } + (*wmh).flags = if urg { + (*wmh).flags | xlib::XUrgencyHint + } else { + (*wmh).flags & !{ xlib::XUrgencyHint } + }; + xlib::XSetWMHints(state.dpy, (*c).win, wmh); + XFree(wmh.cast()); + } +} + +pub fn unfocus(state: &mut State, c: *mut Client, setfocus: bool) { + log::trace!("unfocus"); + if c.is_null() { + return; + } + grabbuttons(state, c, false); + unsafe { + // scheme[SchemeNorm][ColBorder].pixel + let color = state.scheme[(Scheme::Norm, Col::Border)].pixel; + xlib::XSetWindowBorder(state.dpy, (*c).win, color); + if setfocus { + xlib::XSetInputFocus( + state.dpy, + state.root, + RevertToPointerRoot, + CurrentTime, + ); + xlib::XDeleteProperty( + state.dpy, + state.root, + state.netatom[Net::ActiveWindow as usize], + ); + } + } +} + +pub fn updatestatus(state: &mut State) { + log::trace!("updatestatus"); + if gettextprop(state.dpy, state.root, XA_WM_NAME, &mut state.stext) == 0 { + state.stext = "rwm-1.0".to_string(); + } + drawbar(state, state.selmon); + updatesystray(state); +} + +pub fn updatesystrayicongeom( + state: &mut State, + i: *mut Client, + w: c_int, + h: c_int, +) { + if i.is_null() { + return; + } + unsafe { + let i = &mut *i; + i.h = state.bh; + if w == h { + i.w = state.bh; + } else if h == state.bh { + i.w = w; + } else { + i.w = (state.bh as f32 * (w as f32 / h as f32)) as i32; + } + applysizehints(state, i, &mut i.x, &mut i.y, &mut i.w, &mut i.h, False); + // force icons into the systray dimensions if they don't want to + if i.h > state.bh { + if i.w == i.h { + i.w = state.bh; + } else { + i.w = (state.bh as f32 * (i.w as f32 / i.h as f32)) as i32; + } + i.h = state.bh; + } + } +} + +pub fn updatesystrayiconstate( + state: &mut State, + i: *mut Client, + ev: *mut XPropertyEvent, +) { + unsafe { + let mut flags: Atom = 0; + let code; + if !state.CONFIG.showsystray + || i.is_null() + || (*ev).atom != state.xatom[XEmbed::XEmbedInfo as usize] + { + flags = + getatomprop(state, i, state.xatom[XEmbed::XEmbedInfo as usize]); + if flags == 0 { + return; + } + } + let i = &mut *i; + if flags & XEMBED_MAPPED != 0 && i.tags == 0 { + i.tags = 1; + code = XEMBED_WINDOW_ACTIVATE; + XMapRaised(state.dpy, i.win); + setclientstate(state, i, NORMAL_STATE); + } else if (flags & XEMBED_MAPPED) == 0 && i.tags != 0 { + i.tags = 0; + code = XEMBED_WINDOW_DEACTIVATE; + XUnmapWindow(state.dpy, i.win); + setclientstate(state, i, WITHDRAWN_STATE); + } else { + return; + } + sendevent( + state, + i.win, + state.xatom[XEmbed::XEmbed as usize], + StructureNotifyMask as i32, + CurrentTime as i64, + code as i64, + 0, + state.systray().win as i64, + XEMBED_EMBEDDED_VERSION as i64, + ); + } +} + +pub const fn default_window_attributes() -> XSetWindowAttributes { + XSetWindowAttributes { + background_pixmap: 0, + background_pixel: 0, + border_pixmap: 0, + border_pixel: 0, + bit_gravity: 0, + win_gravity: 0, + backing_store: 0, + backing_planes: 0, + backing_pixel: 0, + save_under: 0, + event_mask: 0, + do_not_propagate_mask: 0, + override_redirect: 0, + colormap: 0, + cursor: 0, + } +} + +pub fn updatesystray(state: &mut State) { + unsafe { + let mut wa = default_window_attributes(); + let mut wc: XWindowChanges; + let mut i: *mut Client; + let m: *mut Monitor = systraytomon(state, null_mut()); + let mut x: c_int = (*m).mx + (*m).mw; + let sw = textw(&mut state.drw, &state.stext, 0) + + state.CONFIG.systrayspacing as i32; + let mut w = 1; + + if !state.CONFIG.showsystray { + return; + } + if state.CONFIG.systrayonleft { + x -= sw + state.lrpad / 2; + } + if state.systray.is_none() { + // init systray + let win = XCreateSimpleWindow( + state.dpy, + state.root, + x, + (*m).by, + w, + state.bh as u32, + 0, + 0, + state.scheme[(Scheme::Sel, Col::Bg)].pixel, + ); + wa.event_mask = ButtonPressMask | ExposureMask; + wa.override_redirect = True; + wa.background_pixel = state.scheme[(Scheme::Norm, Col::Bg)].pixel; + XSelectInput(state.dpy, win, SubstructureNotifyMask); + XChangeProperty( + state.dpy, + win, + state.netatom[Net::SystemTrayOrientation as usize], + XA_CARDINAL, + 32, + PropModeReplace, + &state.netatom[Net::SystemTrayOrientationHorz as usize] + as *const _ as *const _, + 1, + ); + XChangeWindowAttributes( + state.dpy, + win, + CWEventMask | CWOverrideRedirect | CWBackPixel, + &mut wa, + ); + XMapRaised(state.dpy, win); + XSetSelectionOwner( + state.dpy, + state.netatom[Net::SystemTray as usize], + win, + CurrentTime, + ); + if XGetSelectionOwner( + state.dpy, + state.netatom[Net::SystemTray as usize], + ) == win + { + sendevent( + state, + state.root, + state.xatom[XEmbed::Manager as usize], + StructureNotifyMask as i32, + CurrentTime as i64, + state.netatom[Net::SystemTray as usize] as i64, + win as i64, + 0_i64, + 0_i64, + ); + XSync(state.dpy, False); + state.systray = Some(Systray { win, icons: null_mut() }); + } else { + log::error!("unable to obtain system tray"); + return; + } + } // end if !SYSTRAY + cfor!(((w, i) = (0, state.systray().icons); + !i.is_null(); + i = (*i).next) { + // make sure the background color stays the same + wa.background_pixel = state.scheme[(Scheme::Norm , Col::Bg )].pixel; + XChangeWindowAttributes(state.dpy, (*i).win, CWBackPixel, &mut wa); + XMapRaised(state.dpy, (*i).win); + w += state.CONFIG.systrayspacing; + (*i).x = w as i32; + XMoveResizeWindow(state.dpy, (*i).win, (*i).x, 0, (*i).w as u32, (*i).h as u32); + w += (*i).w as u32; + if (*i).mon != m { + (*i).mon = m; + } + }); + w = if w != 0 { w + state.CONFIG.systrayspacing } else { 1 }; + x -= w as i32; + XMoveResizeWindow( + state.dpy, + state.systray().win, + x, + (*m).by, + w, + state.bh as u32, + ); + wc = XWindowChanges { + x, + y: (*m).by, + width: w as i32, + height: state.bh, + border_width: 0, + sibling: (*m).barwin, + stack_mode: Above, + }; + XConfigureWindow( + state.dpy, + state.systray().win, + (CWX | CWY | CWWidth | CWHeight | CWSibling | CWStackMode) as u32, + &mut wc, + ); + XMapWindow(state.dpy, state.systray().win); + XMapSubwindows(state.dpy, state.systray().win); + // redraw background + XSetForeground( + state.dpy, + state.drw.gc, + state.scheme[(Scheme::Norm, Col::Bg)].pixel, + ); + XFillRectangle( + state.dpy, + state.systray().win, + state.drw.gc, + 0, + 0, + w, + state.bh as u32, + ); + XSync(state.dpy, False); + } // end unsafe +} + +pub fn wintosystrayicon(state: &State, w: Window) -> *mut Client { + unsafe { + let mut i = null_mut(); + if !state.CONFIG.showsystray || w == 0 { + return i; + } + cfor!((i = state.systray().icons; !i.is_null() && (*i).win != w; + i = (*i).next) {}); + + i + } +} + +pub fn systraytomon(state: &State, m: *mut Monitor) -> *mut Monitor { + unsafe { + let mut t: *mut Monitor; + let mut i; + let mut n; + if state.CONFIG.systraypinning == 0 { + if m.is_null() { + return state.selmon; + } + if m == state.selmon { + return m; + } else { + return null_mut(); + } + } + cfor!(((n, t) = (1, state.mons); + !t.is_null() && !(*t).next.is_null(); + (n, t) = (n+1, (*t).next)) {}); + cfor!(((i, t) = (1, state.mons); + !t.is_null() && !(*t).next.is_null() && i < state.CONFIG.systraypinning; + (i, t) = (i+1, (*t).next)) {}); + if state.CONFIG.systraypinningfailfirst + && n < state.CONFIG.systraypinning + { + return state.mons; + } + + t + } +} + +pub fn textw(drw: &mut Drw, x: &str, lrpad: c_int) -> c_int { + log::trace!("textw"); + unsafe { drw::fontset_getwidth(drw, x) as c_int + lrpad } +} + +pub fn drawbar(state: &mut State, m: *mut Monitor) { + log::trace!("drawbar"); + unsafe { + let mut tw = 0; + let mut stw = 0; + let boxs = state.drw.fonts[0].h / 9; + let boxw = state.drw.fonts[0].h / 6 + 2; + let (mut occ, mut urg) = (0, 0); + + if state.CONFIG.showsystray + && m == systraytomon(state, m) + && !state.CONFIG.systrayonleft + { + stw = getsystraywidth(state); + } + + if !(*m).showbar { + return; + } + + // draw status first so it can be overdrawn by tags later + if m == state.selmon { + // status is only drawn on selected monitor + drw::setscheme(&mut state.drw, state.scheme[Scheme::Norm].clone()); + tw = textw(&mut state.drw, &state.stext, state.lrpad / 2) + 2; // 2px right padding + log::trace!("drawbar: text"); + drw::text( + &mut state.drw, + (*m).ww - tw - stw as i32, + 0, + tw as u32, + state.bh as u32, + (state.lrpad / 2 - 2) as u32, + &state.stext, + 0, + ); + } + + resizebarwin(state, m); + + let mut c = (*m).clients; + while !c.is_null() { + occ |= (*c).tags; + if (*c).isurgent != 0 { + urg |= (*c).tags; + } + c = (*c).next; + } + + let mut x = 0; + for (i, tag) in state.CONFIG.tags.iter().enumerate() { + let text = tag.to_owned(); + let w = textw(&mut state.drw, &text, state.lrpad); + drw::setscheme( + &mut state.drw, + state.scheme[if ((*m).tagset[(*m).seltags as usize] & 1 << i) + != 0 + { + Scheme::Sel + } else { + Scheme::Norm + }] + .clone(), + ); + log::trace!("drawbar: text 2"); + drw::text( + &mut state.drw, + x, + 0, + w as u32, + state.bh as u32, + state.lrpad as u32 / 2, + &text, + (urg as i32) & 1 << i, + ); + + if (occ & 1 << i) != 0 { + drw::rect( + &mut state.drw, + x + boxs as i32, + boxs as i32, + boxw, + boxw, + (m == state.selmon + && !(*state.selmon).sel.is_null() + && ((*(*state.selmon).sel).tags & 1 << i) != 0) + as c_int, + (urg & 1 << i) != 0, + ); + } + x += w as i32; + } + + let w = textw(&mut state.drw, &(*m).ltsymbol, state.lrpad); + drw::setscheme(&mut state.drw, state.scheme[Scheme::Norm].clone()); + log::trace!("drawbar: text 3"); + x = drw::text( + &mut state.drw, + x, + 0, + w as u32, + state.bh as u32, + state.lrpad as u32 / 2, + &(*m).ltsymbol, + 0, + ) as i32; + log::trace!("finished drawbar text 3"); + + let w = (*m).ww - tw - stw as i32 - x; + if w > state.bh { + if !(*m).sel.is_null() { + drw::setscheme( + &mut state.drw, + state.scheme[if m == state.selmon { + Scheme::Sel + } else { + Scheme::Norm + }] + .clone(), + ); + log::trace!("drawbar: text 4"); + drw::text( + &mut state.drw, + x, + 0, + w as u32, + state.bh as u32, + state.lrpad as u32 / 2, + &(*(*m).sel).name, + 0, + ); + if (*(*m).sel).isfloating { + drw::rect( + &mut state.drw, + x + boxs as i32, + boxs as i32, + boxw, + boxw, + (*(*m).sel).isfixed, + false, + ); + } + } else { + drw::setscheme( + &mut state.drw, + state.scheme[Scheme::Norm].clone(), + ); + drw::rect( + &mut state.drw, + x, + 0, + w as u32, + state.bh as u32, + 1, + true, + ); + } + } + drw::map( + &state.drw, + (*m).barwin, + 0, + 0, + (*m).ww as u32 - stw, + state.bh as u32, + ); + } +} + +pub fn gettextprop( + dpy: *mut Display, + w: Window, + atom: Atom, + text: &mut String, +) -> c_int { + log::trace!("gettextprop"); + unsafe { + let mut name = xlib::XTextProperty { + value: std::ptr::null_mut(), + encoding: 0, + format: 0, + nitems: 0, + }; + let c = xlib::XGetTextProperty(dpy, w, &mut name, atom); + if c == 0 || name.nitems == 0 { + return 0; + } + + let mut n = 0; + let mut list: *mut *mut i8 = std::ptr::null_mut(); + if name.encoding == XA_STRING { + let name_val = CStr::from_ptr(name.value.cast()); + *text = name_val.to_string_lossy().to_string(); + } else if xlib::XmbTextPropertyToTextList( + dpy, + &name, + &mut list, + &mut n as *mut _, + ) >= Success as i32 + && n > 0 + && !(*list).is_null() + { + // TODO handle this properly. *list is a "string" in some encoding I + // don't understand. the main test case I noticed an issue with was + // a browser tab with a ·, which was initially taking the value -73 + // as an i8, which is the correct character 183 as a u8. This + // solution works for characters like that that fit in a u8 but + // doesn't work for larger characters like ў (cyrillic short u). + // actually `list` doesn't even contain the right characters for the + // short u. it just starts at the space after it, as demonstrated by + // using libc::printf to try to print it. + // + // Looks like my encoding is different. Getting 238 in Rust vs 287 + // in C. Using XGetAtomName shows 238 is UTF8_STRING, while 287 is + // _NET_WM_WINDOW_TYPE_POPUP_MENU (??). In dwm in the VM, 287 is + // also UTF8_STRING + *text = String::new(); + let mut c = *list; + while *c != 0 { + text.push(char::from(*c as u8)); + c = c.offset(1); + } + xlib::XFreeStringList(list); + } + xlib::XFree(name.value as *mut _); + } + 1 +} + +pub fn updatebars(state: &mut State) { + log::trace!("updatebars"); + let mut wa = xlib::XSetWindowAttributes { + override_redirect: True, + background_pixmap: ParentRelative as u64, + event_mask: ButtonPressMask | ExposureMask, + // everything else should be uninit I guess + background_pixel: 0, + border_pixmap: 0, + border_pixel: 0, + bit_gravity: 0, + win_gravity: 0, + backing_store: 0, + backing_planes: 0, + backing_pixel: 0, + save_under: 0, + do_not_propagate_mask: 0, + colormap: 0, + cursor: 0, + }; + let mut ch = xlib::XClassHint { + res_name: c"rwm".as_ptr().cast_mut(), + res_class: c"rwm".as_ptr().cast_mut(), + }; + + unsafe { + let mut m = state.mons; + while !m.is_null() { + if (*m).barwin != 0 { + continue; + } + let mut w = (*m).ww; + if state.CONFIG.showsystray && m == systraytomon(state, m) { + w -= getsystraywidth(state) as i32; + } + (*m).barwin = xlib::XCreateWindow( + state.dpy, + state.root, + (*m).wx as c_int, + (*m).by as c_int, + w as c_uint, + state.bh as c_uint, + 0, + xlib::XDefaultDepth(state.dpy, state.screen), + CopyFromParent as c_uint, + xlib::XDefaultVisual(state.dpy, state.screen), + CWOverrideRedirect | CWBackPixmap | CWEventMask, + &mut wa, + ); + xlib::XDefineCursor( + state.dpy, + (*m).barwin, + state.cursors.normal.cursor, + ); + if state.CONFIG.showsystray && m == systraytomon(state, m) { + xlib::XMapRaised(state.dpy, state.systray().win); + } + xlib::XMapRaised(state.dpy, (*m).barwin); + xlib::XSetClassHint(state.dpy, (*m).barwin, &mut ch); + m = (*m).next; + } + } +} + +pub fn updategeom(state: &mut State) -> i32 { + log::trace!("updategeom"); + unsafe { + let mut dirty = 0; + if x11::xinerama::XineramaIsActive(state.dpy) != 0 { + log::trace!("updategeom: xinerama active"); + + let mut nn = 0; + let info = x11::xinerama::XineramaQueryScreens( + state.dpy as *mut _, + &mut nn, + ); + + let mut n = 0; + let mut m = state.mons; + while !m.is_null() { + m = (*m).next; + n += 1; + } + + // only consider unique geometries as separate screens + let unique: *mut x11::xinerama::XineramaScreenInfo = ecalloc( + nn as usize, + size_of::(), + ) + .cast(); + // Safety: we obviously just constructed this with this size. don't + // forget to free it later! + let unique = std::slice::from_raw_parts_mut(unique, nn as usize); + let mut i = 0; + let mut j = 0; + while i < nn { + if isuniquegeom(unique, j, info.offset(i as isize) as *mut _) + != 0 + { + libc::memcpy( + (&mut unique[j]) as *mut _ as *mut _, + info.offset(i as isize).cast(), + size_of::(), + ); + j += 1; + } + i += 1; + } + xlib::XFree(info.cast()); + nn = j as i32; + + // new monitors if nn > n + if nn > n { + log::trace!("updategeom: adding monitors"); + } + for _ in n..nn { + let mut m = state.mons; + while !m.is_null() && !(*m).next.is_null() { + m = (*m).next; + } + if !m.is_null() { + (*m).next = createmon(&state); + } else { + state.mons = createmon(&state); + } + } + + let mut i = 0; + let mut m = state.mons; + while i < nn && !m.is_null() { + if i >= n + || unique[i as usize].x_org != (*m).mx as i16 + || unique[i as usize].y_org != (*m).my as i16 + || unique[i as usize].width != (*m).mw as i16 + || unique[i as usize].height != (*m).mh as i16 + { + dirty = 1; + (*m).num = i; + + (*m).mx = unique[i as usize].x_org as i32; + (*m).wx = unique[i as usize].x_org as i32; + + (*m).my = unique[i as usize].y_org as i32; + (*m).wy = unique[i as usize].y_org as i32; + + (*m).mw = unique[i as usize].width as i32; + (*m).ww = unique[i as usize].width as i32; + + (*m).mh = unique[i as usize].height as i32; + (*m).wh = unique[i as usize].height as i32; + + updatebarpos(state, m); + } + m = (*m).next; + i += 1; + } + + // removed monitors if n > nn + if n > nn { + log::trace!("updategeom: removing monitors"); + } + for _ in nn..n { + let mut m = state.mons; + while !m.is_null() && !(*m).next.is_null() { + m = (*m).next; + } + let mut c = (*m).clients; + while !c.is_null() { + dirty = 1; + (*m).clients = (*c).next; + detachstack(c); + (*c).mon = state.mons; + attach(c); + attachstack(c); + c = (*m).clients; + } + if m == state.selmon { + state.selmon = state.mons; + } + cleanupmon(m, state); + } + libc::free(unique.as_mut_ptr().cast()); + } else { + log::trace!("updategeom: default monitor setup"); + + // default monitor setup + if state.mons.is_null() { + state.mons = createmon(&state); + } + if (*state.mons).mw != state.sw || (*state.mons).mh != state.sh { + dirty = 1; + (*state.mons).mw = state.sw; + (*state.mons).ww = state.sw; + (*state.mons).mh = state.sh; + (*state.mons).wh = state.sh; + updatebarpos(state, state.mons); + } + } + if dirty != 0 { + state.selmon = state.mons; + state.selmon = wintomon(state, state.root); + } + dirty + } +} + +pub fn wintomon(state: &mut State, w: Window) -> *mut Monitor { + log::trace!("wintomon"); + unsafe { + let mut x = 0; + let mut y = 0; + if w == state.root && getrootptr(state, &mut x, &mut y) != 0 { + return recttomon(state, x, y, 1, 1); + } + let mut m = state.mons; + while !m.is_null() { + if w == (*m).barwin { + return m; + } + m = (*m).next; + } + let c = wintoclient(state, w); + if !c.is_null() { + return (*c).mon; + } + state.selmon + } +} + +pub fn wintoclient(state: &mut State, w: u64) -> *mut Client { + log::trace!("wintoclient"); + unsafe { + let mut m = state.mons; + while !m.is_null() { + let mut c = (*m).clients; + while !c.is_null() { + if (*c).win == w { + return c; + } + c = (*c).next; + } + m = (*m).next; + } + } + std::ptr::null_mut() +} + +pub fn recttomon( + state: &State, + x: c_int, + y: c_int, + w: c_int, + h: c_int, +) -> *mut Monitor { + log::trace!("recttomon"); + unsafe { + let mut r = state.selmon; + let mut area = 0; + let mut m = state.mons; + while !m.is_null() { + let a = intersect(x, y, w, h, m); + if a > area { + area = a; + r = m; + } + m = (*m).next; + } + r + } +} + +pub fn removesystrayicon(state: &mut State, i: *mut Client) { + unsafe { + if !state.CONFIG.showsystray || i.is_null() { + return; + } + let mut ii: *mut *mut Client; + cfor!(( + ii = &mut state.systray_mut().icons as *mut _; + !ii.is_null() && *ii != i; + ii = &mut (*(*ii)).next) {}); + if !ii.is_null() { + *ii = (*i).next; + } + libc::free(i.cast()); + } +} + +// "macros" + +#[inline] +pub fn intersect( + x: c_int, + y: c_int, + w: c_int, + h: c_int, + m: *mut Monitor, +) -> c_int { + use std::cmp::{max, min}; + unsafe { + max(0, min((x) + (w), (*m).wx + (*m).ww) - max(x, (*m).wx)) + * max(0, min((y) + (h), (*m).wy + (*m).wh) - max(y, (*m).wy)) + } +} + +#[inline] +pub fn width(x: *mut Client) -> i32 { + unsafe { (*x).w + 2 * (*x).bw } +} + +#[inline] +pub fn height(x: *mut Client) -> i32 { + unsafe { (*x).h + 2 * (*x).bw } +} + +#[inline] +pub fn cleanmask(state: &State, mask: u32) -> u32 { + mask & !(state.numlockmask | LockMask) + & (ShiftMask + | ControlMask + | Mod1Mask + | Mod2Mask + | Mod3Mask + | Mod4Mask + | Mod5Mask) +} + +pub fn getrootptr(state: &mut State, x: *mut c_int, y: *mut c_int) -> c_int { + unsafe { + let mut di = 0; + let mut dui = 0; + let mut dummy = 0; + xlib::XQueryPointer( + state.dpy, state.root, &mut dummy, &mut dummy, x, y, &mut di, + &mut di, &mut dui, + ) + } +} + +/// remove `mon` from the linked list of `Monitor`s in `state.MONS` and free it. +pub fn cleanupmon(mon: *mut Monitor, state: &mut State) { + unsafe { + if mon == state.mons { + state.mons = (*state.mons).next; + } else { + let mut m = state.mons; + while !m.is_null() && (*m).next != mon { + m = (*m).next; + } + (*m).next = (*mon).next; + } + xlib::XUnmapWindow(state.dpy, (*mon).barwin); + xlib::XDestroyWindow(state.dpy, (*mon).barwin); + libc::free(mon.cast()); + } +} + +pub fn attachstack(c: *mut Client) { + log::trace!("attachstack"); + unsafe { + (*c).snext = (*(*c).mon).stack; + (*(*c).mon).stack = c; + } +} + +pub fn attach(c: *mut Client) { + log::trace!("attach"); + unsafe { + (*c).next = (*(*c).mon).clients; + (*(*c).mon).clients = c; + } +} + +pub fn detachstack(c: *mut Client) { + log::trace!("detachstack"); + unsafe { + let mut tc: *mut *mut Client = &mut (*(*c).mon).stack; + while !(*tc).is_null() && *tc != c { + tc = &mut (*(*tc)).snext; + } + *tc = (*c).snext; + + if c == (*(*c).mon).sel { + let mut t = (*(*c).mon).stack; + while !t.is_null() && !is_visible(t) { + t = (*t).snext; + } + (*(*c).mon).sel = t; + } + } +} + +/// this is actually a macro in the C code, but an inline function is probably +/// as close as I can get +#[inline] +pub fn is_visible(c: *const Client) -> bool { + unsafe { + ((*c).tags & (*(*c).mon).tagset[(*(*c).mon).seltags as usize]) != 0 + } +} + +pub fn updatebarpos(state: &mut State, m: *mut Monitor) { + log::trace!("updatebarpos"); + + unsafe { + (*m).wy = (*m).my; + (*m).wh = (*m).mh; + if (*m).showbar { + (*m).wh -= state.bh; + (*m).by = if (*m).topbar { (*m).wy } else { (*m).wy + (*m).wh }; + (*m).wy = if (*m).topbar { (*m).wy + state.bh } else { (*m).wy }; + } else { + (*m).by = -state.bh; + } + } +} + +pub fn isuniquegeom( + unique: &mut [x11::xinerama::XineramaScreenInfo], + mut n: usize, + info: *mut x11::xinerama::XineramaScreenInfo, +) -> c_int { + unsafe { + assert!(!info.is_null()); + let info = &*info; + while n > 0 { + n -= 1; // pretty sure this happens immediately in `while (n--)` + + if unique[n].x_org == info.x_org + && unique[n].y_org == info.y_org + && unique[n].width == info.width + && unique[n].height == info.height + { + return 0; + } + } + 1 + } +} + +pub fn cleanup(mut state: State) { + log::trace!("entering cleanup"); + + unsafe { + let a = Arg::Ui(!0); + view(&mut state, &a); + (*state.selmon).lt[(*state.selmon).sellt as usize] = + &Layout { symbol: String::new(), arrange: LayoutFn(None) }; + + let mut m = state.mons; + while !m.is_null() { + while !(*m).stack.is_null() { + unmanage(&mut state, (*m).stack, 0); + } + m = (*m).next; + } + + xlib::XUngrabKey(state.dpy, AnyKey, AnyModifier, state.root); + + while !state.mons.is_null() { + cleanupmon(state.mons, &mut state); + } + + if state.CONFIG.showsystray { + XUnmapWindow(state.dpy, state.systray().win); + XDestroyWindow(state.dpy, state.systray().win); + } + + xlib::XDestroyWindow(state.dpy, state.wmcheckwin); + xlib::XSync(state.dpy, False); + xlib::XSetInputFocus( + state.dpy, + PointerRoot as u64, + RevertToPointerRoot, + CurrentTime, + ); + xlib::XDeleteProperty( + state.dpy, + state.root, + state.netatom[Net::ActiveWindow as usize], + ); + + #[cfg(target_os = "linux")] + drop(Box::from_raw(state.xcon)); + + drop(state); + } + + log::trace!("finished cleanup"); +} + +pub fn unmanage(state: &mut State, c: *mut Client, destroyed: c_int) { + log::trace!("unmanage"); + unsafe { + let m = (*c).mon; + let mut wc = xlib::XWindowChanges { + x: 0, + y: 0, + width: 0, + height: 0, + border_width: 0, + sibling: 0, + stack_mode: 0, + }; + + if !(*c).swallowing.is_null() { + unswallow(state, c); + return; + } + + let s = swallowingclient(state, (*c).win); + if !s.is_null() { + libc::free((*s).swallowing.cast()); + (*s).swallowing = null_mut(); + arrange(state, m); + focus(state, null_mut()); + return; + } + + detach(c); + detachstack(c); + if destroyed == 0 { + wc.border_width = (*c).oldbw; + xlib::XGrabServer(state.dpy); /* avoid race conditions */ + xlib::XSetErrorHandler(Some(xerrordummy)); + xlib::XSelectInput(state.dpy, (*c).win, NoEventMask); + xlib::XConfigureWindow( + state.dpy, + (*c).win, + CWBorderWidth as u32, + &mut wc, + ); /* restore border */ + xlib::XUngrabButton( + state.dpy, + AnyButton as u32, + AnyModifier, + (*c).win, + ); + setclientstate(state, c, WITHDRAWN_STATE); + xlib::XSync(state.dpy, False); + xlib::XSetErrorHandler(Some(xerror)); + xlib::XUngrabServer(state.dpy); + } + libc::free(c.cast()); + + if s.is_null() { + arrange(state, m); + focus(state, null_mut()); + updateclientlist(state); + } + } +} + +/// I'm just using the OpenBSD version of the code in the patch rather than the +/// Linux version that uses XCB +pub fn winpid(state: &mut State, w: Window) -> pid_t { + #[cfg(target_os = "linux")] + unsafe { + log::trace!("winpid linux"); + + let mut result = 0; + + let spec = xcb::res::ClientIdSpec { + client: w as u32, + mask: xcb::res::ClientIdMask::LOCAL_CLIENT_PID, + }; + assert!(!state.xcon.is_null(), "xcon is null"); + let xcon = &*state.xcon; + let cookie = + xcon.send_request(&xcb::res::QueryClientIds { specs: &[spec] }); + let Ok(r) = xcon.wait_for_reply(cookie) else { + return 0; + }; + + for id in r.ids() { + let spec = id.spec(); + if !(spec.mask & xcb::res::ClientIdMask::LOCAL_CLIENT_PID) + .is_empty() + { + result = id.value()[0] as i32; + } + } + + result + } + + #[cfg(not(target_os = "linux"))] + unsafe { + use x11::xlib::{AnyPropertyType, XGetWindowProperty}; + + let mut type_: Atom = 0; + let mut format: c_int = 0; + let mut len: c_ulong = 0; + let mut bytes: c_ulong = 0; + let mut prop: *mut c_uchar = null_mut(); + if XGetWindowProperty( + state.dpy, + w, + XInternAtom(state.dpy, c"_NET_WM_PID".as_ptr(), 0), + 0, + 1, + False, + AnyPropertyType as u64, + &mut type_, + &mut format, + &mut len, + &mut bytes, + &mut prop, + ) != Success as i32 + || prop.is_null() + { + return 0; + } + let ret = *(prop as *mut pid_t); + XFree(prop.cast()); + + ret + } +} + +/// this looks insane... rust has std::os::unix::process::parent_id, but it +/// doesn't take any arguments. we need to get the parent of a specific process +/// here, so we read from /proc +pub fn getparentprocess(p: pid_t) -> pid_t { + let filename = format!("/proc/{p}/stat"); + let Ok(mut f) = std::fs::File::open(filename) else { + return 0; + }; + let mut buf = Vec::new(); + let Ok(_) = f.read_to_end(&mut buf) else { + return 0; + }; + let Ok(s) = String::from_utf8(buf) else { + return 0; + }; + // trying to emulate fscanf(f, "%*u %*s %*c %u", &v); which should give the + // 3rd field + match s.split_ascii_whitespace().nth(3).map(str::parse) { + Some(Ok(p)) => p, + _ => 0, + } +} + +pub fn isdescprocess(p: pid_t, mut c: pid_t) -> pid_t { + while p != c && c != 0 { + c = getparentprocess(c); + } + c +} + +pub fn termforwin(state: &mut State, w: *const Client) -> *mut Client { + unsafe { + let w = &*w; + + if w.pid == 0 || w.isterminal { + return null_mut(); + } + + let mut c; + let mut m; + + cfor!((m = state.mons; !m.is_null(); m = (*m).next) { + cfor!((c = (*m).clients; !c.is_null(); c = (*c).next) { + if (*c).isterminal && (*c).swallowing.is_null() + && (*c).pid != 0 && isdescprocess((*c).pid, w.pid) != 0 { + return c; + } + }); + }); + } + + null_mut() +} + +pub fn swallowingclient(state: &State, w: Window) -> *mut Client { + unsafe { + let mut c; + let mut m; + + cfor!((m = state.mons; !m.is_null(); m = (*m).next) { + cfor!((c = (*m).clients; !c.is_null(); c = (*c).next) { + if !(*c).swallowing.is_null() && (*(*c).swallowing).win == w { + return c; + } + }); + }); + + null_mut() + } +} + +pub fn updateclientlist(state: &mut State) { + unsafe { + xlib::XDeleteProperty( + state.dpy, + state.root, + state.netatom[Net::ClientList as usize], + ); + let mut m = state.mons; + while !m.is_null() { + let mut c = (*m).clients; + while !c.is_null() { + xlib::XChangeProperty( + state.dpy, + state.root, + state.netatom[Net::ClientList as usize], + XA_WINDOW, + 32, + PropModeAppend, + &((*c).win) as *const u64 as *const c_uchar, + 1, + ); + c = (*c).next; + } + m = (*m).next; + } + } +} + +pub fn setclientstate(s: &mut State, c: *mut Client, state: usize) { + let mut data: [c_long; 2] = [state as c_long, XNONE as c_long]; + let ptr: *mut c_uchar = data.as_mut_ptr().cast(); + unsafe { + xlib::XChangeProperty( + s.dpy, + (*c).win, + s.wmatom[WM::State as usize], + s.wmatom[WM::State as usize], + 32, + PropModeReplace, + ptr, + 2, + ); + } +} + +type HandlerFn = fn(&mut State, *mut xlib::XEvent); + +pub static HANDLER: LazyLock<[HandlerFn; x11::xlib::LASTEvent as usize]> = + LazyLock::new(|| { + pub fn dh(_state: &mut State, _ev: *mut xlib::XEvent) {} + let mut ret = [dh as fn(state: &mut State, *mut xlib::XEvent); + x11::xlib::LASTEvent as usize]; + ret[x11::xlib::ButtonPress as usize] = handlers::buttonpress; + ret[x11::xlib::ClientMessage as usize] = handlers::clientmessage; + ret[x11::xlib::ConfigureRequest as usize] = handlers::configurerequest; + ret[x11::xlib::ConfigureNotify as usize] = handlers::configurenotify; + ret[x11::xlib::DestroyNotify as usize] = handlers::destroynotify; + ret[x11::xlib::EnterNotify as usize] = handlers::enternotify; + ret[x11::xlib::Expose as usize] = handlers::expose; + ret[x11::xlib::FocusIn as usize] = handlers::focusin; + ret[x11::xlib::KeyPress as usize] = handlers::keypress; + ret[x11::xlib::MappingNotify as usize] = handlers::mappingnotify; + ret[x11::xlib::MapRequest as usize] = handlers::maprequest; + ret[x11::xlib::MotionNotify as usize] = handlers::motionnotify; + ret[x11::xlib::PropertyNotify as usize] = handlers::propertynotify; + ret[x11::xlib::ResizeRequest as usize] = handlers::resizerequest; + ret[x11::xlib::UnmapNotify as usize] = handlers::unmapnotify; + ret + }); + +/// main event loop +pub fn run(state: &mut State) { + unsafe { + xlib::XSync(state.dpy, False); + let mut ev: MaybeUninit = MaybeUninit::uninit(); + while state.running && xlib::XNextEvent(state.dpy, ev.as_mut_ptr()) == 0 + { + let mut ev: xlib::XEvent = ev.assume_init(); + if let Some(handler) = HANDLER.get(ev.type_ as usize) { + handler(state, &mut ev); + } + } + } +} + +pub fn scan(state: &mut State) { + let mut num = 0; + let mut d1 = 0; + let mut d2 = 0; + let mut wins: *mut Window = std::ptr::null_mut(); + let mut wa: MaybeUninit = MaybeUninit::uninit(); + unsafe { + if xlib::XQueryTree( + state.dpy, + state.root, + &mut d1, + &mut d2, + &mut wins as *mut _, + &mut num, + ) != 0 + { + for i in 0..num { + if xlib::XGetWindowAttributes( + state.dpy, + *wins.offset(i as isize), + wa.as_mut_ptr(), + ) == 0 + || (*wa.as_mut_ptr()).override_redirect != 0 + || xlib::XGetTransientForHint( + state.dpy, + *wins.offset(i as isize), + &mut d1, + ) != 0 + { + continue; + } + if (*wa.as_mut_ptr()).map_state == IsViewable + || getstate(state, *wins.offset(i as isize)) + == ICONIC_STATE as i64 + { + manage(state, *wins.offset(i as isize), wa.as_mut_ptr()); + } + } + for i in 0..num { + // now the transients + if xlib::XGetWindowAttributes( + state.dpy, + *wins.offset(i as isize), + wa.as_mut_ptr(), + ) == 0 + { + continue; + } + if xlib::XGetTransientForHint( + state.dpy, + *wins.offset(i as isize), + &mut d1, + ) != 0 + && ((*wa.as_mut_ptr()).map_state == IsViewable + || getstate(state, *wins.offset(i as isize)) + == ICONIC_STATE as i64) + { + manage(state, *wins.offset(i as isize), wa.as_mut_ptr()); + } + } + if !wins.is_null() { + XFree(wins.cast()); + } + } + } +} + +pub fn manage(state: &mut State, w: Window, wa: *mut xlib::XWindowAttributes) { + log::trace!("manage"); + let mut trans = 0; + unsafe { + let wa = *wa; + let c: *mut Client = util::ecalloc(1, size_of::()) as *mut _; + (*c).win = w; + (*c).pid = winpid(state, w); + (*c).x = wa.x; + (*c).oldx = wa.x; + (*c).y = wa.y; + (*c).oldy = wa.y; + (*c).w = wa.width; + (*c).oldw = wa.width; + (*c).h = wa.height; + (*c).oldh = wa.height; + (*c).oldbw = wa.border_width; + (*c).name = String::new(); + + let mut term: *mut Client = null_mut(); + + updatetitle(state, c); + log::trace!("manage: XGetTransientForHint"); + if xlib::XGetTransientForHint(state.dpy, w, &mut trans) != 0 { + let t = wintoclient(state, trans); + if !t.is_null() { + (*c).mon = (*t).mon; + (*c).tags = (*t).tags; + } else { + // NOTE must keep in sync with else below + (*c).mon = state.selmon; + applyrules(state, c); + term = termforwin(state, c); + } + } else { + // copied else case from above because the condition is supposed + // to be xgettransientforhint && (t = wintoclient) + (*c).mon = state.selmon; + applyrules(state, c); + term = termforwin(state, c); + } + if (*c).x + width(c) > ((*(*c).mon).wx + (*(*c).mon).ww) as i32 { + (*c).x = ((*(*c).mon).wx + (*(*c).mon).ww) as i32 - width(c); + } + if (*c).y + height(c) > ((*(*c).mon).wy + (*(*c).mon).wh) as i32 { + (*c).y = ((*(*c).mon).wy + (*(*c).mon).wh) as i32 - height(c); + } + (*c).x = max((*c).x, (*(*c).mon).wx as i32); + (*c).y = max((*c).y, (*(*c).mon).wy as i32); + (*c).bw = state.CONFIG.borderpx as i32; + + // TODO pretty sure this doesn't work with pertags, which explains some + // behavior I saw before in dwm. probably need to operate on + // selmon.pertag.tags[selmon.pertag.curtag]. + // + // TODO I'm also pretty sure this is _not_ the right way to be handling + // this. checking the name of the window and applying these rules seems + // like something meant to be handled by RULES + (*state.selmon).tagset[(*state.selmon).seltags as usize] &= + !state.scratchtag(); + if (*c).name == state.CONFIG.scratchpadname { + (*c).tags = state.scratchtag(); + (*(*c).mon).tagset[(*(*c).mon).seltags as usize] |= (*c).tags; + (*c).isfloating = true; + (*c).x = (*(*c).mon).wx + (*(*c).mon).ww / 2 - width(c) / 2; + (*c).y = (*(*c).mon).wy + (*(*c).mon).wh / 2 - height(c) / 2; + } + + log::trace!("manage: XWindowChanges"); + let mut wc = xlib::XWindowChanges { + x: 0, + y: 0, + width: 0, + height: 0, + border_width: (*c).bw, + sibling: 0, + stack_mode: 0, + }; + log::trace!("manage: XConfigureWindow"); + xlib::XConfigureWindow(state.dpy, w, CWBorderWidth as u32, &mut wc); + log::trace!( + "manage: XSetWindowBorder with state.dpy = {:?} and w = {w:?}", + &raw const state.dpy + ); + log::trace!("scheme: {:?}", &raw const state.scheme); + let scheme_norm = &state.scheme[Scheme::Norm]; + log::trace!("scheme[SchemeNorm]: {scheme_norm:?}"); + let border = scheme_norm[Col::Border as usize]; + log::trace!("scheme[SchemeNorm][ColBorder]: {border:?}"); + let pixel = border.pixel; + log::trace!("pixel = {pixel:?}"); + xlib::XSetWindowBorder(state.dpy, w, pixel); + configure(state, c); // propagates border width, if size doesn't change + updatewindowtype(state, c); + updatesizehints(state, c); + updatewmhints(state, c); + xlib::XSelectInput( + state.dpy, + w, + EnterWindowMask + | FocusChangeMask + | PropertyChangeMask + | StructureNotifyMask, + ); + grabbuttons(state, c, false); + if !(*c).isfloating { + (*c).oldstate = trans != 0 || (*c).isfixed != 0; + (*c).isfloating = (*c).oldstate; + } + if (*c).isfloating { + xlib::XRaiseWindow(state.dpy, (*c).win); + } + attach(c); + attachstack(c); + xlib::XChangeProperty( + state.dpy, + state.root, + state.netatom[Net::ClientList as usize], + XA_WINDOW, + 32, + PropModeAppend, + &((*c).win as c_uchar), + 1, + ); + // some windows require this + xlib::XMoveResizeWindow( + state.dpy, + (*c).win, + (*c).x + 2 * state.sw, + (*c).y, + (*c).w as u32, + (*c).h as u32, + ); + setclientstate(state, c, NORMAL_STATE); + if (*c).mon == state.selmon { + unfocus(state, (*state.selmon).sel, false); + } + (*(*c).mon).sel = c; + arrange(state, (*c).mon); + xlib::XMapWindow(state.dpy, (*c).win); + if !term.is_null() { + swallow(state, term, c); + } + focus(state, std::ptr::null_mut()); + } +} + +pub fn updatewmhints(state: &mut State, c: *mut Client) { + log::trace!("updatewmhints"); + const URGENT: i64 = xlib::XUrgencyHint; + unsafe { + let wmh = xlib::XGetWMHints(state.dpy, (*c).win); + if !wmh.is_null() { + if c == (*state.selmon).sel && (*wmh).flags & URGENT != 0 { + (*wmh).flags &= !URGENT; + xlib::XSetWMHints(state.dpy, (*c).win, wmh); + } else { + (*c).isurgent = ((*wmh).flags & URGENT != 0) as bool as c_int; + } + if (*wmh).flags & InputHint != 0 { + (*c).neverfocus = ((*wmh).input == 0) as c_int; + } else { + (*c).neverfocus = 0; + } + xlib::XFree(wmh.cast()); + } + } +} + +pub fn updatewindowtype(state: &mut State, c: *mut Client) { + log::trace!("updatewindowtype"); + unsafe { + let s = getatomprop(state, c, state.netatom[Net::WMState as usize]); + let wtype = + getatomprop(state, c, state.netatom[Net::WMWindowType as usize]); + if s == state.netatom[Net::WMFullscreen as usize] { + setfullscreen(state, c, true); + } + if wtype == state.netatom[Net::WMWindowTypeDialog as usize] { + (*c).isfloating = true; + } + } +} + +pub fn setfullscreen(state: &mut State, c: *mut Client, fullscreen: bool) { + unsafe { + if fullscreen && !(*c).isfullscreen { + xlib::XChangeProperty( + state.dpy, + (*c).win, + state.netatom[Net::WMState as usize], + XA_ATOM, + 32, + PropModeReplace, + // trying to emulate (unsigned char*)&netatom[NetWMFullscreen], + // so take a reference and then cast + &state.netatom[Net::WMFullscreen as usize] as *const u64 + as *const c_uchar, + 1, + ); + (*c).isfullscreen = true; + (*c).oldstate = (*c).isfloating; + (*c).oldbw = (*c).bw; + (*c).bw = 0; + (*c).isfloating = true; + resizeclient( + state, + c, + (*(*c).mon).mx, + (*(*c).mon).my, + (*(*c).mon).mw, + (*(*c).mon).mh, + ); + xlib::XRaiseWindow(state.dpy, (*c).win); + } else if !fullscreen && (*c).isfullscreen { + xlib::XChangeProperty( + state.dpy, + (*c).win, + state.netatom[Net::WMState as usize], + XA_ATOM, + 32, + PropModeReplace, + std::ptr::null_mut::(), + 0, + ); + (*c).isfullscreen = false; + (*c).isfloating = (*c).oldstate; + (*c).bw = (*c).oldbw; + (*c).x = (*c).oldx; + (*c).y = (*c).oldy; + (*c).w = (*c).oldw; + (*c).h = (*c).oldh; + resizeclient(state, c, (*c).x, (*c).y, (*c).w, (*c).h); + arrange(state, (*c).mon); + } + } +} + +pub fn getatomprop(state: &mut State, c: *mut Client, prop: Atom) -> Atom { + let mut di = 0; + let mut dl = 0; + let mut p = std::ptr::null_mut(); + let mut da = 0; + let mut atom: Atom = 0; + unsafe { + // FIXME (systray author) getatomprop should return the number of items + // and a pointer to the stored data instead of this workaround + let mut req = XA_ATOM; + if prop == state.xatom[XEmbed::XEmbedInfo as usize] { + req = state.xatom[XEmbed::XEmbedInfo as usize]; + } + if xlib::XGetWindowProperty( + state.dpy, + (*c).win, + prop, + 0, + std::mem::size_of::() as i64, + False, + req, + &mut da, + &mut di, + &mut dl, + &mut dl, + &mut p, + ) == Success as i32 + && !p.is_null() + { + // the C code is *(Atom *)p. is that different from (Atom) *p? + // that's closer to what I had before + atom = *(p as *mut Atom); + if da == state.xatom[XEmbed::XEmbedInfo as usize] && dl == 2 { + atom = *(p as *mut Atom).add(1); + } + XFree(p.cast()); + } + } + atom +} + +// TODO this should really just be a method on Systray and called like +// state.systray.width() +pub fn getsystraywidth(state: &State) -> c_uint { + unsafe { + let mut w = 0; + let mut i; + if state.CONFIG.showsystray { + cfor!(( + i = state.systray().icons; + !i.is_null(); + (w, i) = (w + (*i).w + state.CONFIG.systrayspacing as i32, (*i).next)) + {}); + } + if w != 0 { + w as c_uint + state.CONFIG.systrayspacing + } else { + 1 + } + } +} + +pub fn applyrules(state: &mut State, c: *mut Client) { + log::trace!("applyrules"); + unsafe { + let mut ch = xlib::XClassHint { + res_name: std::ptr::null_mut(), + res_class: std::ptr::null_mut(), + }; + // rule matching + (*c).isfloating = false; + (*c).tags = 0; + xlib::XGetClassHint(state.dpy, (*c).win, &mut ch); + let class = if !ch.res_class.is_null() { + CStr::from_ptr(ch.res_class) + } else { + BROKEN + }; + let instance = if !ch.res_name.is_null() { + CStr::from_ptr(ch.res_name) + } else { + BROKEN + }; + + for r in &state.CONFIG.rules { + if (r.title.is_empty() || (*c).name.contains(&r.title)) + && (r.class.is_empty() + || class.to_string_lossy().contains(&r.class)) + && (r.instance.is_empty() + || instance.to_string_lossy().contains(&r.instance)) + { + (*c).isterminal = r.isterminal; + (*c).noswallow = r.noswallow; + (*c).isfloating = r.isfloating; + (*c).tags |= r.tags; + let mut m = state.mons; + while !m.is_null() && (*m).num != r.monitor { + m = (*m).next; + } + if !m.is_null() { + (*c).mon = m; + } + } + } + if !ch.res_class.is_null() { + xlib::XFree(ch.res_class.cast()); + } + if !ch.res_name.is_null() { + xlib::XFree(ch.res_name.cast()); + } + (*c).tags = if (*c).tags & state.tagmask() != 0 { + (*c).tags & state.tagmask() + } else { + (*(*c).mon).tagset[(*(*c).mon).seltags as usize] + }; + } +} + +pub fn swallow(state: &mut State, p: *mut Client, c: *mut Client) { + unsafe { + let c = &mut *c; + if c.noswallow || c.isterminal { + return; + } + if c.noswallow && !state.CONFIG.swallowfloating && c.isfloating { + return; + } + detach(c); + detachstack(c); + + setclientstate(state, c, WITHDRAWN_STATE); + let p = &mut *p; + XUnmapWindow(state.dpy, p.win); + p.swallowing = c; + c.mon = p.mon; + + std::mem::swap(&mut p.win, &mut c.win); + updatetitle(state, p); + XMoveResizeWindow(state.dpy, p.win, p.x, p.y, p.w as u32, p.h as u32); + arrange(state, p.mon); + configure(state, p); + updateclientlist(state); + } +} + +pub fn unswallow(state: &mut State, c: *mut Client) { + unsafe { + let c = &mut *c; + + c.win = (*c.swallowing).win; + + libc::free(c.swallowing.cast()); + c.swallowing = null_mut(); + + // unfullscreen the client + setfullscreen(state, c, false); + updatetitle(state, c); + arrange(state, c.mon); + XMapWindow(state.dpy, c.win); + XMoveResizeWindow(state.dpy, c.win, c.x, c.y, c.w as u32, c.h as u32); + setclientstate(state, c, NORMAL_STATE); + focus(state, null_mut()); + arrange(state, c.mon); + } +} + +pub const BUTTONMASK: i64 = ButtonPressMask | ButtonReleaseMask; +pub const MOUSEMASK: i64 = BUTTONMASK | PointerMotionMask; + +pub fn updatetitle(state: &mut State, c: *mut Client) { + log::trace!("updatetitle"); + unsafe { + if gettextprop( + state.dpy, + (*c).win, + state.netatom[Net::WMName as usize], + &mut (*c).name, + ) == 0 + { + gettextprop(state.dpy, (*c).win, XA_WM_NAME, &mut (*c).name); + } + if (*c).name.is_empty() { + /* hack to mark broken clients */ + (*c).name = BROKEN.to_string_lossy().to_string(); + } + } +} + +pub fn getstate(state: &mut State, w: Window) -> c_long { + let mut format = 0; + let mut result: c_long = -1; + let mut p: *mut c_uchar = std::ptr::null_mut(); + let mut n = 0; + let mut extra = 0; + let mut real = 0; + unsafe { + let cond = xlib::XGetWindowProperty( + state.dpy, + w, + state.wmatom[WM::State as usize], + 0, + 2, + False, + state.wmatom[WM::State as usize], + &mut real, + &mut format, + &mut n, + &mut extra, + (&mut p) as *mut *mut c_uchar, + ); + if cond != Success as i32 { + return -1; + } + if n != 0 { + result = *p as c_long; + } + XFree(p.cast()); + result + } +} diff --git a/src/state.rs b/src/state.rs index 68ed59f..2f96aa8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,6 +9,7 @@ use x11::xlib::{self, Atom, Display}; use xcb::Connection; use crate::{ + config::Config, drw::{self, Drw}, enums::{Col, Net, Scheme, XEmbed, WM}, Clr, Cursors, Monitor, Systray, Window, @@ -66,6 +67,7 @@ pub struct State { pub wmcheckwin: Window, pub running: bool, pub numlockmask: c_uint, + pub CONFIG: Config, #[cfg(target_os = "linux")] pub xcon: *mut Connection, @@ -79,6 +81,14 @@ impl State { pub fn systray_mut(&mut self) -> &mut Systray { self.systray.as_mut().unwrap() } + + pub fn tagmask(&self) -> u32 { + (1 << self.CONFIG.tags.len()) - 1 + } + + pub fn scratchtag(&self) -> u32 { + 1 << self.CONFIG.tags.len() + } } impl Drop for State { diff --git a/src/tests.rs b/src/tests.rs index 93395a9..b0a7eee 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,5 @@ -use rwm::events::Event; -use xlib::Button1; +use rwm::{events::Event, handlers, textw}; +use x11::xlib::{Button1, XOpenDisplay}; use super::*; @@ -11,9 +11,9 @@ fn main() { let mut cmd = Command::new("Xvfb").arg(":1").spawn().unwrap(); // wait for xephyr to start - let mut dpy = unsafe { xlib::XOpenDisplay(c":1.0".as_ptr()) }; + let mut dpy = unsafe { XOpenDisplay(c":1.0".as_ptr()) }; while dpy.is_null() { - dpy = unsafe { xlib::XOpenDisplay(c":1.0".as_ptr()) }; + dpy = unsafe { XOpenDisplay(c":1.0".as_ptr()) }; } // goto for killing xephyr no matter what @@ -42,7 +42,8 @@ fn main() { let mut button = Event::button( unsafe { (*state.selmon).barwin }, Button1, - CONFIG + state + .CONFIG .tags .iter() .map(|tag| textw(&mut state.drw, tag, state.lrpad)) diff --git a/src/xembed.rs b/src/xembed.rs index 14f3665..9ffaf7c 100644 --- a/src/xembed.rs +++ b/src/xembed.rs @@ -1,17 +1,17 @@ use std::ffi::c_int; -pub(crate) const SYSTEM_TRAY_REQUEST_DOCK: c_int = 0; +pub const SYSTEM_TRAY_REQUEST_DOCK: c_int = 0; /* XEMBED messages */ -pub(crate) const XEMBED_EMBEDDED_NOTIFY: c_int = 0; -pub(crate) const XEMBED_FOCUS_IN: c_int = 4; -pub(crate) const XEMBED_MODALITY_ON: c_int = 10; +pub const XEMBED_EMBEDDED_NOTIFY: c_int = 0; +pub const XEMBED_FOCUS_IN: c_int = 4; +pub const XEMBED_MODALITY_ON: c_int = 10; -pub(crate) const XEMBED_MAPPED: u64 = 1 << 0; -pub(crate) const XEMBED_WINDOW_ACTIVATE: c_int = 1; -pub(crate) const XEMBED_WINDOW_DEACTIVATE: c_int = 2; +pub const XEMBED_MAPPED: u64 = 1 << 0; +pub const XEMBED_WINDOW_ACTIVATE: c_int = 1; +pub const XEMBED_WINDOW_DEACTIVATE: c_int = 2; -pub(crate) const VERSION_MAJOR: c_int = 0; -pub(crate) const VERSION_MINOR: c_int = 0; -pub(crate) const XEMBED_EMBEDDED_VERSION: c_int = +pub const VERSION_MAJOR: c_int = 0; +pub const VERSION_MINOR: c_int = 0; +pub const XEMBED_EMBEDDED_VERSION: c_int = (VERSION_MAJOR << 16) | VERSION_MINOR; From ec8caf81d3746b469f393169e1c37d3910692967 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 23:23:46 -0500 Subject: [PATCH 07/27] clippy fix --- src/main_.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main_.rs b/src/main_.rs index d34e64b..d7314f2 100644 --- a/src/main_.rs +++ b/src/main_.rs @@ -1765,9 +1765,9 @@ pub fn updategeom(state: &mut State) -> i32 { m = (*m).next; } if !m.is_null() { - (*m).next = createmon(&state); + (*m).next = createmon(state); } else { - state.mons = createmon(&state); + state.mons = createmon(state); } } @@ -1831,7 +1831,7 @@ pub fn updategeom(state: &mut State) -> i32 { // default monitor setup if state.mons.is_null() { - state.mons = createmon(&state); + state.mons = createmon(state); } if (*state.mons).mw != state.sw || (*state.mons).mh != state.sh { dirty = 1; From 1967a4b3475dcdbd8d3d25051a9142fdc929ded4 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 23:24:14 -0500 Subject: [PATCH 08/27] lowercase config --- src/handlers.rs | 36 ++++++++--------- src/key_handlers.rs | 26 ++++++------- src/main_.rs | 94 ++++++++++++++++++++++----------------------- src/state.rs | 6 +-- src/tests.rs | 2 +- 5 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/handlers.rs b/src/handlers.rs index cc656ba..fb151a8 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -56,17 +56,17 @@ pub fn buttonpress(state: &mut State, e: *mut XEvent) { let mut x = 0; // emulating do-while loop { - x += textw(&mut state.drw, &state.CONFIG.tags[i], state.lrpad); + x += textw(&mut state.drw, &state.config.tags[i], state.lrpad); // condition if ev.x < x { break; } i += 1; - if i >= state.CONFIG.tags.len() { + if i >= state.config.tags.len() { break; } } - if i < state.CONFIG.tags.len() { + if i < state.config.tags.len() { click = Clk::TagBar; arg = Arg::Ui(1 << i); } else if ev.x @@ -95,20 +95,20 @@ pub fn buttonpress(state: &mut State, e: *mut XEvent) { click = Clk::ClientWin; } } - for i in 0..state.CONFIG.buttons.len() { - if click as u32 == state.CONFIG.buttons[i].click - && state.CONFIG.buttons[i].func.0.is_some() - && state.CONFIG.buttons[i].button == ev.button - && cleanmask(state, state.CONFIG.buttons[i].mask) + for i in 0..state.config.buttons.len() { + if click as u32 == state.config.buttons[i].click + && state.config.buttons[i].func.0.is_some() + && state.config.buttons[i].button == ev.button + && cleanmask(state, state.config.buttons[i].mask) == cleanmask(state, ev.state) { - let f = state.CONFIG.buttons[i].func.0.unwrap(); + let f = state.config.buttons[i].func.0.unwrap(); let a = if click == Clk::TagBar - && state.CONFIG.buttons[i].arg.i() == 0 + && state.config.buttons[i].arg.i() == 0 { &arg } else { - &state.CONFIG.buttons[i].arg + &state.config.buttons[i].arg }; f(state, a) } @@ -121,7 +121,7 @@ pub(crate) fn clientmessage(state: &mut State, e: *mut XEvent) { let cme = &(*e).client_message; let mut c = wintoclient(state, cme.window); - if state.CONFIG.showsystray + if state.config.showsystray && cme.window == state.systray().win && cme.message_type == state.netatom[Net::SystemTrayOP as usize] { @@ -479,15 +479,15 @@ pub(crate) fn keypress(state: &mut State, e: *mut XEvent) { let ev = &mut (*e).key; let keysym = xlib::XKeycodeToKeysym(state.dpy, ev.keycode as KeyCode, 0); - for i in 0..state.CONFIG.keys.len() { - if keysym == state.CONFIG.keys[i].keysym - && cleanmask(state, state.CONFIG.keys[i].mod_) + for i in 0..state.config.keys.len() { + if keysym == state.config.keys[i].keysym + && cleanmask(state, state.config.keys[i].mod_) == cleanmask(state, ev.state) - && state.CONFIG.keys[i].func.0.is_some() + && state.config.keys[i].func.0.is_some() { - state.CONFIG.keys[i].func.0.unwrap()( + state.config.keys[i].func.0.unwrap()( state, - &(state.CONFIG.keys[i].arg), + &(state.config.keys[i].arg), ); } } diff --git a/src/key_handlers.rs b/src/key_handlers.rs index 7747a79..52a8512 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -31,7 +31,7 @@ pub(crate) fn togglebar(state: &mut State, _arg: *const Arg) { [(*state.selmon).pertag.curtag as usize]; updatebarpos(state, state.selmon); resizebarwin(state, state.selmon); - if state.CONFIG.showsystray { + if state.config.showsystray { let mut wc = XWindowChanges { x: 0, y: 0, @@ -67,7 +67,7 @@ pub(crate) fn focusstack(state: &mut State, arg: *const Arg) { if (*state.selmon).sel.is_null() || ((*(*state.selmon).sel).isfullscreen - && state.CONFIG.lock_fullscreen) + && state.config.lock_fullscreen) { return; } @@ -245,7 +245,7 @@ pub(crate) fn setlayout(state: &mut State, arg: *const Arg) { if arg.is_null() || (*arg).l().is_none() || !std::ptr::eq( - &state.CONFIG.layouts[(*arg).l().unwrap()], + &state.config.layouts[(*arg).l().unwrap()], (*state.selmon).lt[(*state.selmon).sellt as usize], ) { @@ -258,7 +258,7 @@ pub(crate) fn setlayout(state: &mut State, arg: *const Arg) { (*state.selmon).pertag.ltidxs [(*state.selmon).pertag.curtag as usize] [(*state.selmon).sellt as usize] = - &state.CONFIG.layouts[(*arg).l().unwrap()]; + &state.config.layouts[(*arg).l().unwrap()]; (*state.selmon).lt[(*state.selmon).sellt as usize] = (*state.selmon).pertag.ltidxs [(*state.selmon).pertag.curtag as usize] @@ -546,24 +546,24 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { let mut nx = ocx + (ev.motion.x - x); let mut ny = ocy + (ev.motion.y - y); if ((*state.selmon).wx - nx).abs() - < state.CONFIG.snap as c_int + < state.config.snap as c_int { nx = (*state.selmon).wx; } else if (((*state.selmon).wx + (*state.selmon).ww) - (nx + width(c))) .abs() - < state.CONFIG.snap as c_int + < state.config.snap as c_int { nx = (*state.selmon).wx + (*state.selmon).ww - width(c); } if ((*state.selmon).wy - ny).abs() - < state.CONFIG.snap as c_int + < state.config.snap as c_int { ny = (*state.selmon).wy; } else if (((*state.selmon).wy + (*state.selmon).wh) - (ny + height(c))) .abs() - < state.CONFIG.snap as c_int + < state.config.snap as c_int { ny = (*state.selmon).wy + (*state.selmon).wh - height(c); @@ -573,8 +573,8 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { .arrange .0 .is_some() - && ((nx - c.x).abs() > state.CONFIG.snap as c_int - || (ny - c.y).abs() > state.CONFIG.snap as c_int) + && ((nx - c.x).abs() > state.config.snap as c_int + || (ny - c.y).abs() > state.config.snap as c_int) { togglefloating(state, null_mut()); } @@ -675,8 +675,8 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { .arrange .0 .is_some() - && ((nw - c.w).abs() > state.CONFIG.snap as c_int - || (nh - c.h).abs() > state.CONFIG.snap as c_int) + && ((nw - c.w).abs() > state.config.snap as c_int + || (nh - c.h).abs() > state.config.snap as c_int) { togglefloating(state, null_mut()); } @@ -721,7 +721,7 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { pub(crate) fn spawn(state: &mut State, arg: *const Arg) { unsafe { let mut argv = (*arg).v(); - if argv == *state.CONFIG.dmenucmd { + if argv == *state.config.dmenucmd { log::trace!("spawn: dmenucmd on monitor {}", (*state.selmon).num); argv.push("-m".into()); argv.push((*state.selmon).num.to_string()); diff --git a/src/main_.rs b/src/main_.rs index d7314f2..c39cf7d 100644 --- a/src/main_.rs +++ b/src/main_.rs @@ -148,22 +148,22 @@ pub fn createmon(state: &State) -> *mut Monitor { unsafe { (*m).tagset[0] = 1; (*m).tagset[1] = 1; - (*m).mfact = state.CONFIG.mfact; - (*m).nmaster = state.CONFIG.nmaster; - (*m).showbar = state.CONFIG.showbar; - (*m).topbar = state.CONFIG.topbar; - (*m).lt[0] = &state.CONFIG.layouts[0]; - (*m).lt[1] = &state.CONFIG.layouts[1 % state.CONFIG.layouts.len()]; - (*m).ltsymbol = state.CONFIG.layouts[0].symbol.clone(); + (*m).mfact = state.config.mfact; + (*m).nmaster = state.config.nmaster; + (*m).showbar = state.config.showbar; + (*m).topbar = state.config.topbar; + (*m).lt[0] = &state.config.layouts[0]; + (*m).lt[1] = &state.config.layouts[1 % state.config.layouts.len()]; + (*m).ltsymbol = state.config.layouts[0].symbol.clone(); (*m).pertag = Pertag { curtag: 1, prevtag: 1, - nmasters: vec![(*m).nmaster; state.CONFIG.tags.len() + 1], - mfacts: vec![(*m).mfact; state.CONFIG.tags.len() + 1], - sellts: vec![(*m).sellt; state.CONFIG.tags.len() + 1], - ltidxs: vec![(*m).lt; state.CONFIG.tags.len() + 1], - showbars: vec![(*m).showbar; state.CONFIG.tags.len() + 1], + nmasters: vec![(*m).nmaster; state.config.tags.len() + 1], + mfacts: vec![(*m).mfact; state.config.tags.len() + 1], + sellts: vec![(*m).sellt; state.config.tags.len() + 1], + ltidxs: vec![(*m).lt; state.config.tags.len() + 1], + showbars: vec![(*m).showbar; state.config.tags.len() + 1], }; } @@ -223,8 +223,8 @@ pub fn setup(dpy: *mut Display) -> State { let root = xlib::XRootWindow(dpy, screen); let sw = xlib::XDisplayWidth(dpy, screen); let mut drw = drw::create(dpy, screen, root, sw as u32, sh as u32); - let CONFIG = Config::load_home(); - if fontset_create(&mut drw, &CONFIG.fonts).is_err() + let config = Config::load_home(); + if fontset_create(&mut drw, &config.fonts).is_err() || drw.fonts.is_empty() { panic!("no fonts could be loaded"); @@ -259,7 +259,7 @@ pub fn setup(dpy: *mut Display) -> State { numlockmask: 0, running: true, systray: None, - CONFIG: Config::load_home(), + config: Config::load_home(), #[cfg(target_os = "linux")] xcon: null_mut(), @@ -324,10 +324,10 @@ pub fn setup(dpy: *mut Display) -> State { XInternAtom(state.dpy, c"_XEMBED_INFO".as_ptr(), False); /* init appearance */ - for i in 0..state.CONFIG.colors.0.len() { + for i in 0..state.config.colors.0.len() { state.scheme.push(drw::scm_create( &state.drw, - &state.CONFIG.colors.0[i], + &state.config.colors.0[i], 3, )); } @@ -570,7 +570,7 @@ pub fn grabbuttons(state: &mut State, c: *mut Client, focused: bool) { XNONE as u64, ); } - for button in &state.CONFIG.buttons { + for button in &state.config.buttons { if button.click == Clk::ClientWin as u32 { for mod_ in modifiers { xlib::XGrabButton( @@ -753,9 +753,9 @@ pub fn resizeclient( pub fn resizebarwin(state: &mut State, m: *mut Monitor) { unsafe { let mut w = (*m).ww; - if state.CONFIG.showsystray + if state.config.showsystray && m == systraytomon(state, m) - && !state.CONFIG.systrayonleft + && !state.config.systrayonleft { w -= getsystraywidth(state) as i32; } @@ -847,7 +847,7 @@ pub fn applysizehints( if *w < state.bh { *w = state.bh; } - if state.CONFIG.resize_hints + if state.config.resize_hints || (*c).isfloating || (*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) .arrange @@ -1030,7 +1030,7 @@ pub fn grabkeys(state: &mut State) { return; } for k in start..=end { - for key in &state.CONFIG.keys { + for key in &state.config.keys { // skip modifier codes, we do that ourselves if key.keysym == (*syms.offset(((k - start) * skip) as isize)) as u64 @@ -1166,7 +1166,7 @@ pub fn updatesystrayiconstate( unsafe { let mut flags: Atom = 0; let code; - if !state.CONFIG.showsystray + if !state.config.showsystray || i.is_null() || (*ev).atom != state.xatom[XEmbed::XEmbedInfo as usize] { @@ -1232,13 +1232,13 @@ pub fn updatesystray(state: &mut State) { let m: *mut Monitor = systraytomon(state, null_mut()); let mut x: c_int = (*m).mx + (*m).mw; let sw = textw(&mut state.drw, &state.stext, 0) - + state.CONFIG.systrayspacing as i32; + + state.config.systrayspacing as i32; let mut w = 1; - if !state.CONFIG.showsystray { + if !state.config.showsystray { return; } - if state.CONFIG.systrayonleft { + if state.config.systrayonleft { x -= sw + state.lrpad / 2; } if state.systray.is_none() { @@ -1312,7 +1312,7 @@ pub fn updatesystray(state: &mut State) { wa.background_pixel = state.scheme[(Scheme::Norm , Col::Bg )].pixel; XChangeWindowAttributes(state.dpy, (*i).win, CWBackPixel, &mut wa); XMapRaised(state.dpy, (*i).win); - w += state.CONFIG.systrayspacing; + w += state.config.systrayspacing; (*i).x = w as i32; XMoveResizeWindow(state.dpy, (*i).win, (*i).x, 0, (*i).w as u32, (*i).h as u32); w += (*i).w as u32; @@ -1320,7 +1320,7 @@ pub fn updatesystray(state: &mut State) { (*i).mon = m; } }); - w = if w != 0 { w + state.CONFIG.systrayspacing } else { 1 }; + w = if w != 0 { w + state.config.systrayspacing } else { 1 }; x -= w as i32; XMoveResizeWindow( state.dpy, @@ -1369,7 +1369,7 @@ pub fn updatesystray(state: &mut State) { pub fn wintosystrayicon(state: &State, w: Window) -> *mut Client { unsafe { let mut i = null_mut(); - if !state.CONFIG.showsystray || w == 0 { + if !state.config.showsystray || w == 0 { return i; } cfor!((i = state.systray().icons; !i.is_null() && (*i).win != w; @@ -1384,7 +1384,7 @@ pub fn systraytomon(state: &State, m: *mut Monitor) -> *mut Monitor { let mut t: *mut Monitor; let mut i; let mut n; - if state.CONFIG.systraypinning == 0 { + if state.config.systraypinning == 0 { if m.is_null() { return state.selmon; } @@ -1398,10 +1398,10 @@ pub fn systraytomon(state: &State, m: *mut Monitor) -> *mut Monitor { !t.is_null() && !(*t).next.is_null(); (n, t) = (n+1, (*t).next)) {}); cfor!(((i, t) = (1, state.mons); - !t.is_null() && !(*t).next.is_null() && i < state.CONFIG.systraypinning; + !t.is_null() && !(*t).next.is_null() && i < state.config.systraypinning; (i, t) = (i+1, (*t).next)) {}); - if state.CONFIG.systraypinningfailfirst - && n < state.CONFIG.systraypinning + if state.config.systraypinningfailfirst + && n < state.config.systraypinning { return state.mons; } @@ -1424,9 +1424,9 @@ pub fn drawbar(state: &mut State, m: *mut Monitor) { let boxw = state.drw.fonts[0].h / 6 + 2; let (mut occ, mut urg) = (0, 0); - if state.CONFIG.showsystray + if state.config.showsystray && m == systraytomon(state, m) - && !state.CONFIG.systrayonleft + && !state.config.systrayonleft { stw = getsystraywidth(state); } @@ -1465,7 +1465,7 @@ pub fn drawbar(state: &mut State, m: *mut Monitor) { } let mut x = 0; - for (i, tag) in state.CONFIG.tags.iter().enumerate() { + for (i, tag) in state.config.tags.iter().enumerate() { let text = tag.to_owned(); let w = textw(&mut state.drw, &text, state.lrpad); drw::setscheme( @@ -1676,7 +1676,7 @@ pub fn updatebars(state: &mut State) { continue; } let mut w = (*m).ww; - if state.CONFIG.showsystray && m == systraytomon(state, m) { + if state.config.showsystray && m == systraytomon(state, m) { w -= getsystraywidth(state) as i32; } (*m).barwin = xlib::XCreateWindow( @@ -1698,7 +1698,7 @@ pub fn updatebars(state: &mut State) { (*m).barwin, state.cursors.normal.cursor, ); - if state.CONFIG.showsystray && m == systraytomon(state, m) { + if state.config.showsystray && m == systraytomon(state, m) { xlib::XMapRaised(state.dpy, state.systray().win); } xlib::XMapRaised(state.dpy, (*m).barwin); @@ -1917,7 +1917,7 @@ pub fn recttomon( pub fn removesystrayicon(state: &mut State, i: *mut Client) { unsafe { - if !state.CONFIG.showsystray || i.is_null() { + if !state.config.showsystray || i.is_null() { return; } let mut ii: *mut *mut Client; @@ -2107,7 +2107,7 @@ pub fn cleanup(mut state: State) { cleanupmon(state.mons, &mut state); } - if state.CONFIG.showsystray { + if state.config.showsystray { XUnmapWindow(state.dpy, state.systray().win); XDestroyWindow(state.dpy, state.systray().win); } @@ -2536,7 +2536,7 @@ pub fn manage(state: &mut State, w: Window, wa: *mut xlib::XWindowAttributes) { } (*c).x = max((*c).x, (*(*c).mon).wx as i32); (*c).y = max((*c).y, (*(*c).mon).wy as i32); - (*c).bw = state.CONFIG.borderpx as i32; + (*c).bw = state.config.borderpx as i32; // TODO pretty sure this doesn't work with pertags, which explains some // behavior I saw before in dwm. probably need to operate on @@ -2547,7 +2547,7 @@ pub fn manage(state: &mut State, w: Window, wa: *mut xlib::XWindowAttributes) { // like something meant to be handled by RULES (*state.selmon).tagset[(*state.selmon).seltags as usize] &= !state.scratchtag(); - if (*c).name == state.CONFIG.scratchpadname { + if (*c).name == state.config.scratchpadname { (*c).tags = state.scratchtag(); (*(*c).mon).tagset[(*(*c).mon).seltags as usize] |= (*c).tags; (*c).isfloating = true; @@ -2772,15 +2772,15 @@ pub fn getsystraywidth(state: &State) -> c_uint { unsafe { let mut w = 0; let mut i; - if state.CONFIG.showsystray { + if state.config.showsystray { cfor!(( i = state.systray().icons; !i.is_null(); - (w, i) = (w + (*i).w + state.CONFIG.systrayspacing as i32, (*i).next)) + (w, i) = (w + (*i).w + state.config.systrayspacing as i32, (*i).next)) {}); } if w != 0 { - w as c_uint + state.CONFIG.systrayspacing + w as c_uint + state.config.systrayspacing } else { 1 } @@ -2809,7 +2809,7 @@ pub fn applyrules(state: &mut State, c: *mut Client) { BROKEN }; - for r in &state.CONFIG.rules { + for r in &state.config.rules { if (r.title.is_empty() || (*c).name.contains(&r.title)) && (r.class.is_empty() || class.to_string_lossy().contains(&r.class)) @@ -2849,7 +2849,7 @@ pub fn swallow(state: &mut State, p: *mut Client, c: *mut Client) { if c.noswallow || c.isterminal { return; } - if c.noswallow && !state.CONFIG.swallowfloating && c.isfloating { + if c.noswallow && !state.config.swallowfloating && c.isfloating { return; } detach(c); diff --git a/src/state.rs b/src/state.rs index 2f96aa8..2452ecc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -67,7 +67,7 @@ pub struct State { pub wmcheckwin: Window, pub running: bool, pub numlockmask: c_uint, - pub CONFIG: Config, + pub config: Config, #[cfg(target_os = "linux")] pub xcon: *mut Connection, @@ -83,11 +83,11 @@ impl State { } pub fn tagmask(&self) -> u32 { - (1 << self.CONFIG.tags.len()) - 1 + (1 << self.config.tags.len()) - 1 } pub fn scratchtag(&self) -> u32 { - 1 << self.CONFIG.tags.len() + 1 << self.config.tags.len() } } diff --git a/src/tests.rs b/src/tests.rs index b0a7eee..40ae2e7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -43,7 +43,7 @@ fn main() { unsafe { (*state.selmon).barwin }, Button1, state - .CONFIG + .config .tags .iter() .map(|tag| textw(&mut state.drw, tag, state.lrpad)) From 31551c1a2de8fa8e2851aa182f7b3fe7d7a9a2b9 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Wed, 11 Dec 2024 23:52:31 -0500 Subject: [PATCH 09/27] delete fig, use lua, check Key loading --- Cargo.lock | 18 -- Cargo.toml | 1 - src/config.lua | 17 +- src/config.rs | 263 ++---------------- src/config/fig_env.rs | 62 ++--- src/config/key.rs | 78 +----- .../rwm__config__tests__from_lua.snap | 20 +- 7 files changed, 88 insertions(+), 371 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32e8fb6..30bac5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,14 +175,6 @@ dependencies = [ "typeid", ] -[[package]] -name = "fig" -version = "0.1.0" -source = "git+https://github.com/ntBre/fig#37dcfda168ddbf8d1b5b83838de8d3982e8791fe" -dependencies = [ - "nom", -] - [[package]] name = "humantime" version = "2.1.0" @@ -285,15 +277,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "nom" -version = "8.0.0-alpha2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2785486691910e746b3d84220af18362bd15ba71f9b7bc0a47ac549550b7fe2" -dependencies = [ - "memchr", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -423,7 +406,6 @@ name = "rwm" version = "0.1.0" dependencies = [ "env_logger", - "fig", "insta", "libc", "log", diff --git a/Cargo.toml b/Cargo.toml index 7689465..cb3509d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ libc = "0.2.158" yeslogic-fontconfig-sys = "6.0.0" env_logger = "0.11.3" log = "0.4.21" -fig = { git = "https://github.com/ntBre/fig" } mlua = { version = "0.10.2", features = ["lua54", "serialize"] } serde = { version = "1.0.216", features = ["derive"] } diff --git a/src/config.lua b/src/config.lua index 6df9b49..eaae866 100644 --- a/src/config.lua +++ b/src/config.lua @@ -1,4 +1,16 @@ +function key (mod, keysym, func, arg) + return { + mod_ = mod, + keysym = keysym, + func = func, + arg = arg, + } +end + dmenufont = "monospace:size=10" +dmenucmd = {"dmenu_run", "-fn", dmenufont, "-nb"} +modkey = Mod4Mask +s_mod = ShiftMask | modkey rwm = { borderpx = 3, @@ -17,8 +29,9 @@ rwm = { -- gray4, cyan, cyan sel = {"#eeeeee", "#005577", "#005577"}, }, - keys = {}, -- TODO - dmenucmd = {"dmenu_run", "-fn", dmenufont, "-nb"}, -- TODO + -- TODO + keys = {key(modkey, XK_p, spawn, {V = dmenucmd})}, + dmenucmd = dmenucmd, -- TODO rules = {}, -- TODO swallowfloating = false, systraypinning = 0, diff --git a/src/config.rs b/src/config.rs index 4973d79..228fb5f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,8 +7,7 @@ use std::{ sync::LazyLock, }; -use fig::{Fig, FigError, Value}; -use key::{get_arg, FUNC_MAP}; +use fig_env::{CLICKS, HANDLERS, KEYS}; use mlua::{Lua, LuaSerdeExt as _}; use x11::keysym::{ XK_Return, XK_Tab, XK_b, XK_c, XK_comma, XK_d, XK_f, XK_grave, XK_h, XK_i, @@ -19,12 +18,12 @@ use x11::keysym::{ use x11::xlib::{Button1, Button2, Button3, ControlMask, Mod4Mask, ShiftMask}; use crate::{ - config::key::{conv, Key}, + config::key::Key, enums::{Clk, Scheme}, key_handlers::*, layouts::{monocle, tile}, }; -use crate::{Arg, Button, ButtonFn, Layout, LayoutFn, Monitor, Rule, State}; +use crate::{Arg, Button, Layout, LayoutFn, Rule}; mod fig_env; pub mod key; @@ -159,242 +158,30 @@ pub struct Config { unsafe impl Send for Config {} unsafe impl Sync for Config {} -fn get( - v: &mut HashMap, - name: &str, -) -> Result { - v.remove(name).ok_or(format!("failed to find {name}")) -} - -/// Extract colors from a fig Map>. The two Str keys should be -/// `SchemeNorm` and `SchemeSel`, and the two Lists should be of length 3. -fn get_colors( - v: &mut HashMap, -) -> Result<[[CString; 3]; 2], Box> { - let colors = get(v, "colors")?; - let mut colors = - colors.try_into_map().map_err(|_| "colors must be a map")?; - let norm: Vec = get(&mut colors, "SchemeNorm")?.try_into()?; - let sel: Vec = get(&mut colors, "SchemeSel")?.try_into()?; - let mut ret = default_colors(); - let [n0, n1, n2] = &norm[..] else { - return Err("not enough colors for SchemeNorm".into()); - }; - ret[Scheme::Norm as usize] = [ - CString::new(n0.clone())?, - CString::new(n1.clone())?, - CString::new(n2.clone())?, - ]; - let [s0, s1, s2] = &sel[..] else { - return Err("not enough colors for SchemeSel".into()); - }; - ret[Scheme::Sel as usize] = [ - CString::new(s0.clone())?, - CString::new(s1.clone())?, - CString::new(s2.clone())?, - ]; - Ok(ret) -} - -fn get_keys(v: &mut HashMap) -> Result, FigError> { - let err = Err(FigError::Conversion); - let Some(keys) = v.get("keys") else { - log::trace!("failed to get keys from config"); - return err; - }; - let keys = conv(keys.as_list())?; - keys.into_iter().map(Key::try_from).collect() -} +impl Config { + #[allow(dependency_on_unit_never_type_fallback, unused)] + pub fn from_lua(path: impl AsRef) -> Result> { + let lua = Lua::new(); + let globals = lua.globals(); -fn get_rules(v: &mut HashMap) -> Result, FigError> { - let err = Err(FigError::Conversion); - let Some(rules) = v.get("rules") else { - log::trace!("failed to get rules from config"); - return err; - }; - let rules: Vec = conv(rules.as_list())?; - - let maybe_string = |val: Value| -> Result { - if let Ok(s) = String::try_from(val.clone()) { - Ok(s) - } else if val.is_nil() { - Ok(String::new()) - } else { - log::error!("expected Str or Nil"); - Err(FigError::Conversion) - } - }; - - let mut ret = Vec::new(); - for rule in rules { - let rule: Vec = rule.try_into()?; - if rule.len() != 8 { - log::error!("invalid rule: {rule:?}"); - return err; + // install handler functions for keybindings + for (k, v) in HANDLERS { + globals.set(k, v); } - ret.push(Rule { - class: maybe_string(rule[0].clone())?, - instance: maybe_string(rule[1].clone())?, - title: maybe_string(rule[2].clone())?, - tags: i64::try_from(rule[3].clone())? as u32, - isfloating: rule[4].clone().try_into()?, - isterminal: rule[5].clone().try_into()?, - noswallow: rule[6].clone().try_into()?, - monitor: i64::try_from(rule[7].clone())? as i32, - }); - } - Ok(ret) -} - -fn get_buttons( - v: &mut HashMap, -) -> Result, FigError> { - let err = Err(FigError::Conversion); - let Some(buttons) = v.get("buttons") else { - log::trace!("failed to get buttons from config"); - return err; - }; - let buttons: Vec = conv(buttons.as_list())?; - - // each entry should be a list of - // Clk (int), mask (int), button (int), func (str or nil), arg (Map) - let mut ret = Vec::new(); - for button in buttons { - let button: Vec = button.try_into()?; - if button.len() != 5 { - log::error!("Expected 5 fields for button"); - return err; + // install key definitions + for (k, v) in KEYS { + globals.set(k, v); } - let func = match &button[3] { - Value::Str(s) => match FUNC_MAP.get(s.as_str()) { - res @ Some(_) => res.cloned(), - None => { - log::error!("unrecognized func name for button: {s}"); - return err; - } - }, - Value::Nil => None, - _ => { - log::error!("func field on button should be Str or nil"); - return err; - } - }; - ret.push(Button { - click: i64::try_from(button[0].clone())? as u32, - mask: i64::try_from(button[1].clone())? as u32, - button: i64::try_from(button[2].clone())? as u32, - func: ButtonFn(func), - arg: get_arg(conv(button[4].as_map())?)?, - }); - } - - Ok(ret) -} -fn get_layouts( - v: &mut HashMap, -) -> Result, FigError> { - let err = Err(FigError::Conversion); - let Some(layouts) = v.get("layouts") else { - log::trace!("failed to get layouts from config"); - return err; - }; - let layouts: Vec = conv(layouts.as_list())?; - - // each entry should be a list of - // Clk (int), mask (int), layout (int), func (str or nil), arg (Map) - let mut ret = Vec::new(); - for layout in layouts { - let layout: Vec = layout.try_into()?; - if layout.len() != 2 { - log::error!("Expected 2 fields for layout"); - return err; + // install click and button definitions + for (k, v) in CLICKS { + globals.set(k, v); } - let symbol: String = layout[0].clone().try_into()?; - - type F = fn(&mut State, *mut Monitor); - let arrange = match &layout[1] { - Value::Str(s) if s == "tile" => Some(tile as F), - Value::Str(s) if s == "monocle" => Some(monocle as F), - Value::Nil => None, - _ => { - log::error!("func field on layout should be Str or nil"); - return err; - } - }; - - ret.push(Layout { symbol, arrange: LayoutFn(arrange) }); - } - - Ok(ret) -} - -impl TryFrom for Config { - type Error = Box; - - fn try_from(Fig { variables: mut v }: Fig) -> Result { - let float = |val: fig::Value| val.try_into(); - let int = |val: fig::Value| { - val.try_into_int().map_err(|_| "unable to parse int") - }; - let bool = |val: fig::Value| val.try_into(); - let cstr_list = |val: fig::Value| -> Result, _> { - let strs: Vec = val.try_into()?; - strs.into_iter() - .map(CString::new) - .collect::, _>>() - .map_err(|_| Box::new(FigError::Conversion)) - }; - let str_list = |val: fig::Value| -> Result, FigError> { - let strs: Vec = val.try_into()?; - Ok(strs.into_iter().map(String::from).collect()) - }; - Ok(Self { - borderpx: int(get(&mut v, "borderpx")?)? as c_uint, - snap: int(get(&mut v, "snap")?)? as c_uint, - showbar: bool(get(&mut v, "showbar")?)?, - topbar: bool(get(&mut v, "topbar")?)?, - mfact: float(get(&mut v, "mfact")?)?, - nmaster: int(get(&mut v, "nmaster")?)? as c_int, - resize_hints: bool(get(&mut v, "resize_hints")?)?, - lock_fullscreen: bool(get(&mut v, "lock_fullscreen")?)?, - fonts: cstr_list(get(&mut v, "fonts")?)?, - tags: str_list(get(&mut v, "tags")?)?, - colors: ColorMap(get_colors(&mut v)?), - keys: get_keys(&mut v)?, - dmenucmd: get(&mut v, "dmenucmd")?.try_into()?, - rules: get_rules(&mut v)?, - swallowfloating: get(&mut v, "swallowfloating")?.try_into()?, - systraypinning: i64::try_from(get(&mut v, "systraypinning")?)? - as u32, - systrayonleft: get(&mut v, "systrayonleft")?.try_into()?, - systrayspacing: i64::try_from(get(&mut v, "systrayspacing")?)? - as u32, - systraypinningfailfirst: get(&mut v, "systraypinningfailfirst")? - .try_into()?, - showsystray: get(&mut v, "showsystray")?.try_into()?, - buttons: get_buttons(&mut v)?, - layouts: get_layouts(&mut v)?, - scratchpadname: String::try_from(get(&mut v, "scratchpadname")?)?, - }) - } -} - -impl Config { - pub fn load(path: impl AsRef) -> Result> { - let s = std::fs::read_to_string(path)?; - let mut f = fig::Fig::new(); - f.variables = fig_env::FIG_ENV.clone(); - f.parse(&s)?; - Self::try_from(f) - } - - #[allow(dependency_on_unit_never_type_fallback, unused)] - pub fn from_lua(path: impl AsRef) -> Result> { - let lua = Lua::new(); - let globals = lua.globals(); + for (k, v) in fig_env::BUTTONS { + globals.set(k, v); + } lua.load(include_str!("config.lua")).eval()?; lua.load(read_to_string(path)?).eval()?; @@ -418,7 +205,7 @@ impl Config { .join("rwm") .join("config.fig"); - Config::load(config_path).unwrap_or_else(|e| { + Config::from_lua(config_path).unwrap_or_else(|e| { log::error!("failed to read config file: {e:?}"); Config::default() }) @@ -639,14 +426,6 @@ mod tests { use super::*; - #[test] - fn load_config() { - let _ = env_logger::try_init(); - let conf = Config::load("example.fig").unwrap(); - - assert_eq!(conf.tags.len(), 9); - } - #[test] fn from_lua() { let got = Config::from_lua("example.lua").unwrap(); diff --git a/src/config/fig_env.rs b/src/config/fig_env.rs index b0e0a4a..1e504f2 100644 --- a/src/config/fig_env.rs +++ b/src/config/fig_env.rs @@ -1,10 +1,7 @@ //! Build an initial Fig environment containing useful symbols for writing //! your config -use std::{collections::HashMap, sync::LazyLock}; - use crate::enums::Clk; -use fig::Value; use x11::xlib::{ Button1, Button2, Button3, ControlMask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, ShiftMask, @@ -15,8 +12,7 @@ use x11::keysym::*; macro_rules! handler_fns { ($($id:ident$(,)*)*) => { [ - $((stringify!($id).into(), - Value::Str(stringify!($id).into())),)* + $((stringify!($id), stringify!($id)),)* ] } } @@ -24,8 +20,7 @@ macro_rules! handler_fns { macro_rules! keys { ($($id:ident$(,)*)*) => { [ - $((stringify!($id).into(), - Value::Int($id as i64)),)* + $((stringify!($id), $id),)* ] } } @@ -35,37 +30,30 @@ macro_rules! keys { macro_rules! clicks { ($($id:ident$(,)*)*) => { [ - $((concat!("Clk", stringify!($id)).into(), - Value::Int(Clk::$id as i64)),)* + $((concat!("Clk", stringify!($id)), Clk::$id as u32),)* ] } } -pub static FIG_ENV: LazyLock> = LazyLock::new( - || { - let handlers = handler_fns! { - focusmon, focusstack, pushstack, incnmaster, killclient, quit, setlayout, setmfact, - spawn, tag, tagmon, togglebar, togglefloating, toggletag, toggleview, - view, zoom, movemouse, resizemouse, tile, monocle, fullscreen, - togglescratch, - }; - let keys = keys! { - Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, - XK_a, XK_b, XK_c, XK_d, XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, XK_k, - XK_l, XK_m, XK_n, XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, - XK_w, XK_x, XK_y, XK_z, XK_0, XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, - XK_7, XK_8, XK_9, XK_Return, XK_Tab, XK_space, XK_comma, XK_period, - XK_grave, ShiftMask, ControlMask, - }; - let clicks = clicks! { - TagBar, LtSymbol, StatusText, WinTitle, ClientWin, RootWin, - }; - let buttons = keys! { Button1, Button2, Button3 }; - let mut ret = HashMap::new(); - ret.extend(handlers); - ret.extend(keys); - ret.extend(clicks); - ret.extend(buttons); - ret - }, -); +pub(super) static HANDLERS: [(&str, &str); 23] = handler_fns! { + focusmon, focusstack, pushstack, incnmaster, killclient, quit, setlayout, setmfact, + spawn, tag, tagmon, togglebar, togglefloating, toggletag, toggleview, + view, zoom, movemouse, resizemouse, tile, monocle, fullscreen, + togglescratch, +}; + +pub(super) static KEYS: [(&str, u32); 48] = keys! { + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, + XK_a, XK_b, XK_c, XK_d, XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, XK_k, + XK_l, XK_m, XK_n, XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, + XK_w, XK_x, XK_y, XK_z, XK_0, XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, + XK_7, XK_8, XK_9, XK_Return, XK_Tab, XK_space, XK_comma, XK_period, + XK_grave, ShiftMask, ControlMask, +}; + +pub(super) static CLICKS: [(&str, u32); 6] = clicks! { + TagBar, LtSymbol, StatusText, WinTitle, ClientWin, RootWin, +}; + +pub(super) static BUTTONS: [(&str, u32); 3] = + keys! { Button1, Button2, Button3 }; diff --git a/src/config/key.rs b/src/config/key.rs index 04527ef..c468428 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, ffi::c_uint, sync::LazyLock}; use crate::{Arg, State}; -use fig::{FigError, Value}; use x11::xlib::KeySym; #[derive(Clone, Debug, serde::Deserialize)] @@ -11,8 +10,13 @@ pub struct KeyFn(pub Option); impl TryFrom for KeyFn { type Error = String; - fn try_from(_value: String) -> Result { - todo!() + fn try_from(value: String) -> Result { + Ok(KeyFn(Some( + FUNC_MAP + .get(value.as_str()) + .cloned() + .ok_or_else(|| format!("no key `{value}`"))?, + ))) } } @@ -38,16 +42,8 @@ impl Key { unsafe impl Sync for Key {} -/// convert an option from Value::as_* into a fig result -pub(crate) fn conv(opt: Option<&T>) -> Result { - match opt { - Some(v) => Ok(v.clone()), - None => Err(FigError::Conversion), - } -} - type FnMap = HashMap<&'static str, fn(&mut State, *const Arg)>; -pub(super) static FUNC_MAP: LazyLock = LazyLock::new(|| { +static FUNC_MAP: LazyLock = LazyLock::new(|| { use crate::key_handlers::*; type FN = fn(&mut State, *const Arg); HashMap::from([ @@ -75,61 +71,3 @@ pub(super) static FUNC_MAP: LazyLock = LazyLock::new(|| { ("resizemouse", resizemouse as FN), ]) }); - -impl TryFrom for Key { - type Error = FigError; - - /// Convert a Value::List of length 4 into a Key. assumes the list entries - /// are mod_, keysym, func (as a string name), and arg as a Map - fn try_from(value: Value) -> Result { - let err = Err(FigError::Conversion); - let Value::List(l) = value else { - log::error!("Key should be a list: {value:?}"); - return err; - }; - if l.len() != 4 { - log::error!("Key list should have 4 fields: {l:?}"); - return err; - } - let func_name = conv(l[2].as_str())?; - let func = conv(FUNC_MAP.get(func_name.as_str()))?; - - let arg = get_arg(conv(l[3].as_map())?)?; - Ok(Self { - mod_: conv(l[0].as_int())? as u32, - keysym: conv(l[1].as_int())? as u64, - func: KeyFn(Some(func)), - arg, - }) - } -} - -/// Try to extract an Arg from a fig::Map -pub(super) fn get_arg(arg: HashMap) -> Result { - let err = Err(FigError::Conversion); - if arg.len() != 1 { - log::error!("Key arg map should have 1 entry: {arg:?}"); - return err; - } - let arg: Vec<(String, Value)> = arg.into_iter().collect(); - let key = arg[0].0.to_lowercase(); - let arg = match key.as_str() { - "i" => Arg::I(conv(arg[0].1.as_int())? as i32), - "ui" => Arg::Ui(conv(arg[0].1.as_int())? as u32), - "f" => Arg::F(conv(arg[0].1.as_float())? as f32), - "v" => { - // the value will be a fig List[Value], so map over the - // Vec and try turning them all into Strings - let v = conv(arg[0].1.as_list())?; - let v: Result, _> = - v.into_iter().map(|v| conv(v.as_str())).collect(); - Arg::V(v?) - } - "l" => Arg::L(arg[0].1.as_int().map(|i| *i as usize)), - _ => { - log::error!("Unrecognized Key arg type: {key:?}"); - return err; - } - }; - Ok(arg) -} diff --git a/src/snapshots/rwm__config__tests__from_lua.snap b/src/snapshots/rwm__config__tests__from_lua.snap index 717bc66..9a982ff 100644 --- a/src/snapshots/rwm__config__tests__from_lua.snap +++ b/src/snapshots/rwm__config__tests__from_lua.snap @@ -40,7 +40,25 @@ Config { ], ], ), - keys: [], + keys: [ + Key { + mod_: 64, + keysym: 112, + func: KeyFn( + Some( + 0x00006459c2d32160, + ), + ), + arg: V( + [ + "dmenu_run", + "-fn", + "monospace:size=10", + "-nb", + ], + ), + }, + ], dmenucmd: [ "dmenu_run", "-fn", From 9f9a349e1e17a9b5f7fcd0f2126355498659953e Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 00:03:07 -0500 Subject: [PATCH 10/27] custom Debug impl for Key for reproducible snapshots --- src/config.rs | 2 +- src/config/key.rs | 15 +++++++++++++-- src/snapshots/rwm__config__tests__from_lua.snap | 6 ++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index 228fb5f..e7a5f3f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -429,6 +429,6 @@ mod tests { #[test] fn from_lua() { let got = Config::from_lua("example.lua").unwrap(); - assert_debug_snapshot!(got); + assert_debug_snapshot!(got) } } diff --git a/src/config/key.rs b/src/config/key.rs index c468428..be12cb3 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ffi::c_uint, sync::LazyLock}; +use std::{collections::HashMap, ffi::c_uint, fmt::Debug, sync::LazyLock}; use crate::{Arg, State}; use x11::xlib::KeySym; @@ -21,7 +21,7 @@ impl TryFrom for KeyFn { } #[repr(C)] -#[derive(Debug, Clone, serde::Deserialize)] +#[derive(Clone, serde::Deserialize)] pub struct Key { pub mod_: c_uint, pub keysym: KeySym, @@ -29,6 +29,17 @@ pub struct Key { pub arg: Arg, } +impl Debug for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Key") + .field("mod_", &self.mod_) + .field("keysym", &self.keysym) + .field("func", &self.func.0.map(|_| "[func]")) + .field("arg", &self.arg) + .finish() + } +} + impl Key { pub const fn new( mod_: c_uint, diff --git a/src/snapshots/rwm__config__tests__from_lua.snap b/src/snapshots/rwm__config__tests__from_lua.snap index 9a982ff..e74f828 100644 --- a/src/snapshots/rwm__config__tests__from_lua.snap +++ b/src/snapshots/rwm__config__tests__from_lua.snap @@ -44,10 +44,8 @@ Config { Key { mod_: 64, keysym: 112, - func: KeyFn( - Some( - 0x00006459c2d32160, - ), + func: Some( + "[func]", ), arg: V( [ From 8de9ed91e3995220d0c5fd2aed7bcca034aa1a74 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 00:29:45 -0500 Subject: [PATCH 11/27] update dmenucmd and test buttons --- src/config.lua | 42 +++++++++++++++---- src/config/key.rs | 2 +- src/lib.rs | 32 ++++++++++++-- .../rwm__config__tests__from_lua.snap | 30 ++++++++++++- 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/src/config.lua b/src/config.lua index eaae866..437dde9 100644 --- a/src/config.lua +++ b/src/config.lua @@ -1,3 +1,4 @@ +-- Constructor functions function key (mod, keysym, func, arg) return { mod_ = mod, @@ -7,8 +8,27 @@ function key (mod, keysym, func, arg) } end +-- Default colors +gray1 = "#222222" +gray2 = "#444444" +gray3 = "#bbbbbb" +gray4 = "#eeeeee" +cyan = "#005577" + dmenufont = "monospace:size=10" -dmenucmd = {"dmenu_run", "-fn", dmenufont, "-nb"} +dmenucmd = { + "dmenu_run", + "-fn", + dmenufont, + "-nb", + gray1, + "-nf", + gray3, + "-sb", + cyan, + "-sf", + gray4, +} modkey = Mod4Mask s_mod = ShiftMask | modkey @@ -24,22 +44,28 @@ rwm = { fonts = {"monospace:size=10"}, tags = {"1", "2", "3", "4", "5", "6", "7", "8", "9"}, colors = { - -- gray3, gray1, gray2 - norm = {"#bbbbbb", "#222222", "#444444"}, - -- gray4, cyan, cyan - sel = {"#eeeeee", "#005577", "#005577"}, + norm = {gray3, gray1, gray2}, + sel = {gray4, cyan, cyan}, }, -- TODO keys = {key(modkey, XK_p, spawn, {V = dmenucmd})}, - dmenucmd = dmenucmd, -- TODO - rules = {}, -- TODO + dmenucmd = dmenucmd, + rules = {}, swallowfloating = false, systraypinning = 0, systrayonleft = false, systrayspacing = 2, systraypinningfailfirst = true, showsystray = true, - buttons = {}, -- TODO + -- TODO + buttons = { + { + click = ClkLtSymbol, + mask = 0, + button = Button1, + func = setlayout, + }, + }, layouts = {}, -- TODO scratchpadname = "scratchpad", } diff --git a/src/config/key.rs b/src/config/key.rs index be12cb3..6bf3861 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -54,7 +54,7 @@ impl Key { unsafe impl Sync for Key {} type FnMap = HashMap<&'static str, fn(&mut State, *const Arg)>; -static FUNC_MAP: LazyLock = LazyLock::new(|| { +pub(crate) static FUNC_MAP: LazyLock = LazyLock::new(|| { use crate::key_handlers::*; type FN = fn(&mut State, *const Arg); HashMap::from([ diff --git a/src/lib.rs b/src/lib.rs index 66ff088..490bedf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,11 @@ #![allow(clippy::missing_safety_doc, clippy::not_unsafe_ptr_arg_deref)] -use std::ffi::{c_int, c_uint}; +use std::{ + ffi::{c_int, c_uint}, + fmt::Debug, +}; +use config::key::FUNC_MAP; use enums::Clk; use x11::xft::XftColor; @@ -66,15 +70,28 @@ impl Arg { } } -#[derive(Clone, Debug, serde::Deserialize)] +#[derive(Clone, serde::Deserialize)] #[serde(try_from = "String")] pub struct ButtonFn(pub Option); impl TryFrom for ButtonFn { type Error = String; - fn try_from(_value: String) -> Result { - todo!() + fn try_from(value: String) -> Result { + Ok(Self(Some( + FUNC_MAP + .get(value.as_str()) + .cloned() + .ok_or_else(|| format!("no key `{value}`"))?, + ))) + } +} + +impl Debug for ButtonFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ButtonFn") + .field(&self.0.map(|_| "[func]")) + .finish() } } @@ -85,9 +102,16 @@ pub struct Button { pub mask: c_uint, pub button: c_uint, pub func: ButtonFn, + #[serde(default = "default_button_arg")] pub arg: Arg, } +/// Hack to get around `{L = nil}` equating to an empty table in Lua. If the +/// table's empty, treat it as the only optional Arg variant +fn default_button_arg() -> Arg { + Arg::L(None) +} + impl Button { pub const fn new( click: Clk, diff --git a/src/snapshots/rwm__config__tests__from_lua.snap b/src/snapshots/rwm__config__tests__from_lua.snap index e74f828..2d0f1e2 100644 --- a/src/snapshots/rwm__config__tests__from_lua.snap +++ b/src/snapshots/rwm__config__tests__from_lua.snap @@ -53,6 +53,13 @@ Config { "-fn", "monospace:size=10", "-nb", + "#222222", + "-nf", + "#bbbbbb", + "-sb", + "#005577", + "-sf", + "#eeeeee", ], ), }, @@ -62,6 +69,13 @@ Config { "-fn", "monospace:size=10", "-nb", + "#222222", + "-nf", + "#bbbbbb", + "-sb", + "#005577", + "-sf", + "#eeeeee", ], rules: [], swallowfloating: false, @@ -70,7 +84,21 @@ Config { systrayspacing: 2, systraypinningfailfirst: true, showsystray: true, - buttons: [], + buttons: [ + Button { + click: 1, + mask: 0, + button: 1, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: L( + None, + ), + }, + ], layouts: [], scratchpadname: "scratchpad", } From 3d3660d4bf7688203201ea013a9f3f030674b84a Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 00:36:18 -0500 Subject: [PATCH 12/27] add layouts --- src/config.lua | 6 ++++- src/config.rs | 6 ++--- src/handlers.rs | 1 - src/key_handlers.rs | 6 ----- src/lib.rs | 22 ++++++++++++++---- src/main_.rs | 16 ++++++------- .../rwm__config__tests__from_lua.snap | 23 ++++++++++++++++++- src/tests.rs | 1 - 8 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/config.lua b/src/config.lua index 437dde9..b297c56 100644 --- a/src/config.lua +++ b/src/config.lua @@ -66,6 +66,10 @@ rwm = { func = setlayout, }, }, - layouts = {}, -- TODO + layouts = { + {symbol = "[]=", arrange = tile }, + {symbol = "><>", arrange = nil }, + {symbol = "[M]", arrange = monocle }, + }, scratchpadname = "scratchpad", } diff --git a/src/config.rs b/src/config.rs index e7a5f3f..8832ef1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -283,9 +283,9 @@ static RULES: LazyLock<[Rule; 3]> = LazyLock::new(|| { static LAYOUTS: LazyLock<[Layout; 3]> = LazyLock::new(|| { [ - Layout { symbol: "[]=".to_string(), arrange: LayoutFn(Some(tile)) }, - Layout { symbol: "><>".to_string(), arrange: LayoutFn(None) }, - Layout { symbol: "[M]".to_string(), arrange: LayoutFn(Some(monocle)) }, + Layout { symbol: "[]=".to_string(), arrange: Some(LayoutFn(tile)) }, + Layout { symbol: "><>".to_string(), arrange: None }, + Layout { symbol: "[M]".to_string(), arrange: Some(LayoutFn(monocle)) }, ] }); diff --git a/src/handlers.rs b/src/handlers.rs index fb151a8..7ea7912 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -291,7 +291,6 @@ pub(crate) fn configurerequest(state: &mut State, e: *mut XEvent) { } else if (*c).isfloating || (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_none() { let m = (*c).mon; diff --git a/src/key_handlers.rs b/src/key_handlers.rs index 52a8512..dad87f3 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -118,7 +118,6 @@ pub(crate) fn setmfact(state: &mut State, arg: *const Arg) { if arg.is_null() || (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_none() { return; @@ -146,7 +145,6 @@ pub(crate) fn zoom(state: &mut State, _arg: *const Arg) { let mut c = (*state.selmon).sel; if (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_none() || c.is_null() || (*c).isfloating @@ -571,7 +569,6 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { if !c.isfloating && (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_some() && ((nx - c.x).abs() > state.config.snap as c_int || (ny - c.y).abs() > state.config.snap as c_int) @@ -580,7 +577,6 @@ pub(crate) fn movemouse(state: &mut State, _arg: *const Arg) { } if (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_none() || c.isfloating { @@ -673,7 +669,6 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { && !c.isfloating && (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_some() && ((nw - c.w).abs() > state.config.snap as c_int || (nh - c.h).abs() > state.config.snap as c_int) @@ -682,7 +677,6 @@ pub(crate) fn resizemouse(state: &mut State, _arg: *const Arg) { } if (*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_none() || c.isfloating { diff --git a/src/lib.rs b/src/lib.rs index 490bedf..df45f59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ use std::{ use config::key::FUNC_MAP; use enums::Clk; +use layouts::{monocle, tile}; use x11::xft::XftColor; pub mod config; @@ -154,15 +155,25 @@ pub struct Systray { pub icons: *mut Client, } -#[derive(Debug, Clone, serde::Deserialize)] +#[derive(Clone, serde::Deserialize)] #[serde(try_from = "String")] -pub struct LayoutFn(pub Option); +pub struct LayoutFn(pub fn(&mut State, *mut Monitor)); impl TryFrom for LayoutFn { type Error = String; - fn try_from(_value: String) -> Result { - todo!() + fn try_from(value: String) -> Result { + match value.as_str() { + "tile" => Ok(Self(tile)), + "monocle" => Ok(Self(monocle)), + s => Err(format!("unknown layout `{s}`")), + } + } +} + +impl Debug for LayoutFn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("LayoutFn").field(&"[func]").finish() } } @@ -170,7 +181,8 @@ impl TryFrom for LayoutFn { #[derive(Debug, Clone, serde::Deserialize)] pub struct Layout { pub symbol: String, - pub arrange: LayoutFn, + #[serde(default)] + pub arrange: Option, } #[derive(Clone, Debug)] diff --git a/src/main_.rs b/src/main_.rs index c39cf7d..97bc96a 100644 --- a/src/main_.rs +++ b/src/main_.rs @@ -15,8 +15,8 @@ use crate::xembed::{ XEMBED_WINDOW_DEACTIVATE, }; use crate::{ - drw, handlers, Arg, Client, Layout, LayoutFn, Monitor, Pertag, State, - Systray, Window, ICONIC_STATE, NORMAL_STATE, WITHDRAWN_STATE, + drw, handlers, Arg, Client, Layout, Monitor, Pertag, State, Systray, + Window, ICONIC_STATE, NORMAL_STATE, WITHDRAWN_STATE, }; use libc::{c_long, c_uchar, pid_t, sigaction}; use x11::keysym::XK_Num_Lock; @@ -621,9 +621,9 @@ pub fn arrangemon(state: &mut State, m: *mut Monitor) { log::trace!("arrangemon"); unsafe { (*m).ltsymbol = (*(*m).lt[(*m).sellt as usize]).symbol.clone(); - let arrange = (*(*m).lt[(*m).sellt as usize]).arrange.0; + let arrange = &(*(*m).lt[(*m).sellt as usize]).arrange; if let Some(arrange) = arrange { - (arrange)(state, m); + (arrange.0)(state, m); } } } @@ -636,11 +636,11 @@ pub fn restack(state: &mut State, m: *mut Monitor) { return; } if (*(*m).sel).isfloating - || (*(*m).lt[(*m).sellt as usize]).arrange.0.is_none() + || (*(*m).lt[(*m).sellt as usize]).arrange.is_none() { xlib::XRaiseWindow(state.dpy, (*(*m).sel).win); } - if (*(*m).lt[(*m).sellt as usize]).arrange.0.is_some() { + if (*(*m).lt[(*m).sellt as usize]).arrange.is_some() { let mut wc = xlib::XWindowChanges { stack_mode: Below, sibling: (*m).barwin, @@ -681,7 +681,6 @@ pub fn showhide(state: &mut State, c: *mut Client) { xlib::XMoveWindow(state.dpy, (*c).win, (*c).x, (*c).y); if ((*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) .arrange - .0 .is_none() || (*c).isfloating) && !(*c).isfullscreen @@ -851,7 +850,6 @@ pub fn applysizehints( || (*c).isfloating || (*(*(*c).mon).lt[(*(*c).mon).sellt as usize]) .arrange - .0 .is_none() { if (*c).hintsvalid == 0 { @@ -2091,7 +2089,7 @@ pub fn cleanup(mut state: State) { let a = Arg::Ui(!0); view(&mut state, &a); (*state.selmon).lt[(*state.selmon).sellt as usize] = - &Layout { symbol: String::new(), arrange: LayoutFn(None) }; + &Layout { symbol: String::new(), arrange: None }; let mut m = state.mons; while !m.is_null() { diff --git a/src/snapshots/rwm__config__tests__from_lua.snap b/src/snapshots/rwm__config__tests__from_lua.snap index 2d0f1e2..4afbaf0 100644 --- a/src/snapshots/rwm__config__tests__from_lua.snap +++ b/src/snapshots/rwm__config__tests__from_lua.snap @@ -99,6 +99,27 @@ Config { ), }, ], - layouts: [], + layouts: [ + Layout { + symbol: "[]=", + arrange: Some( + LayoutFn( + "[func]", + ), + ), + }, + Layout { + symbol: "><>", + arrange: None, + }, + Layout { + symbol: "[M]", + arrange: Some( + LayoutFn( + "[func]", + ), + ), + }, + ], scratchpadname: "scratchpad", } diff --git a/src/tests.rs b/src/tests.rs index 40ae2e7..cc58582 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -57,7 +57,6 @@ fn main() { unsafe { assert!((*(*state.selmon).lt[(*state.selmon).sellt as usize]) .arrange - .0 .is_none()); } From ce021aa1488d3e43aeea69d4f865c3738891e204 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 00:57:01 -0500 Subject: [PATCH 13/27] install lua --- .github/workflows/check.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9a98add..2655f93 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -37,7 +37,7 @@ jobs: - name: Install X libraries run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev + sudo apt-get install libxft-dev libxinerama-dev lua - name: Install ${{ matrix.toolchain }} uses: dtolnay/rust-toolchain@master with: @@ -58,7 +58,7 @@ jobs: - name: Install X libraries run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev + sudo apt-get install libxft-dev libxinerama-dev lua - name: Install nightly uses: dtolnay/rust-toolchain@nightly - name: cargo doc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d9e8e8..e4284d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,11 +24,11 @@ jobs: if: ${{ ! matrix.is_mac }} run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev xserver-xephyr libxcb1-dev + sudo apt-get install libxft-dev libxinerama-dev xserver-xephyr libxcb1-dev lua - name: Install X libraries (mac) if: ${{ matrix.is_mac }} run: | - brew install libxft libxinerama + brew install libxft libxinerama lua brew install --cask xquartz echo "PATH=$PATH:/opt/X11/bin" >> $GITHUB_ENV - name: Install ${{ matrix.toolchain }} From 5ace45a73f38a1cee4b7bddbae6c39660532a502 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 00:57:59 -0500 Subject: [PATCH 14/27] ubuntu requires specifying lua5.4 --- .github/workflows/check.yml | 4 ++-- .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2655f93..fbe6f63 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -37,7 +37,7 @@ jobs: - name: Install X libraries run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev lua + sudo apt-get install libxft-dev libxinerama-dev lua5.4 - name: Install ${{ matrix.toolchain }} uses: dtolnay/rust-toolchain@master with: @@ -58,7 +58,7 @@ jobs: - name: Install X libraries run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev lua + sudo apt-get install libxft-dev libxinerama-dev lua5.4 - name: Install nightly uses: dtolnay/rust-toolchain@nightly - name: cargo doc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e4284d6..2765378 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: if: ${{ ! matrix.is_mac }} run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev xserver-xephyr libxcb1-dev lua + sudo apt-get install libxft-dev libxinerama-dev xserver-xephyr libxcb1-dev lua5.4 - name: Install X libraries (mac) if: ${{ matrix.is_mac }} run: | From e3936438ccdb27488c1096f32179116ca7aa2e04 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 01:00:25 -0500 Subject: [PATCH 15/27] also install liblua5.4 --- .github/workflows/check.yml | 4 ++-- .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index fbe6f63..031100e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -37,7 +37,7 @@ jobs: - name: Install X libraries run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev lua5.4 + sudo apt-get install libxft-dev libxinerama-dev lua5.4 liblua5.4-dev - name: Install ${{ matrix.toolchain }} uses: dtolnay/rust-toolchain@master with: @@ -58,7 +58,7 @@ jobs: - name: Install X libraries run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev lua5.4 + sudo apt-get install libxft-dev libxinerama-dev lua5.4 liblua5.4-dev - name: Install nightly uses: dtolnay/rust-toolchain@nightly - name: cargo doc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2765378..305e6ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: if: ${{ ! matrix.is_mac }} run: | sudo apt-get update - sudo apt-get install libxft-dev libxinerama-dev xserver-xephyr libxcb1-dev lua5.4 + sudo apt-get install libxft-dev libxinerama-dev xserver-xephyr libxcb1-dev lua5.4 liblua5.4-dev - name: Install X libraries (mac) if: ${{ matrix.is_mac }} run: | From 0aa6bf4a77df6d6d3f7195a7d28661e33bf50f5b Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 12:43:24 -0500 Subject: [PATCH 16/27] rename main_ to core --- src/{main_.rs => core.rs} | 0 src/key_handlers.rs | 4 ++-- src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/{main_.rs => core.rs} (100%) diff --git a/src/main_.rs b/src/core.rs similarity index 100% rename from src/main_.rs rename to src/core.rs diff --git a/src/key_handlers.rs b/src/key_handlers.rs index dad87f3..59e339b 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -12,13 +12,13 @@ use x11::xlib::{ XUngrabServer, XWarpPointer, XWindowChanges, CWY, }; -use crate::enums::WM; -use crate::main_::{ +use crate::core::{ arrange, attach, attachstack, detach, detachstack, drawbar, focus, getrootptr, height, is_visible, nexttiled, pop, recttomon, resize, resizebarwin, restack, sendevent, setfullscreen, unfocus, updatebarpos, width, xerror, xerrordummy, HANDLER, MOUSEMASK, XNONE, }; +use crate::enums::WM; use crate::{cfor, State}; use crate::{Arg, Client, Monitor}; diff --git a/src/lib.rs b/src/lib.rs index df45f59..e979098 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,8 @@ pub mod layouts; pub mod util; pub mod xembed; -pub use main_::*; -mod main_; +pub use core::*; +mod core; pub use state::*; mod state; From 306841a0c20cb54f6b414fbaada04e56088ad034 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Thu, 12 Dec 2024 01:21:10 -0500 Subject: [PATCH 17/27] start generating tag keys --- src/config.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/config.lua b/src/config.lua index b297c56..ad1c85d 100644 --- a/src/config.lua +++ b/src/config.lua @@ -8,6 +8,16 @@ function key (mod, keysym, func, arg) } end +function tagkeys (keys) + for i, xk in ipairs({XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9}) do + arg = {Ui = 1 << (i - 1)} + table.insert(keys, key(modkey, xk, view, arg)) + table.insert(keys, key(modkey|ControlMask, xk, toggleview, arg)) + table.insert(keys, key(s_mod, xk, tag, arg)) + table.insert(keys, key(s_mod|ControlMask, xk, toggletag, arg)) + end +end + -- Default colors gray1 = "#222222" gray2 = "#444444" @@ -32,6 +42,12 @@ dmenucmd = { modkey = Mod4Mask s_mod = ShiftMask | modkey +-- TODO +keys = { + key(modkey, XK_p, spawn, {V = dmenucmd}), +} +tagkeys(keys) + rwm = { borderpx = 3, snap = 32, @@ -47,9 +63,9 @@ rwm = { norm = {gray3, gray1, gray2}, sel = {gray4, cyan, cyan}, }, - -- TODO - keys = {key(modkey, XK_p, spawn, {V = dmenucmd})}, + keys = keys, dmenucmd = dmenucmd, + -- TODO rules = {}, swallowfloating = false, systraypinning = 0, From d8e7d5d3d14edeb5c2395ae82fca77fcf7215c8d Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 13:16:55 -0500 Subject: [PATCH 18/27] finish the default config --- src/config.lua | 84 +- src/config/key.rs | 1 + .../rwm__config__tests__from_lua.snap | 785 +++++++++++++++++- 3 files changed, 859 insertions(+), 11 deletions(-) diff --git a/src/config.lua b/src/config.lua index ad1c85d..f33997b 100644 --- a/src/config.lua +++ b/src/config.lua @@ -8,6 +8,29 @@ function key (mod, keysym, func, arg) } end +function button (click, mask, button, func, arg) + return { + click = click, + mask = mask, + button = button, + func = func, + arg = arg, + } +end + +function rule (class, instance, title, tags, isfloating, isterminal, noswallow, monitor) + return { + class = class, + instance = instance, + title = title, + tags = tags, + isfloating = isfloating, + isterminal = isterminal, + noswallow = noswallow, + monitor = monitor, + } +end + function tagkeys (keys) for i, xk in ipairs({XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9}) do arg = {Ui = 1 << (i - 1)} @@ -39,12 +62,48 @@ dmenucmd = { "-sf", gray4, } + +termcmd = {"st", "-e", "zsh"} + +scratchpadname = "scratchpad" +scratchpadcmd = {"st", "-t", scratchpadname, "-g", "120x34"} + modkey = Mod4Mask s_mod = ShiftMask | modkey --- TODO +-- Lua doesn't have true integers, so ~0 gives -1. We're just trying to get all +-- of the bits in a u32 set, so this works too. math.floor is required for the +-- deserializer to detect this as an int +all_tags = math.floor(2^32 - 1) + keys = { key(modkey, XK_p, spawn, {V = dmenucmd}), + key(s_mod, XK_Return, spawn, {V = termcmd}), + key(modkey, XK_grave, togglescratch, {V = scratchpadcmd}), + key(modkey, XK_b, togglebar, {I = 0}), + key(modkey, XK_j, focusstack, {I = 1}), + key(modkey, XK_k, focusstack, {I = -1}), + key(s_mod, XK_j, pushstack, {I = 1}), + key(s_mod, XK_k, pushstack, {I = -1}), + key(modkey, XK_i, incnmaster, {I = 1}), + key(modkey, XK_d, incnmaster, {I = -1}), + key(modkey, XK_h, setmfact, {F = -0.05}), + key(modkey, XK_l, setmfact, {F = 0.05}), + key(modkey, XK_Return, zoom, {I = 0}), + key(modkey, XK_Tab, view, {Ui = 0}), + key(s_mod, XK_c, killclient, {I = 0}), + key(modkey, XK_t, setlayout, {L = 0}), + key(modkey, XK_f, setlayout, {L = 1}), + key(modkey, XK_m, setlayout, {L = 2}), + key(modkey, XK_space, setlayout), + key(s_mod, XK_space, togglefloating, {I = 0}), + key(modkey, XK_0, view, {Ui = all_tags}), + key(s_mod, XK_0, tag, {Ui = all_tags}), + key(modkey, XK_comma, focusmon, {I = -1}), + key(modkey, XK_period, focusmon, {I = 1}), + key(s_mod, XK_comma, tagmon, {I = -1}), + key(s_mod, XK_period, tagmon, {I = 1}), + key(s_mod, XK_q, quit, {I = 0}), } tagkeys(keys) @@ -65,22 +124,27 @@ rwm = { }, keys = keys, dmenucmd = dmenucmd, - -- TODO - rules = {}, + rules = { + rule("st-256color", "", "", 0, false, true, false, -1), + }, swallowfloating = false, systraypinning = 0, systrayonleft = false, systrayspacing = 2, systraypinningfailfirst = true, showsystray = true, - -- TODO buttons = { - { - click = ClkLtSymbol, - mask = 0, - button = Button1, - func = setlayout, - }, + button(ClkLtSymbol, 0, Button1, setlayout), + button(ClkLtSymbol, 0, Button3, setlayout, {L = 2}), + button(ClkWinTitle, 0, Button2, zoom, {I = 0}), + button(ClkStatusText, 0, Button2, spawn, {V = termcmd}), + button(ClkClientWin, modkey, Button1, movemouse, {I = 0}), + button(ClkClientWin, modkey, Button2, togglefloating, {I = 0}), + button(ClkClientWin, modkey, Button3, resizemouse, {I = 0}), + button(ClkTagBar, 0, Button1, view, {I = 0}), + button(ClkTagBar, 0, Button3, toggleview, {I = 0}), + button(ClkTagBar, modkey, Button1, tag, {I = 0}), + button(ClkTagBar, modkey, Button3, toggletag, {I = 0}), }, layouts = { {symbol = "[]=", arrange = tile }, diff --git a/src/config/key.rs b/src/config/key.rs index 6bf3861..1b29c5e 100644 --- a/src/config/key.rs +++ b/src/config/key.rs @@ -26,6 +26,7 @@ pub struct Key { pub mod_: c_uint, pub keysym: KeySym, pub func: KeyFn, + #[serde(default = "crate::default_button_arg")] pub arg: Arg, } diff --git a/src/snapshots/rwm__config__tests__from_lua.snap b/src/snapshots/rwm__config__tests__from_lua.snap index 4afbaf0..a2a85d7 100644 --- a/src/snapshots/rwm__config__tests__from_lua.snap +++ b/src/snapshots/rwm__config__tests__from_lua.snap @@ -63,6 +63,642 @@ Config { ], ), }, + Key { + mod_: 65, + keysym: 65293, + func: Some( + "[func]", + ), + arg: V( + [ + "st", + "-e", + "zsh", + ], + ), + }, + Key { + mod_: 64, + keysym: 96, + func: Some( + "[func]", + ), + arg: V( + [ + "st", + "-t", + "scratchpad", + "-g", + "120x34", + ], + ), + }, + Key { + mod_: 64, + keysym: 98, + func: Some( + "[func]", + ), + arg: I( + 0, + ), + }, + Key { + mod_: 64, + keysym: 106, + func: Some( + "[func]", + ), + arg: I( + 1, + ), + }, + Key { + mod_: 64, + keysym: 107, + func: Some( + "[func]", + ), + arg: I( + -1, + ), + }, + Key { + mod_: 65, + keysym: 106, + func: Some( + "[func]", + ), + arg: I( + 1, + ), + }, + Key { + mod_: 65, + keysym: 107, + func: Some( + "[func]", + ), + arg: I( + -1, + ), + }, + Key { + mod_: 64, + keysym: 105, + func: Some( + "[func]", + ), + arg: I( + 1, + ), + }, + Key { + mod_: 64, + keysym: 100, + func: Some( + "[func]", + ), + arg: I( + -1, + ), + }, + Key { + mod_: 64, + keysym: 104, + func: Some( + "[func]", + ), + arg: F( + -0.05, + ), + }, + Key { + mod_: 64, + keysym: 108, + func: Some( + "[func]", + ), + arg: F( + 0.05, + ), + }, + Key { + mod_: 64, + keysym: 65293, + func: Some( + "[func]", + ), + arg: I( + 0, + ), + }, + Key { + mod_: 64, + keysym: 65289, + func: Some( + "[func]", + ), + arg: Ui( + 0, + ), + }, + Key { + mod_: 65, + keysym: 99, + func: Some( + "[func]", + ), + arg: I( + 0, + ), + }, + Key { + mod_: 64, + keysym: 116, + func: Some( + "[func]", + ), + arg: L( + Some( + 0, + ), + ), + }, + Key { + mod_: 64, + keysym: 102, + func: Some( + "[func]", + ), + arg: L( + Some( + 1, + ), + ), + }, + Key { + mod_: 64, + keysym: 109, + func: Some( + "[func]", + ), + arg: L( + Some( + 2, + ), + ), + }, + Key { + mod_: 64, + keysym: 32, + func: Some( + "[func]", + ), + arg: L( + None, + ), + }, + Key { + mod_: 65, + keysym: 32, + func: Some( + "[func]", + ), + arg: I( + 0, + ), + }, + Key { + mod_: 64, + keysym: 48, + func: Some( + "[func]", + ), + arg: Ui( + 4294967295, + ), + }, + Key { + mod_: 65, + keysym: 48, + func: Some( + "[func]", + ), + arg: Ui( + 4294967295, + ), + }, + Key { + mod_: 64, + keysym: 44, + func: Some( + "[func]", + ), + arg: I( + -1, + ), + }, + Key { + mod_: 64, + keysym: 46, + func: Some( + "[func]", + ), + arg: I( + 1, + ), + }, + Key { + mod_: 65, + keysym: 44, + func: Some( + "[func]", + ), + arg: I( + -1, + ), + }, + Key { + mod_: 65, + keysym: 46, + func: Some( + "[func]", + ), + arg: I( + 1, + ), + }, + Key { + mod_: 65, + keysym: 113, + func: Some( + "[func]", + ), + arg: I( + 0, + ), + }, + Key { + mod_: 64, + keysym: 49, + func: Some( + "[func]", + ), + arg: Ui( + 1, + ), + }, + Key { + mod_: 68, + keysym: 49, + func: Some( + "[func]", + ), + arg: Ui( + 1, + ), + }, + Key { + mod_: 65, + keysym: 49, + func: Some( + "[func]", + ), + arg: Ui( + 1, + ), + }, + Key { + mod_: 69, + keysym: 49, + func: Some( + "[func]", + ), + arg: Ui( + 1, + ), + }, + Key { + mod_: 64, + keysym: 50, + func: Some( + "[func]", + ), + arg: Ui( + 2, + ), + }, + Key { + mod_: 68, + keysym: 50, + func: Some( + "[func]", + ), + arg: Ui( + 2, + ), + }, + Key { + mod_: 65, + keysym: 50, + func: Some( + "[func]", + ), + arg: Ui( + 2, + ), + }, + Key { + mod_: 69, + keysym: 50, + func: Some( + "[func]", + ), + arg: Ui( + 2, + ), + }, + Key { + mod_: 64, + keysym: 51, + func: Some( + "[func]", + ), + arg: Ui( + 4, + ), + }, + Key { + mod_: 68, + keysym: 51, + func: Some( + "[func]", + ), + arg: Ui( + 4, + ), + }, + Key { + mod_: 65, + keysym: 51, + func: Some( + "[func]", + ), + arg: Ui( + 4, + ), + }, + Key { + mod_: 69, + keysym: 51, + func: Some( + "[func]", + ), + arg: Ui( + 4, + ), + }, + Key { + mod_: 64, + keysym: 52, + func: Some( + "[func]", + ), + arg: Ui( + 8, + ), + }, + Key { + mod_: 68, + keysym: 52, + func: Some( + "[func]", + ), + arg: Ui( + 8, + ), + }, + Key { + mod_: 65, + keysym: 52, + func: Some( + "[func]", + ), + arg: Ui( + 8, + ), + }, + Key { + mod_: 69, + keysym: 52, + func: Some( + "[func]", + ), + arg: Ui( + 8, + ), + }, + Key { + mod_: 64, + keysym: 53, + func: Some( + "[func]", + ), + arg: Ui( + 16, + ), + }, + Key { + mod_: 68, + keysym: 53, + func: Some( + "[func]", + ), + arg: Ui( + 16, + ), + }, + Key { + mod_: 65, + keysym: 53, + func: Some( + "[func]", + ), + arg: Ui( + 16, + ), + }, + Key { + mod_: 69, + keysym: 53, + func: Some( + "[func]", + ), + arg: Ui( + 16, + ), + }, + Key { + mod_: 64, + keysym: 54, + func: Some( + "[func]", + ), + arg: Ui( + 32, + ), + }, + Key { + mod_: 68, + keysym: 54, + func: Some( + "[func]", + ), + arg: Ui( + 32, + ), + }, + Key { + mod_: 65, + keysym: 54, + func: Some( + "[func]", + ), + arg: Ui( + 32, + ), + }, + Key { + mod_: 69, + keysym: 54, + func: Some( + "[func]", + ), + arg: Ui( + 32, + ), + }, + Key { + mod_: 64, + keysym: 55, + func: Some( + "[func]", + ), + arg: Ui( + 64, + ), + }, + Key { + mod_: 68, + keysym: 55, + func: Some( + "[func]", + ), + arg: Ui( + 64, + ), + }, + Key { + mod_: 65, + keysym: 55, + func: Some( + "[func]", + ), + arg: Ui( + 64, + ), + }, + Key { + mod_: 69, + keysym: 55, + func: Some( + "[func]", + ), + arg: Ui( + 64, + ), + }, + Key { + mod_: 64, + keysym: 56, + func: Some( + "[func]", + ), + arg: Ui( + 128, + ), + }, + Key { + mod_: 68, + keysym: 56, + func: Some( + "[func]", + ), + arg: Ui( + 128, + ), + }, + Key { + mod_: 65, + keysym: 56, + func: Some( + "[func]", + ), + arg: Ui( + 128, + ), + }, + Key { + mod_: 69, + keysym: 56, + func: Some( + "[func]", + ), + arg: Ui( + 128, + ), + }, + Key { + mod_: 64, + keysym: 57, + func: Some( + "[func]", + ), + arg: Ui( + 256, + ), + }, + Key { + mod_: 68, + keysym: 57, + func: Some( + "[func]", + ), + arg: Ui( + 256, + ), + }, + Key { + mod_: 65, + keysym: 57, + func: Some( + "[func]", + ), + arg: Ui( + 256, + ), + }, + Key { + mod_: 69, + keysym: 57, + func: Some( + "[func]", + ), + arg: Ui( + 256, + ), + }, ], dmenucmd: [ "dmenu_run", @@ -77,7 +713,18 @@ Config { "-sf", "#eeeeee", ], - rules: [], + rules: [ + Rule { + class: "st-256color", + instance: "", + title: "", + tags: 0, + isfloating: false, + isterminal: true, + noswallow: false, + monitor: -1, + }, + ], swallowfloating: false, systraypinning: 0, systrayonleft: false, @@ -98,6 +745,142 @@ Config { None, ), }, + Button { + click: 1, + mask: 0, + button: 3, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: L( + Some( + 2, + ), + ), + }, + Button { + click: 3, + mask: 0, + button: 2, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 2, + mask: 0, + button: 2, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: V( + [ + "st", + "-e", + "zsh", + ], + ), + }, + Button { + click: 4, + mask: 64, + button: 1, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 4, + mask: 64, + button: 2, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 4, + mask: 64, + button: 3, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 0, + mask: 0, + button: 1, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 0, + mask: 0, + button: 3, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 0, + mask: 64, + button: 1, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, + Button { + click: 0, + mask: 64, + button: 3, + func: ButtonFn( + Some( + "[func]", + ), + ), + arg: I( + 0, + ), + }, ], layouts: [ Layout { From 8abfb41b8807efe69c831ce18b32f8e0b69ae62a Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 13:42:16 -0500 Subject: [PATCH 19/27] move docs to config.lua --- src/config.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/config.lua b/src/config.lua index f33997b..04ef32b 100644 --- a/src/config.lua +++ b/src/config.lua @@ -31,6 +31,15 @@ function rule (class, instance, title, tags, isfloating, isterminal, noswallow, } end +-- For each tag key (N = 1, 2, ...) you generally want to set four bindings: +-- +-- * MOD+N to view the tag +-- * MOD+Ctrl+N to toggle viewing the tag +-- * MOD+Shift+N to send a window to the tag +-- * MOD+Shift+Control+N to toggle the display of a window on a tag +-- +-- This function inserts each of those into `keys` for the default tag keys XK_1 +-- through XK_9. function tagkeys (keys) for i, xk in ipairs({XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9}) do arg = {Ui = 1 << (i - 1)} @@ -127,11 +136,18 @@ rwm = { rules = { rule("st-256color", "", "", 0, false, true, false, -1), }, + -- Whether to swallow floating windows swallowfloating = false, + -- If 0, sloppy systray follows monitor, otherwise pin systray to monitor n systraypinning = 0, + -- Pin the systray to the left size of the bar systrayonleft = false, + -- Spacing in pixels between icons in the systray systrayspacing = 2, + -- If pinning the systray to the active monitor fails, display it on first + -- monitor. If `false`, display it instead on the last monitor systraypinningfailfirst = true, + -- Show the systray showsystray = true, buttons = { button(ClkLtSymbol, 0, Button1, setlayout), From 81f3191bb29fea40d2252de997690eaf15f59249 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 13:42:21 -0500 Subject: [PATCH 20/27] take defaults entirely from lua --- src/config.rs | 337 ++++++++------------------------------------------ 1 file changed, 54 insertions(+), 283 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8832ef1..99929b2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,91 +4,45 @@ use std::{ ffi::{c_float, c_int, c_uint, CString}, fs::read_to_string, path::Path, - sync::LazyLock, }; use fig_env::{CLICKS, HANDLERS, KEYS}; -use mlua::{Lua, LuaSerdeExt as _}; -use x11::keysym::{ - XK_Return, XK_Tab, XK_b, XK_c, XK_comma, XK_d, XK_f, XK_grave, XK_h, XK_i, - XK_j, XK_k, XK_l, XK_m, XK_p, XK_period, XK_q, XK_space, XK_t, XK_0, XK_1, - XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, -}; - -use x11::xlib::{Button1, Button2, Button3, ControlMask, Mod4Mask, ShiftMask}; +use mlua::{Lua, LuaSerdeExt as _, Table}; -use crate::{ - config::key::Key, - enums::{Clk, Scheme}, - key_handlers::*, - layouts::{monocle, tile}, -}; -use crate::{Arg, Button, Layout, LayoutFn, Rule}; +use crate::{config::key::Key, enums::Scheme, Button, Layout, Rule}; mod fig_env; pub mod key; -impl Default for Config { - fn default() -> Self { - Self { - borderpx: 3, - snap: 32, - showbar: true, - topbar: true, - mfact: 0.5, - nmaster: 1, - resize_hints: false, - lock_fullscreen: true, - fonts: vec![c"monospace:size=10".into()], - tags: ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - .map(String::from) - .to_vec(), - colors: ColorMap(default_colors()), - keys: default_keys().to_vec(), - dmenucmd: DMENUCMD.to_vec(), - rules: RULES.to_vec(), - swallowfloating: SWALLOWFLOATING, - systraypinning: SYSTRAYPINNING, - systrayonleft: SYSTRAYONLEFT, - systrayspacing: SYSTRAYSPACING, - systraypinningfailfirst: SYSTRAYPINNINGFAILFIRST, - showsystray: SHOWSYSTRAY, - buttons: BUTTONS.to_vec(), - layouts: LAYOUTS.to_vec(), - scratchpadname: SCRATCHPADNAME.to_string(), - } - } -} - #[derive(Debug, serde::Deserialize)] #[serde(try_from = "HashMap>")] -pub struct ColorMap(pub [[CString; 3]; 2]); +pub struct ColorMap(pub Vec>); impl TryFrom>> for ColorMap { type Error = Box; fn try_from( - value: HashMap>, + mut value: HashMap>, ) -> Result { - let mut ret = default_colors(); - let norm = value.get("norm").ok_or("missing key norm")?; - let sel = value.get("sel").ok_or("missing key sel")?; - let [n0, n1, n2] = &norm[..] else { + let mut ret = vec![vec![], vec![]]; + let norm = value.remove("norm").ok_or("missing key norm")?; + let sel = value.remove("sel").ok_or("missing key sel")?; + + if norm.len() != 3 { return Err("not enough colors for SchemeNorm".into()); }; - ret[Scheme::Norm as usize] = [ - CString::new(n0.clone())?, - CString::new(n1.clone())?, - CString::new(n2.clone())?, - ]; - let [s0, s1, s2] = &sel[..] else { + ret[Scheme::Norm as usize] = norm + .into_iter() + .map(CString::new) + .collect::>()?; + + if sel.len() != 3 { return Err("not enough colors for SchemeSel".into()); }; - ret[Scheme::Sel as usize] = [ - CString::new(s0.clone())?, - CString::new(s1.clone())?, - CString::new(s2.clone())?, - ]; + ret[Scheme::Sel as usize] = sel + .into_iter() + .map(CString::new) + .collect::>()?; Ok(Self(ret)) } @@ -158,35 +112,60 @@ pub struct Config { unsafe impl Send for Config {} unsafe impl Sync for Config {} -impl Config { - #[allow(dependency_on_unit_never_type_fallback, unused)] - pub fn from_lua(path: impl AsRef) -> Result> { +struct ConfigBuilder { + lua: Lua, + globals: Table, +} + +impl ConfigBuilder { + fn new() -> Self { let lua = Lua::new(); let globals = lua.globals(); // install handler functions for keybindings for (k, v) in HANDLERS { - globals.set(k, v); + globals.set(k, v).unwrap(); } // install key definitions for (k, v) in KEYS { - globals.set(k, v); + globals.set(k, v).unwrap(); } // install click and button definitions for (k, v) in CLICKS { - globals.set(k, v); + globals.set(k, v).unwrap(); } for (k, v) in fig_env::BUTTONS { - globals.set(k, v); + globals.set(k, v).unwrap(); } - lua.load(include_str!("config.lua")).eval()?; - lua.load(read_to_string(path)?).eval()?; + lua.load(include_str!("config.lua")).exec().unwrap(); + + Self { lua, globals } + } + + /// Load and eval `path` into the Lua interpreter in `self`. + fn load(self, path: impl AsRef) -> Result> { + self.lua.load(read_to_string(path)?).exec()?; + Ok(self) + } - Ok(lua.from_value(globals.get("rwm")?)?) + fn finish(self) -> Result> { + Ok(self.lua.from_value(self.globals.get("rwm")?)?) + } +} + +impl Default for Config { + fn default() -> Self { + ConfigBuilder::new().finish().unwrap() + } +} + +impl Config { + pub fn from_lua(path: impl AsRef) -> Result> { + ConfigBuilder::new().load(path)?.finish() } /// Attempt to load a config file on first usage from `$XDG_CONFIG_HOME`, @@ -212,214 +191,6 @@ impl Config { } } -// appearance - -/// Swallow floating windows by default -const SWALLOWFLOATING: bool = false; - -/// 0: sloppy systray follows selected monitor, >0: pin systray to monitor x -static SYSTRAYPINNING: c_uint = 0; -const SYSTRAYONLEFT: bool = false; -/// systray spacing -const SYSTRAYSPACING: c_uint = 2; -/// if pinning fails and this is true, display systray on the first monitor, -/// else display systray on the last monitor -const SYSTRAYPINNINGFAILFIRST: bool = true; -const SHOWSYSTRAY: bool = true; - -const COL_GRAY1: &str = "#222222"; -const COL_GRAY2: &str = "#444444"; -const COL_GRAY3: &str = "#bbbbbb"; -const COL_GRAY4: &str = "#eeeeee"; -const COL_CYAN: &str = "#005577"; - -fn default_colors() -> [[CString; 3]; 2] { - let mut ret = std::array::from_fn(|_| { - std::array::from_fn(|_| CString::new("").unwrap()) - }); - ret[Scheme::Norm as usize] = - [COL_GRAY3, COL_GRAY1, COL_GRAY2].map(|s| CString::new(s).unwrap()); - ret[Scheme::Sel as usize] = - [COL_GRAY4, COL_CYAN, COL_CYAN].map(|s| CString::new(s).unwrap()); - ret -} - -static RULES: LazyLock<[Rule; 3]> = LazyLock::new(|| { - [ - Rule { - class: "Gimp".into(), - instance: String::new(), - title: String::new(), - tags: 0, - isfloating: true, - isterminal: false, - noswallow: false, - monitor: -1, - }, - Rule { - class: "Firefox".into(), - instance: String::new(), - title: String::new(), - tags: 1 << 8, - isfloating: false, - isterminal: false, - noswallow: true, - monitor: -1, - }, - Rule { - class: "st-256color".into(), - instance: String::new(), - title: String::new(), - tags: 0, - isfloating: false, - isterminal: true, - noswallow: false, - monitor: -1, - }, - ] -}); - -// layouts - -static LAYOUTS: LazyLock<[Layout; 3]> = LazyLock::new(|| { - [ - Layout { symbol: "[]=".to_string(), arrange: Some(LayoutFn(tile)) }, - Layout { symbol: "><>".to_string(), arrange: None }, - Layout { symbol: "[M]".to_string(), arrange: Some(LayoutFn(monocle)) }, - ] -}); - -// key definitions -const MODKEY: c_uint = Mod4Mask; - -// commands - -const S_MOD: c_uint = MODKEY | ShiftMask; - -static DMENUFONT: &str = "monospace:size=10"; -static DMENUCMD: LazyLock> = LazyLock::new(|| { - vec![ - "dmenu_run".into(), - "-fn".into(), - DMENUFONT.into(), - "-nb".into(), - COL_GRAY1.into(), - "-nf".into(), - COL_GRAY3.into(), - "-sb".into(), - COL_CYAN.into(), - "-sf".into(), - COL_GRAY4.into(), - ] -}); -static TERMCMD: LazyLock> = LazyLock::new(|| vec!["st".into()]); - -const SCRATCHPADNAME: &str = "scratchpad"; -static SCRATCHPADCMD: LazyLock> = LazyLock::new(|| { - vec![ - "st".into(), - "-t".into(), - SCRATCHPADNAME.to_string(), - "-g".into(), - "120x34".into(), - ] -}); - -fn default_keys() -> [Key; 61] { - [ - Key::new(MODKEY, XK_p, spawn, Arg::V(DMENUCMD.clone())), - Key::new(S_MOD, XK_Return, spawn, Arg::V(TERMCMD.to_vec())), - Key::new( - MODKEY, - XK_grave, - togglescratch, - Arg::V(SCRATCHPADCMD.to_vec()), - ), - Key::new(MODKEY, XK_b, togglebar, Arg::I(0)), - Key::new(MODKEY, XK_j, focusstack, Arg::I(1)), - Key::new(MODKEY, XK_k, focusstack, Arg::I(-1)), - Key::new(MODKEY, XK_i, incnmaster, Arg::I(1)), - Key::new(MODKEY, XK_d, incnmaster, Arg::I(-1)), - Key::new(MODKEY, XK_h, setmfact, Arg::F(-0.05)), - Key::new(MODKEY, XK_l, setmfact, Arg::F(0.05)), - Key::new(MODKEY, XK_Return, zoom, Arg::I(0)), - Key::new(MODKEY, XK_Tab, view, Arg::Ui(0)), - Key::new(S_MOD, XK_c, killclient, Arg::I(0)), - Key::new(MODKEY, XK_t, setlayout, Arg::L(Some(0))), - Key::new(MODKEY, XK_f, setlayout, Arg::L(Some(1))), - Key::new(MODKEY, XK_m, setlayout, Arg::L(Some(2))), - Key::new(MODKEY, XK_space, setlayout, Arg::L(None)), - Key::new(S_MOD, XK_space, togglefloating, Arg::I(0)), - Key::new(MODKEY, XK_0, view, Arg::Ui(!0)), - Key::new(S_MOD, XK_0, tag, Arg::Ui(!0)), - Key::new(MODKEY, XK_comma, focusmon, Arg::I(-1)), - Key::new(MODKEY, XK_period, focusmon, Arg::I(1)), - Key::new(S_MOD, XK_comma, tagmon, Arg::I(-1)), - Key::new(S_MOD, XK_period, tagmon, Arg::I(1)), - Key::new(MODKEY, XK_1, view, Arg::Ui(1 << 0)), - Key::new(MODKEY | ControlMask, XK_1, toggleview, Arg::Ui(1 << 0)), - Key::new(S_MOD, XK_1, tag, Arg::Ui(1 << 0)), - Key::new(S_MOD | ControlMask, XK_1, toggletag, Arg::Ui(1 << 0)), - Key::new(MODKEY, XK_2, view, Arg::Ui(1 << 1)), - Key::new(MODKEY | ControlMask, XK_2, toggleview, Arg::Ui(1 << 1)), - Key::new(S_MOD, XK_2, tag, Arg::Ui(1 << 1)), - Key::new(S_MOD | ControlMask, XK_2, toggletag, Arg::Ui(1 << 1)), - Key::new(MODKEY, XK_3, view, Arg::Ui(1 << 2)), - Key::new(MODKEY | ControlMask, XK_3, toggleview, Arg::Ui(1 << 2)), - Key::new(S_MOD, XK_3, tag, Arg::Ui(1 << 2)), - Key::new(S_MOD | ControlMask, XK_3, toggletag, Arg::Ui(1 << 2)), - Key::new(MODKEY, XK_4, view, Arg::Ui(1 << 3)), - Key::new(MODKEY | ControlMask, XK_4, toggleview, Arg::Ui(1 << 3)), - Key::new(S_MOD, XK_4, tag, Arg::Ui(1 << 3)), - Key::new(S_MOD | ControlMask, XK_4, toggletag, Arg::Ui(1 << 3)), - Key::new(MODKEY, XK_5, view, Arg::Ui(1 << 4)), - Key::new(MODKEY | ControlMask, XK_5, toggleview, Arg::Ui(1 << 4)), - Key::new(S_MOD, XK_5, tag, Arg::Ui(1 << 4)), - Key::new(S_MOD | ControlMask, XK_5, toggletag, Arg::Ui(1 << 4)), - Key::new(MODKEY, XK_6, view, Arg::Ui(1 << 5)), - Key::new(MODKEY | ControlMask, XK_6, toggleview, Arg::Ui(1 << 5)), - Key::new(S_MOD, XK_6, tag, Arg::Ui(1 << 5)), - Key::new(S_MOD | ControlMask, XK_6, toggletag, Arg::Ui(1 << 5)), - Key::new(MODKEY, XK_7, view, Arg::Ui(1 << 6)), - Key::new(MODKEY | ControlMask, XK_7, toggleview, Arg::Ui(1 << 6)), - Key::new(S_MOD, XK_7, tag, Arg::Ui(1 << 6)), - Key::new(S_MOD | ControlMask, XK_7, toggletag, Arg::Ui(1 << 6)), - Key::new(MODKEY, XK_8, view, Arg::Ui(1 << 7)), - Key::new(MODKEY | ControlMask, XK_8, toggleview, Arg::Ui(1 << 7)), - Key::new(S_MOD, XK_8, tag, Arg::Ui(1 << 7)), - Key::new(S_MOD | ControlMask, XK_8, toggletag, Arg::Ui(1 << 7)), - Key::new(MODKEY, XK_9, view, Arg::Ui(1 << 8)), - Key::new(MODKEY | ControlMask, XK_9, toggleview, Arg::Ui(1 << 8)), - Key::new(S_MOD, XK_9, tag, Arg::Ui(1 << 8)), - Key::new(S_MOD | ControlMask, XK_9, toggletag, Arg::Ui(1 << 8)), - Key::new(S_MOD, XK_q, quit, Arg::I(0)), - ] -} - -// button definitions - -static BUTTONS: LazyLock<[Button; 11]> = LazyLock::new(|| { - [ - Button::new(Clk::LtSymbol, 0, Button1, setlayout, Arg::L(None)), - Button::new(Clk::LtSymbol, 0, Button3, setlayout, Arg::L(Some(2))), - Button::new(Clk::WinTitle, 0, Button2, zoom, Arg::I(0)), - Button::new( - Clk::StatusText, - 0, - Button2, - spawn, - Arg::V(TERMCMD.to_vec()), - ), - Button::new(Clk::ClientWin, MODKEY, Button1, movemouse, Arg::I(0)), - Button::new(Clk::ClientWin, MODKEY, Button2, togglefloating, Arg::I(0)), - Button::new(Clk::ClientWin, MODKEY, Button3, resizemouse, Arg::I(0)), - Button::new(Clk::TagBar, 0, Button1, view, Arg::I(0)), - Button::new(Clk::TagBar, 0, Button3, toggleview, Arg::I(0)), - Button::new(Clk::TagBar, MODKEY, Button1, tag, Arg::I(0)), - Button::new(Clk::TagBar, MODKEY, Button3, toggletag, Arg::I(0)), - ] -}); - #[cfg(test)] mod tests { use insta::assert_debug_snapshot; From d0f3a0680953d1f4cbb5d18d0d2ce6c5f72aa247 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 13:44:49 -0500 Subject: [PATCH 21/27] update config path --- src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 99929b2..41bc3af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -182,7 +182,7 @@ impl Config { let config_path = Path::new(&home.unwrap()) .join(".config") .join("rwm") - .join("config.fig"); + .join("config.lua"); Config::from_lua(config_path).unwrap_or_else(|e| { log::error!("failed to read config file: {e:?}"); From c6c69000c0ae0d91db91fe1d230aa920fb8617d7 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 13:44:59 -0500 Subject: [PATCH 22/27] don't load config twice --- src/core.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.rs b/src/core.rs index 97bc96a..3c4b3f8 100644 --- a/src/core.rs +++ b/src/core.rs @@ -259,7 +259,7 @@ pub fn setup(dpy: *mut Display) -> State { numlockmask: 0, running: true, systray: None, - config: Config::load_home(), + config, #[cfg(target_os = "linux")] xcon: null_mut(), From 0b3b5f09ca7fead96dbb6669e1620d8bc994f301 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sat, 14 Dec 2024 16:50:11 -0500 Subject: [PATCH 23/27] use previously-defined scratchpad name --- src/config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.lua b/src/config.lua index 04ef32b..98b3e56 100644 --- a/src/config.lua +++ b/src/config.lua @@ -167,5 +167,5 @@ rwm = { {symbol = "><>", arrange = nil }, {symbol = "[M]", arrange = monocle }, }, - scratchpadname = "scratchpad", + scratchpadname = scratchpadname, } From 25c6c3a611943dc404a411cf1d632471cc797eb4 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sun, 19 Jan 2025 17:34:16 -0500 Subject: [PATCH 24/27] make way more keys available in the default config --- scripts/keysyms.py | 1017 +++++++++++++++++++++++++++++++++++++++++ src/config.rs | 6 +- src/config/fig_env.rs | 255 ++++++++++- 3 files changed, 1266 insertions(+), 12 deletions(-) create mode 100644 scripts/keysyms.py diff --git a/scripts/keysyms.py b/scripts/keysyms.py new file mode 100644 index 0000000..87b71c6 --- /dev/null +++ b/scripts/keysyms.py @@ -0,0 +1,1017 @@ +"""Generate a table of X keysyms for inclusion in the Lua config file +environment. This should be included in `fig_env.rs` to take advantage of the +`use x11::keysym::*` and `keys` macros that are used in the generated output. +""" + +import textwrap +from io import StringIO + +# these are present in my keysymdef.h but not in the x11 crate, so filter them +# out +SKIP = { + "XK_3270_AltCursor", + "XK_3270_Attn", + "XK_3270_BackTab", + "XK_3270_ChangeScreen", + "XK_3270_Copy", + "XK_3270_CursorBlink", + "XK_3270_CursorSelect", + "XK_3270_DeleteWord", + "XK_3270_Duplicate", + "XK_3270_Enter", + "XK_3270_EraseEOF", + "XK_3270_EraseInput", + "XK_3270_ExSelect", + "XK_3270_FieldMark", + "XK_3270_Ident", + "XK_3270_Jump", + "XK_3270_KeyClick", + "XK_3270_Left2", + "XK_3270_PA1", + "XK_3270_PA2", + "XK_3270_PA3", + "XK_3270_Play", + "XK_3270_PrintScreen", + "XK_3270_Quit", + "XK_3270_Record", + "XK_3270_Reset", + "XK_3270_Right2", + "XK_3270_Rule", + "XK_3270_Setup", + "XK_3270_Test", + "XK_Abelowdot", + "XK_Abreveacute", + "XK_Abrevebelowdot", + "XK_Abrevegrave", + "XK_Abrevehook", + "XK_Abrevetilde", + "XK_Acircumflexacute", + "XK_Acircumflexbelowdot", + "XK_Acircumflexgrave", + "XK_Acircumflexhook", + "XK_Acircumflextilde", + "XK_Ahook", + "XK_Arabic_0", + "XK_Arabic_1", + "XK_Arabic_2", + "XK_Arabic_3", + "XK_Arabic_4", + "XK_Arabic_5", + "XK_Arabic_6", + "XK_Arabic_7", + "XK_Arabic_8", + "XK_Arabic_9", + "XK_Arabic_ddal", + "XK_Arabic_farsi_yeh", + "XK_Arabic_fullstop", + "XK_Arabic_gaf", + "XK_Arabic_hamza_above", + "XK_Arabic_hamza_below", + "XK_Arabic_heh_doachashmee", + "XK_Arabic_heh_goal", + "XK_Arabic_jeh", + "XK_Arabic_keheh", + "XK_Arabic_madda_above", + "XK_Arabic_noon_ghunna", + "XK_Arabic_peh", + "XK_Arabic_percent", + "XK_Arabic_rreh", + "XK_Arabic_superscript_alef", + "XK_Arabic_tcheh", + "XK_Arabic_tteh", + "XK_Arabic_veh", + "XK_Arabic_yeh_baree", + "XK_Armenian_AT", + "XK_Armenian_AYB", + "XK_Armenian_BEN", + "XK_Armenian_CHA", + "XK_Armenian_DA", + "XK_Armenian_DZA", + "XK_Armenian_E", + "XK_Armenian_FE", + "XK_Armenian_GHAT", + "XK_Armenian_GIM", + "XK_Armenian_HI", + "XK_Armenian_HO", + "XK_Armenian_INI", + "XK_Armenian_JE", + "XK_Armenian_KEN", + "XK_Armenian_KE", + "XK_Armenian_KHE", + "XK_Armenian_LYUN", + "XK_Armenian_MEN", + "XK_Armenian_NU", + "XK_Armenian_O", + "XK_Armenian_PE", + "XK_Armenian_PYUR", + "XK_Armenian_RA", + "XK_Armenian_RE", + "XK_Armenian_SE", + "XK_Armenian_SHA", + "XK_Armenian_TCHE", + "XK_Armenian_TO", + "XK_Armenian_TSA", + "XK_Armenian_TSO", + "XK_Armenian_TYUN", + "XK_Armenian_VEV", + "XK_Armenian_VO", + "XK_Armenian_VYUN", + "XK_Armenian_YECH", + "XK_Armenian_ZA", + "XK_Armenian_ZHE", + "XK_Armenian_accent", + "XK_Armenian_amanak", + "XK_Armenian_apostrophe", + "XK_Armenian_at", + "XK_Armenian_ayb", + "XK_Armenian_ben", + "XK_Armenian_but", + "XK_Armenian_cha", + "XK_Armenian_da", + "XK_Armenian_dza", + "XK_Armenian_e", + "XK_Armenian_exclam", + "XK_Armenian_fe", + "XK_Armenian_full_stop", + "XK_Armenian_ghat", + "XK_Armenian_gim", + "XK_Armenian_hi", + "XK_Armenian_ho", + "XK_Armenian_hyphen", + "XK_Armenian_ini", + "XK_Armenian_je", + "XK_Armenian_ke", + "XK_Armenian_ken", + "XK_Armenian_khe", + "XK_Armenian_ligature_ew", + "XK_Armenian_lyun", + "XK_Armenian_men", + "XK_Armenian_nu", + "XK_Armenian_o", + "XK_Armenian_paruyk", + "XK_Armenian_pe", + "XK_Armenian_pyur", + "XK_Armenian_question", + "XK_Armenian_ra", + "XK_Armenian_re", + "XK_Armenian_se", + "XK_Armenian_separation_mark", + "XK_Armenian_sha", + "XK_Armenian_shesht", + "XK_Armenian_tche", + "XK_Armenian_to", + "XK_Armenian_tsa", + "XK_Armenian_tso", + "XK_Armenian_tyun", + "XK_Armenian_verjaket", + "XK_Armenian_vev", + "XK_Armenian_vo", + "XK_Armenian_vyun", + "XK_Armenian_yech", + "XK_Armenian_yentamna", + "XK_Armenian_za", + "XK_Armenian_zhe", + "XK_Babovedot", + "XK_Codeinput", + "XK_ColonSign", + "XK_CruzeiroSign", + "XK_Cyrillic_CHE_descender", + "XK_Cyrillic_CHE_vertstroke", + "XK_Cyrillic_EN_descender", + "XK_Cyrillic_GHE_bar", + "XK_Cyrillic_HA_descender", + "XK_Cyrillic_I_macron", + "XK_Cyrillic_KA_descender", + "XK_Cyrillic_KA_vertstroke", + "XK_Cyrillic_O_bar", + "XK_Cyrillic_SCHWA", + "XK_Cyrillic_SHHA", + "XK_Cyrillic_U_macron", + "XK_Cyrillic_U_straight_bar", + "XK_Cyrillic_U_straight", + "XK_Cyrillic_ZHE_descender", + "XK_Cyrillic_che_descender", + "XK_Cyrillic_che_vertstroke", + "XK_Cyrillic_en_descender", + "XK_Cyrillic_ghe_bar", + "XK_Cyrillic_ha_descender", + "XK_Cyrillic_i_macron", + "XK_Cyrillic_ka_descender", + "XK_Cyrillic_ka_vertstroke", + "XK_Cyrillic_o_bar", + "XK_Cyrillic_schwa", + "XK_Cyrillic_shha", + "XK_Cyrillic_u_macron", + "XK_Cyrillic_u_straight_bar", + "XK_Cyrillic_u_straight", + "XK_Cyrillic_zhe_descender", + "XK_Dabovedot", + "XK_DongSign", + "XK_EZH", + "XK_Ebelowdot", + "XK_Ecircumflexacute", + "XK_Ecircumflexbelowdot", + "XK_Ecircumflexgrave", + "XK_Ecircumflexhook", + "XK_Ecircumflextilde", + "XK_EcuSign", + "XK_Ehook", + "XK_Etilde", + "XK_EuroSign", + "XK_FFrancSign", + "XK_Fabovedot", + "XK_Farsi_0", + "XK_Farsi_1", + "XK_Farsi_2", + "XK_Farsi_3", + "XK_Farsi_4", + "XK_Farsi_5", + "XK_Farsi_6", + "XK_Farsi_7", + "XK_Farsi_8", + "XK_Farsi_9", + "XK_Farsi_yeh", + "XK_Gcaron", + "XK_Georgian_an", + "XK_Georgian_ban", + "XK_Georgian_can", + "XK_Georgian_char", + "XK_Georgian_chin", + "XK_Georgian_cil", + "XK_Georgian_don", + "XK_Georgian_en", + "XK_Georgian_fi", + "XK_Georgian_gan", + "XK_Georgian_ghan", + "XK_Georgian_hae", + "XK_Georgian_har", + "XK_Georgian_he", + "XK_Georgian_hie", + "XK_Georgian_hoe", + "XK_Georgian_in", + "XK_Georgian_jhan", + "XK_Georgian_jil", + "XK_Georgian_kan", + "XK_Georgian_khar", + "XK_Georgian_las", + "XK_Georgian_man", + "XK_Georgian_nar", + "XK_Georgian_on", + "XK_Georgian_par", + "XK_Georgian_phar", + "XK_Georgian_qar", + "XK_Georgian_rae", + "XK_Georgian_san", + "XK_Georgian_shin", + "XK_Georgian_tan", + "XK_Georgian_tar", + "XK_Georgian_un", + "XK_Georgian_vin", + "XK_Georgian_we", + "XK_Georgian_xan", + "XK_Georgian_zen", + "XK_Georgian_zhar", + "XK_Greek_IOTAdieresis", + "XK_Hangul_AE", + "XK_Hangul_A", + "XK_Hangul_AraeAE", + "XK_Hangul_AraeA", + "XK_Hangul_Banja", + "XK_Hangul_Cieuc", + "XK_Hangul_Codeinput", + "XK_Hangul_Dikeud", + "XK_Hangul_EO", + "XK_Hangul_EU", + "XK_Hangul_E", + "XK_Hangul_End", + "XK_Hangul_Hanja", + "XK_Hangul_Hieuh", + "XK_Hangul_I", + "XK_Hangul_Ieung", + "XK_Hangul_J_Cieuc", + "XK_Hangul_J_Dikeud", + "XK_Hangul_J_Hieuh", + "XK_Hangul_J_Ieung", + "XK_Hangul_J_Jieuj", + "XK_Hangul_J_Khieuq", + "XK_Hangul_J_KiyeogSios", + "XK_Hangul_J_Kiyeog", + "XK_Hangul_J_KkogjiDalrinIeung", + "XK_Hangul_J_Mieum", + "XK_Hangul_J_NieunHieuh", + "XK_Hangul_J_NieunJieuj", + "XK_Hangul_J_Nieun", + "XK_Hangul_J_PanSios", + "XK_Hangul_J_Phieuf", + "XK_Hangul_J_PieubSios", + "XK_Hangul_J_Pieub", + "XK_Hangul_J_RieulHieuh", + "XK_Hangul_J_RieulKiyeog", + "XK_Hangul_J_RieulMieum", + "XK_Hangul_J_RieulPhieuf", + "XK_Hangul_J_RieulPieub", + "XK_Hangul_J_RieulSios", + "XK_Hangul_J_RieulTieut", + "XK_Hangul_J_Rieul", + "XK_Hangul_J_Sios", + "XK_Hangul_J_SsangKiyeog", + "XK_Hangul_J_SsangSios", + "XK_Hangul_J_Tieut", + "XK_Hangul_J_YeorinHieuh", + "XK_Hangul_Jamo", + "XK_Hangul_Jeonja", + "XK_Hangul_Jieuj", + "XK_Hangul_Khieuq", + "XK_Hangul_KiyeogSios", + "XK_Hangul_Kiyeog", + "XK_Hangul_KkogjiDalrinIeung", + "XK_Hangul_Mieum", + "XK_Hangul_MultipleCandidate", + "XK_Hangul_NieunHieuh", + "XK_Hangul_NieunJieuj", + "XK_Hangul_Nieun", + "XK_Hangul_OE", + "XK_Hangul_O", + "XK_Hangul_PanSios", + "XK_Hangul_Phieuf", + "XK_Hangul_PieubSios", + "XK_Hangul_Pieub", + "XK_Hangul_PostHanja", + "XK_Hangul_PreHanja", + "XK_Hangul_PreviousCandidate", + "XK_Hangul_RieulHieuh", + "XK_Hangul_RieulKiyeog", + "XK_Hangul_RieulMieum", + "XK_Hangul_RieulPhieuf", + "XK_Hangul_RieulPieub", + "XK_Hangul_RieulSios", + "XK_Hangul_RieulTieut", + "XK_Hangul_RieulYeorinHieuh", + "XK_Hangul_Rieul", + "XK_Hangul_Romaja", + "XK_Hangul_SingleCandidate", + "XK_Hangul_Sios", + "XK_Hangul_Special", + "XK_Hangul_SsangDikeud", + "XK_Hangul_SsangJieuj", + "XK_Hangul_SsangKiyeog", + "XK_Hangul_SsangPieub", + "XK_Hangul_SsangSios", + "XK_Hangul_Start", + "XK_Hangul_SunkyeongeumMieum", + "XK_Hangul_SunkyeongeumPhieuf", + "XK_Hangul_SunkyeongeumPieub", + "XK_Hangul_Tieut", + "XK_Hangul_U", + "XK_Hangul_WAE", + "XK_Hangul_WA", + "XK_Hangul_WEO", + "XK_Hangul_WE", + "XK_Hangul_WI", + "XK_Hangul_YAE", + "XK_Hangul_YA", + "XK_Hangul_YEO", + "XK_Hangul_YE", + "XK_Hangul_YI", + "XK_Hangul_YO", + "XK_Hangul_YU", + "XK_Hangul_YeorinHieuh", + "XK_Hangul_switch", + "XK_Hangul", + "XK_Ibelowdot", + "XK_Ibreve", + "XK_Ihook", + "XK_Kanji_Bangou", + "XK_Korean_Won", + "XK_Lbelowdot", + "XK_LiraSign", + "XK_Mabovedot", + "XK_Mae_Koho", + "XK_MillSign", + "XK_MultipleCandidate", + "XK_NairaSign", + "XK_NewSheqelSign", + "XK_OE", + "XK_Obarred", + "XK_Obelowdot", + "XK_Ocaron", + "XK_Ocircumflexacute", + "XK_Ocircumflexbelowdot", + "XK_Ocircumflexgrave", + "XK_Ocircumflexhook", + "XK_Ocircumflextilde", + "XK_Ohook", + "XK_Ohorn", + "XK_Ohornacute", + "XK_Ohornbelowdot", + "XK_Ohorngrave", + "XK_Ohornhook", + "XK_Ohorntilde", + "XK_Oslash", + "XK_Pabovedot", + "XK_PesetaSign", + "XK_PreviousCandidate", + "XK_RupeeSign", + "XK_SCHWA", + "XK_Sabovedot", + "XK_SingleCandidate", + "XK_Sinh_a", + "XK_Sinh_aa2", + "XK_Sinh_aa", + "XK_Sinh_ae2", + "XK_Sinh_ae", + "XK_Sinh_aee2", + "XK_Sinh_aee", + "XK_Sinh_ai2", + "XK_Sinh_ai", + "XK_Sinh_al", + "XK_Sinh_au2", + "XK_Sinh_au", + "XK_Sinh_ba", + "XK_Sinh_bha", + "XK_Sinh_ca", + "XK_Sinh_cha", + "XK_Sinh_dda", + "XK_Sinh_ddha", + "XK_Sinh_dha", + "XK_Sinh_dhha", + "XK_Sinh_e2", + "XK_Sinh_e", + "XK_Sinh_ee2", + "XK_Sinh_ee", + "XK_Sinh_fa", + "XK_Sinh_ga", + "XK_Sinh_gha", + "XK_Sinh_h2", + "XK_Sinh_ha", + "XK_Sinh_i2", + "XK_Sinh_i", + "XK_Sinh_ii2", + "XK_Sinh_ii", + "XK_Sinh_ja", + "XK_Sinh_jha", + "XK_Sinh_jnya", + "XK_Sinh_ka", + "XK_Sinh_kha", + "XK_Sinh_kunddaliya", + "XK_Sinh_la", + "XK_Sinh_lla", + "XK_Sinh_lu2", + "XK_Sinh_lu", + "XK_Sinh_luu2", + "XK_Sinh_luu", + "XK_Sinh_ma", + "XK_Sinh_mba", + "XK_Sinh_na", + "XK_Sinh_ndda", + "XK_Sinh_ndha", + "XK_Sinh_ng2", + "XK_Sinh_ng", + "XK_Sinh_nga", + "XK_Sinh_nja", + "XK_Sinh_nna", + "XK_Sinh_nya", + "XK_Sinh_o2", + "XK_Sinh_o", + "XK_Sinh_oo2", + "XK_Sinh_oo", + "XK_Sinh_pa", + "XK_Sinh_pha", + "XK_Sinh_ra", + "XK_Sinh_ri", + "XK_Sinh_rii", + "XK_Sinh_ru2", + "XK_Sinh_ruu2", + "XK_Sinh_sa", + "XK_Sinh_sha", + "XK_Sinh_ssha", + "XK_Sinh_tha", + "XK_Sinh_thha", + "XK_Sinh_tta", + "XK_Sinh_ttha", + "XK_Sinh_u2", + "XK_Sinh_u", + "XK_Sinh_uu2", + "XK_Sinh_uu", + "XK_Sinh_va", + "XK_Sinh_ya", + "XK_Tabovedot", + "XK_Thai_baht", + "XK_Thai_bobaimai", + "XK_Thai_chochan", + "XK_Thai_chochang", + "XK_Thai_choching", + "XK_Thai_chochoe", + "XK_Thai_dochada", + "XK_Thai_dodek", + "XK_Thai_fofa", + "XK_Thai_fofan", + "XK_Thai_hohip", + "XK_Thai_honokhuk", + "XK_Thai_khokhai", + "XK_Thai_khokhon", + "XK_Thai_khokhuat", + "XK_Thai_khokhwai", + "XK_Thai_khorakhang", + "XK_Thai_kokai", + "XK_Thai_lakkhangyao", + "XK_Thai_lekchet", + "XK_Thai_lekha", + "XK_Thai_lekhok", + "XK_Thai_lekkao", + "XK_Thai_leknung", + "XK_Thai_lekpaet", + "XK_Thai_leksam", + "XK_Thai_leksi", + "XK_Thai_leksong", + "XK_Thai_leksun", + "XK_Thai_lochula", + "XK_Thai_loling", + "XK_Thai_lu", + "XK_Thai_maichattawa", + "XK_Thai_maiek", + "XK_Thai_maihanakat_maitho", + "XK_Thai_maihanakat", + "XK_Thai_maitaikhu", + "XK_Thai_maitho", + "XK_Thai_maitri", + "XK_Thai_maiyamok", + "XK_Thai_moma", + "XK_Thai_ngongu", + "XK_Thai_nikhahit", + "XK_Thai_nonen", + "XK_Thai_nonu", + "XK_Thai_oang", + "XK_Thai_paiyannoi", + "XK_Thai_phinthu", + "XK_Thai_phophan", + "XK_Thai_phophung", + "XK_Thai_phosamphao", + "XK_Thai_popla", + "XK_Thai_rorua", + "XK_Thai_ru", + "XK_Thai_saraa", + "XK_Thai_saraaa", + "XK_Thai_saraae", + "XK_Thai_saraaimaimalai", + "XK_Thai_saraaimaimuan", + "XK_Thai_saraam", + "XK_Thai_sarae", + "XK_Thai_sarai", + "XK_Thai_saraii", + "XK_Thai_sarao", + "XK_Thai_sarau", + "XK_Thai_saraue", + "XK_Thai_sarauee", + "XK_Thai_sarauu", + "XK_Thai_sorusi", + "XK_Thai_sosala", + "XK_Thai_soso", + "XK_Thai_sosua", + "XK_Thai_thanthakhat", + "XK_Thai_thonangmontho", + "XK_Thai_thophuthao", + "XK_Thai_thothahan", + "XK_Thai_thothan", + "XK_Thai_thothong", + "XK_Thai_thothung", + "XK_Thai_topatak", + "XK_Thai_totao", + "XK_Thai_wowaen", + "XK_Thai_yoyak", + "XK_Thai_yoying", + "XK_Ubelowdot", + "XK_Uhook", + "XK_Uhorn", + "XK_Uhornacute", + "XK_Uhornbelowdot", + "XK_Uhorngrave", + "XK_Uhornhook", + "XK_Uhorntilde", + "XK_Ukrainian_GHE_WITH_UPTURN", + "XK_Ukrainian_ghe_with_upturn", + "XK_VoidSymbol", + "XK_Wacute", + "XK_Wcircumflex", + "XK_Wdiaeresis", + "XK_Wgrave", + "XK_WonSign", + "XK_Xabovedot", + "XK_Ybelowdot", + "XK_Ycircumflex", + "XK_Ydiaeresis", + "XK_Ygrave", + "XK_Yhook", + "XK_Ytilde", + "XK_Zen_Koho", + "XK_Zstroke", + "XK_abelowdot", + "XK_abreveacute", + "XK_abrevebelowdot", + "XK_abrevegrave", + "XK_abrevehook", + "XK_abrevetilde", + "XK_acircumflexacute", + "XK_acircumflexbelowdot", + "XK_acircumflexgrave", + "XK_acircumflexhook", + "XK_acircumflextilde", + "XK_ahook", + "XK_approxeq", + "XK_babovedot", + "XK_because", + "XK_braille_blank", + "XK_braille_dot_10", + "XK_braille_dot_1", + "XK_braille_dot_2", + "XK_braille_dot_3", + "XK_braille_dot_4", + "XK_braille_dot_5", + "XK_braille_dot_6", + "XK_braille_dot_7", + "XK_braille_dot_8", + "XK_braille_dot_9", + "XK_braille_dots_12345678", + "XK_braille_dots_1234567", + "XK_braille_dots_1234568", + "XK_braille_dots_123456", + "XK_braille_dots_1234578", + "XK_braille_dots_123457", + "XK_braille_dots_123458", + "XK_braille_dots_12345", + "XK_braille_dots_1234678", + "XK_braille_dots_123467", + "XK_braille_dots_123468", + "XK_braille_dots_12346", + "XK_braille_dots_123478", + "XK_braille_dots_12347", + "XK_braille_dots_12348", + "XK_braille_dots_1234", + "XK_braille_dots_1235678", + "XK_braille_dots_123567", + "XK_braille_dots_123568", + "XK_braille_dots_12356", + "XK_braille_dots_123578", + "XK_braille_dots_12357", + "XK_braille_dots_12358", + "XK_braille_dots_1235", + "XK_braille_dots_123678", + "XK_braille_dots_12367", + "XK_braille_dots_12368", + "XK_braille_dots_1236", + "XK_braille_dots_12378", + "XK_braille_dots_1237", + "XK_braille_dots_1238", + "XK_braille_dots_123", + "XK_braille_dots_1245678", + "XK_braille_dots_124567", + "XK_braille_dots_124568", + "XK_braille_dots_12456", + "XK_braille_dots_124578", + "XK_braille_dots_12457", + "XK_braille_dots_12458", + "XK_braille_dots_1245", + "XK_braille_dots_124678", + "XK_braille_dots_12467", + "XK_braille_dots_12468", + "XK_braille_dots_1246", + "XK_braille_dots_12478", + "XK_braille_dots_1247", + "XK_braille_dots_1248", + "XK_braille_dots_124", + "XK_braille_dots_125678", + "XK_braille_dots_12567", + "XK_braille_dots_12568", + "XK_braille_dots_1256", + "XK_braille_dots_12578", + "XK_braille_dots_1257", + "XK_braille_dots_1258", + "XK_braille_dots_125", + "XK_braille_dots_12678", + "XK_braille_dots_1267", + "XK_braille_dots_1268", + "XK_braille_dots_126", + "XK_braille_dots_1278", + "XK_braille_dots_127", + "XK_braille_dots_128", + "XK_braille_dots_12", + "XK_braille_dots_1345678", + "XK_braille_dots_134567", + "XK_braille_dots_134568", + "XK_braille_dots_13456", + "XK_braille_dots_134578", + "XK_braille_dots_13457", + "XK_braille_dots_13458", + "XK_braille_dots_1345", + "XK_braille_dots_134678", + "XK_braille_dots_13467", + "XK_braille_dots_13468", + "XK_braille_dots_1346", + "XK_braille_dots_13478", + "XK_braille_dots_1347", + "XK_braille_dots_1348", + "XK_braille_dots_134", + "XK_braille_dots_135678", + "XK_braille_dots_13567", + "XK_braille_dots_13568", + "XK_braille_dots_1356", + "XK_braille_dots_13578", + "XK_braille_dots_1357", + "XK_braille_dots_1358", + "XK_braille_dots_135", + "XK_braille_dots_13678", + "XK_braille_dots_1367", + "XK_braille_dots_1368", + "XK_braille_dots_136", + "XK_braille_dots_1378", + "XK_braille_dots_137", + "XK_braille_dots_138", + "XK_braille_dots_13", + "XK_braille_dots_145678", + "XK_braille_dots_14567", + "XK_braille_dots_14568", + "XK_braille_dots_1456", + "XK_braille_dots_14578", + "XK_braille_dots_1457", + "XK_braille_dots_1458", + "XK_braille_dots_145", + "XK_braille_dots_14678", + "XK_braille_dots_1467", + "XK_braille_dots_1468", + "XK_braille_dots_146", + "XK_braille_dots_1478", + "XK_braille_dots_147", + "XK_braille_dots_148", + "XK_braille_dots_14", + "XK_braille_dots_15678", + "XK_braille_dots_1567", + "XK_braille_dots_1568", + "XK_braille_dots_156", + "XK_braille_dots_1578", + "XK_braille_dots_157", + "XK_braille_dots_158", + "XK_braille_dots_15", + "XK_braille_dots_1678", + "XK_braille_dots_167", + "XK_braille_dots_168", + "XK_braille_dots_16", + "XK_braille_dots_178", + "XK_braille_dots_17", + "XK_braille_dots_18", + "XK_braille_dots_1", + "XK_braille_dots_2345678", + "XK_braille_dots_234567", + "XK_braille_dots_234568", + "XK_braille_dots_23456", + "XK_braille_dots_234578", + "XK_braille_dots_23457", + "XK_braille_dots_23458", + "XK_braille_dots_2345", + "XK_braille_dots_234678", + "XK_braille_dots_23467", + "XK_braille_dots_23468", + "XK_braille_dots_2346", + "XK_braille_dots_23478", + "XK_braille_dots_2347", + "XK_braille_dots_2348", + "XK_braille_dots_234", + "XK_braille_dots_235678", + "XK_braille_dots_23567", + "XK_braille_dots_23568", + "XK_braille_dots_2356", + "XK_braille_dots_23578", + "XK_braille_dots_2357", + "XK_braille_dots_2358", + "XK_braille_dots_235", + "XK_braille_dots_23678", + "XK_braille_dots_2367", + "XK_braille_dots_2368", + "XK_braille_dots_236", + "XK_braille_dots_2378", + "XK_braille_dots_237", + "XK_braille_dots_238", + "XK_braille_dots_23", + "XK_braille_dots_245678", + "XK_braille_dots_24567", + "XK_braille_dots_24568", + "XK_braille_dots_2456", + "XK_braille_dots_24578", + "XK_braille_dots_2457", + "XK_braille_dots_2458", + "XK_braille_dots_245", + "XK_braille_dots_24678", + "XK_braille_dots_2467", + "XK_braille_dots_2468", + "XK_braille_dots_246", + "XK_braille_dots_2478", + "XK_braille_dots_247", + "XK_braille_dots_248", + "XK_braille_dots_24", + "XK_braille_dots_25678", + "XK_braille_dots_2567", + "XK_braille_dots_2568", + "XK_braille_dots_256", + "XK_braille_dots_2578", + "XK_braille_dots_257", + "XK_braille_dots_258", + "XK_braille_dots_25", + "XK_braille_dots_2678", + "XK_braille_dots_267", + "XK_braille_dots_268", + "XK_braille_dots_26", + "XK_braille_dots_278", + "XK_braille_dots_27", + "XK_braille_dots_28", + "XK_braille_dots_2", + "XK_braille_dots_345678", + "XK_braille_dots_34567", + "XK_braille_dots_34568", + "XK_braille_dots_3456", + "XK_braille_dots_34578", + "XK_braille_dots_3457", + "XK_braille_dots_3458", + "XK_braille_dots_345", + "XK_braille_dots_34678", + "XK_braille_dots_3467", + "XK_braille_dots_3468", + "XK_braille_dots_346", + "XK_braille_dots_3478", + "XK_braille_dots_347", + "XK_braille_dots_348", + "XK_braille_dots_34", + "XK_braille_dots_35678", + "XK_braille_dots_3567", + "XK_braille_dots_3568", + "XK_braille_dots_356", + "XK_braille_dots_3578", + "XK_braille_dots_357", + "XK_braille_dots_358", + "XK_braille_dots_35", + "XK_braille_dots_3678", + "XK_braille_dots_367", + "XK_braille_dots_368", + "XK_braille_dots_36", + "XK_braille_dots_378", + "XK_braille_dots_37", + "XK_braille_dots_38", + "XK_braille_dots_3", + "XK_braille_dots_45678", + "XK_braille_dots_4567", + "XK_braille_dots_4568", + "XK_braille_dots_456", + "XK_braille_dots_4578", + "XK_braille_dots_457", + "XK_braille_dots_458", + "XK_braille_dots_45", + "XK_braille_dots_4678", + "XK_braille_dots_467", + "XK_braille_dots_468", + "XK_braille_dots_46", + "XK_braille_dots_478", + "XK_braille_dots_47", + "XK_braille_dots_48", + "XK_braille_dots_4", + "XK_braille_dots_5678", + "XK_braille_dots_567", + "XK_braille_dots_568", + "XK_braille_dots_56", + "XK_braille_dots_578", + "XK_braille_dots_57", + "XK_braille_dots_58", + "XK_braille_dots_5", + "XK_braille_dots_678", + "XK_braille_dots_67", + "XK_braille_dots_68", + "XK_braille_dots_6", + "XK_braille_dots_78", + "XK_braille_dots_7", + "XK_braille_dots_8", + "XK_combining_acute", + "XK_combining_belowdot", + "XK_combining_grave", + "XK_combining_hook", + "XK_combining_tilde", + "XK_containsas", + "XK_cuberoot", + "XK_dabovedot", + "XK_dead_SCHWA", + "XK_dead_hamza", + "XK_dead_schwa", + "XK_dintegral", + "XK_ebelowdot", + "XK_ecircumflexacute", + "XK_ecircumflexbelowdot", + "XK_ecircumflexgrave", + "XK_ecircumflexhook", + "XK_ecircumflextilde", + "XK_ehook", + "XK_eightsubscript", + "XK_eightsuperior", + "XK_elementof", + "XK_emptyset", + "XK_etilde", + "XK_ezh", + "XK_fabovedot", + "XK_fivesubscript", + "XK_fivesuperior", + "XK_foursubscript", + "XK_foursuperior", + "XK_fourthroot", + "XK_gcaron", + "XK_guillemetleft", + "XK_guillemetright", + "XK_ibelowdot", + "XK_ibreve", + "XK_ihook", + "XK_lbelowdot", + "XK_mabovedot", + "XK_ninesubscript", + "XK_ninesuperior", + "XK_notapproxeq", + "XK_notelementof", + "XK_notidentical", + "XK_obarred", + "XK_obelowdot", + "XK_ocaron", + "XK_ocircumflexacute", + "XK_ocircumflexbelowdot", + "XK_ocircumflexgrave", + "XK_ocircumflexhook", + "XK_ocircumflextilde", + "XK_oe", + "XK_ohook", + "XK_ohorn", + "XK_ohornacute", + "XK_ohornbelowdot", + "XK_ohorngrave", + "XK_ohornhook", + "XK_ohorntilde", + "XK_onesubscript", + "XK_ooblique", + "XK_ordmasculine", + "XK_pabovedot", + "XK_partdifferential", + "XK_permille", + "XK_sabovedot", + "XK_schwa", + "XK_sevensubscript", + "XK_sevensuperior", + "XK_sixsubscript", + "XK_sixsuperior", + "XK_squareroot", + "XK_stricteq", + "XK_tabovedot", + "XK_threesubscript", + "XK_tintegral", + "XK_twosubscript", + "XK_ubelowdot", + "XK_uhook", + "XK_uhorn", + "XK_uhornacute", + "XK_uhornbelowdot", + "XK_uhorngrave", + "XK_uhornhook", + "XK_uhorntilde", + "XK_wacute", + "XK_wcircumflex", + "XK_wdiaeresis", + "XK_wgrave", + "XK_xabovedot", + "XK_ybelowdot", + "XK_ycircumflex", + "XK_ygrave", + "XK_yhook", + "XK_ytilde", + "XK_zerosubscript", + "XK_zerosuperior", + "XK_zstroke", +} + + +def main(): + # leave a format specifier to patch up with the length later + s = StringIO() + # writing replaces the initial value?? so I have to do this out here + s.write("pub(super) static XKEYS: [(&str, u32); {}] = keys! {{\n") + count = 0 + with open("/usr/include/X11/keysymdef.h") as f: + for line in f: + if line.startswith("#define XK_"): + _def, xkey, *rest = line.split() + if xkey in SKIP: + continue + s.write(xkey + ", ") + count += 1 + s.write("\n}};") + + print( + textwrap.fill( + s.getvalue(), + width=80, + subsequent_indent=" " * 4, + ).format(count) + ) + + +if __name__ == "__main__": + main() diff --git a/src/config.rs b/src/config.rs index 41bc3af..367fc9a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use std::{ path::Path, }; -use fig_env::{CLICKS, HANDLERS, KEYS}; +use fig_env::{CLICKS, HANDLERS, KEYS, XKEYS}; use mlua::{Lua, LuaSerdeExt as _, Table}; use crate::{config::key::Key, enums::Scheme, Button, Layout, Rule}; @@ -128,8 +128,8 @@ impl ConfigBuilder { } // install key definitions - for (k, v) in KEYS { - globals.set(k, v).unwrap(); + for (k, v) in KEYS.iter().chain(XKEYS.iter()) { + globals.set(*k, *v).unwrap(); } // install click and button definitions diff --git a/src/config/fig_env.rs b/src/config/fig_env.rs index 1e504f2..d033c56 100644 --- a/src/config/fig_env.rs +++ b/src/config/fig_env.rs @@ -42,18 +42,255 @@ pub(super) static HANDLERS: [(&str, &str); 23] = handler_fns! { togglescratch, }; -pub(super) static KEYS: [(&str, u32); 48] = keys! { - Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, - XK_a, XK_b, XK_c, XK_d, XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, XK_k, - XK_l, XK_m, XK_n, XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, - XK_w, XK_x, XK_y, XK_z, XK_0, XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, - XK_7, XK_8, XK_9, XK_Return, XK_Tab, XK_space, XK_comma, XK_period, - XK_grave, ShiftMask, ControlMask, -}; - pub(super) static CLICKS: [(&str, u32); 6] = clicks! { TagBar, LtSymbol, StatusText, WinTitle, ClientWin, RootWin, }; pub(super) static BUTTONS: [(&str, u32); 3] = keys! { Button1, Button2, Button3 }; + +pub(super) static KEYS: [(&str, u32); 6] = keys! { + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, ShiftMask, ControlMask, +}; + +/// All of the keysyms in [`x11::keysym`], generated by `scripts/keysyms.py`. +pub(super) static XKEYS: [(&str, u32); 1134] = keys! { XK_BackSpace, XK_Tab, +XK_Linefeed, XK_Clear, XK_Return, XK_Pause, XK_Scroll_Lock, XK_Sys_Req, +XK_Escape, XK_Delete, XK_Multi_key, XK_Kanji, XK_Muhenkan, XK_Henkan_Mode, +XK_Henkan, XK_Romaji, XK_Hiragana, XK_Katakana, XK_Hiragana_Katakana, +XK_Zenkaku, XK_Hankaku, XK_Zenkaku_Hankaku, XK_Touroku, XK_Massyo, +XK_Kana_Lock, XK_Kana_Shift, XK_Eisu_Shift, XK_Eisu_toggle, XK_Home, +XK_Left, XK_Up, XK_Right, XK_Down, XK_Prior, XK_Page_Up, XK_Next, +XK_Page_Down, XK_End, XK_Begin, XK_Select, XK_Print, XK_Execute, XK_Insert, +XK_Undo, XK_Redo, XK_Menu, XK_Find, XK_Cancel, XK_Help, XK_Break, +XK_Mode_switch, XK_script_switch, XK_Num_Lock, XK_KP_Space, XK_KP_Tab, +XK_KP_Enter, XK_KP_F1, XK_KP_F2, XK_KP_F3, XK_KP_F4, XK_KP_Home, XK_KP_Left, +XK_KP_Up, XK_KP_Right, XK_KP_Down, XK_KP_Prior, XK_KP_Page_Up, XK_KP_Next, +XK_KP_Page_Down, XK_KP_End, XK_KP_Begin, XK_KP_Insert, XK_KP_Delete, +XK_KP_Equal, XK_KP_Multiply, XK_KP_Add, XK_KP_Separator, XK_KP_Subtract, +XK_KP_Decimal, XK_KP_Divide, XK_KP_0, XK_KP_1, XK_KP_2, XK_KP_3, XK_KP_4, +XK_KP_5, XK_KP_6, XK_KP_7, XK_KP_8, XK_KP_9, XK_F1, XK_F2, XK_F3, XK_F4, +XK_F5, XK_F6, XK_F7, XK_F8, XK_F9, XK_F10, XK_F11, XK_L1, XK_F12, XK_L2, +XK_F13, XK_L3, XK_F14, XK_L4, XK_F15, XK_L5, XK_F16, XK_L6, XK_F17, XK_L7, +XK_F18, XK_L8, XK_F19, XK_L9, XK_F20, XK_L10, XK_F21, XK_R1, XK_F22, XK_R2, +XK_F23, XK_R3, XK_F24, XK_R4, XK_F25, XK_R5, XK_F26, XK_R6, XK_F27, XK_R7, +XK_F28, XK_R8, XK_F29, XK_R9, XK_F30, XK_R10, XK_F31, XK_R11, XK_F32, +XK_R12, XK_F33, XK_R13, XK_F34, XK_R14, XK_F35, XK_R15, XK_Shift_L, +XK_Shift_R, XK_Control_L, XK_Control_R, XK_Caps_Lock, XK_Shift_Lock, +XK_Meta_L, XK_Meta_R, XK_Alt_L, XK_Alt_R, XK_Super_L, XK_Super_R, +XK_Hyper_L, XK_Hyper_R, XK_ISO_Lock, XK_ISO_Level2_Latch, +XK_ISO_Level3_Shift, XK_ISO_Level3_Latch, XK_ISO_Level3_Lock, +XK_ISO_Level5_Shift, XK_ISO_Level5_Latch, XK_ISO_Level5_Lock, +XK_ISO_Group_Shift, XK_ISO_Group_Latch, XK_ISO_Group_Lock, +XK_ISO_Next_Group, XK_ISO_Next_Group_Lock, XK_ISO_Prev_Group, +XK_ISO_Prev_Group_Lock, XK_ISO_First_Group, XK_ISO_First_Group_Lock, +XK_ISO_Last_Group, XK_ISO_Last_Group_Lock, XK_ISO_Left_Tab, +XK_ISO_Move_Line_Up, XK_ISO_Move_Line_Down, XK_ISO_Partial_Line_Up, +XK_ISO_Partial_Line_Down, XK_ISO_Partial_Space_Left, +XK_ISO_Partial_Space_Right, XK_ISO_Set_Margin_Left, XK_ISO_Set_Margin_Right, +XK_ISO_Release_Margin_Left, XK_ISO_Release_Margin_Right, +XK_ISO_Release_Both_Margins, XK_ISO_Fast_Cursor_Left, +XK_ISO_Fast_Cursor_Right, XK_ISO_Fast_Cursor_Up, XK_ISO_Fast_Cursor_Down, +XK_ISO_Continuous_Underline, XK_ISO_Discontinuous_Underline, +XK_ISO_Emphasize, XK_ISO_Center_Object, XK_ISO_Enter, XK_dead_grave, +XK_dead_acute, XK_dead_circumflex, XK_dead_tilde, XK_dead_perispomeni, +XK_dead_macron, XK_dead_breve, XK_dead_abovedot, XK_dead_diaeresis, +XK_dead_abovering, XK_dead_doubleacute, XK_dead_caron, XK_dead_cedilla, +XK_dead_ogonek, XK_dead_iota, XK_dead_voiced_sound, +XK_dead_semivoiced_sound, XK_dead_belowdot, XK_dead_hook, XK_dead_horn, +XK_dead_stroke, XK_dead_abovecomma, XK_dead_psili, +XK_dead_abovereversedcomma, XK_dead_dasia, XK_dead_doublegrave, +XK_dead_belowring, XK_dead_belowmacron, XK_dead_belowcircumflex, +XK_dead_belowtilde, XK_dead_belowbreve, XK_dead_belowdiaeresis, +XK_dead_invertedbreve, XK_dead_belowcomma, XK_dead_currency, +XK_dead_lowline, XK_dead_aboveverticalline, XK_dead_belowverticalline, +XK_dead_longsolidusoverlay, XK_dead_a, XK_dead_A, XK_dead_e, XK_dead_E, +XK_dead_i, XK_dead_I, XK_dead_o, XK_dead_O, XK_dead_u, XK_dead_U, +XK_dead_small_schwa, XK_dead_capital_schwa, XK_dead_greek, +XK_First_Virtual_Screen, XK_Prev_Virtual_Screen, XK_Next_Virtual_Screen, +XK_Last_Virtual_Screen, XK_Terminate_Server, XK_AccessX_Enable, +XK_AccessX_Feedback_Enable, XK_RepeatKeys_Enable, XK_SlowKeys_Enable, +XK_BounceKeys_Enable, XK_StickyKeys_Enable, XK_MouseKeys_Enable, +XK_MouseKeys_Accel_Enable, XK_Overlay1_Enable, XK_Overlay2_Enable, +XK_AudibleBell_Enable, XK_Pointer_Left, XK_Pointer_Right, XK_Pointer_Up, +XK_Pointer_Down, XK_Pointer_UpLeft, XK_Pointer_UpRight, XK_Pointer_DownLeft, +XK_Pointer_DownRight, XK_Pointer_Button_Dflt, XK_Pointer_Button1, +XK_Pointer_Button2, XK_Pointer_Button3, XK_Pointer_Button4, +XK_Pointer_Button5, XK_Pointer_DblClick_Dflt, XK_Pointer_DblClick1, +XK_Pointer_DblClick2, XK_Pointer_DblClick3, XK_Pointer_DblClick4, +XK_Pointer_DblClick5, XK_Pointer_Drag_Dflt, XK_Pointer_Drag1, +XK_Pointer_Drag2, XK_Pointer_Drag3, XK_Pointer_Drag4, XK_Pointer_Drag5, +XK_Pointer_EnableKeys, XK_Pointer_Accelerate, XK_Pointer_DfltBtnNext, +XK_Pointer_DfltBtnPrev, XK_ch, XK_Ch, XK_CH, XK_c_h, XK_C_h, XK_C_H, +XK_space, XK_exclam, XK_quotedbl, XK_numbersign, XK_dollar, XK_percent, +XK_ampersand, XK_apostrophe, XK_quoteright, XK_parenleft, XK_parenright, +XK_asterisk, XK_plus, XK_comma, XK_minus, XK_period, XK_slash, XK_0, XK_1, +XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, XK_colon, XK_semicolon, +XK_less, XK_equal, XK_greater, XK_question, XK_at, XK_A, XK_B, XK_C, XK_D, +XK_E, XK_F, XK_G, XK_H, XK_I, XK_J, XK_K, XK_L, XK_M, XK_N, XK_O, XK_P, +XK_Q, XK_R, XK_S, XK_T, XK_U, XK_V, XK_W, XK_X, XK_Y, XK_Z, XK_bracketleft, +XK_backslash, XK_bracketright, XK_asciicircum, XK_underscore, XK_grave, +XK_quoteleft, XK_a, XK_b, XK_c, XK_d, XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, +XK_k, XK_l, XK_m, XK_n, XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, +XK_w, XK_x, XK_y, XK_z, XK_braceleft, XK_bar, XK_braceright, XK_asciitilde, +XK_nobreakspace, XK_exclamdown, XK_cent, XK_sterling, XK_currency, XK_yen, +XK_brokenbar, XK_section, XK_diaeresis, XK_copyright, XK_ordfeminine, +XK_guillemotleft, XK_notsign, XK_hyphen, XK_registered, XK_macron, +XK_degree, XK_plusminus, XK_twosuperior, XK_threesuperior, XK_acute, XK_mu, +XK_paragraph, XK_periodcentered, XK_cedilla, XK_onesuperior, XK_masculine, +XK_guillemotright, XK_onequarter, XK_onehalf, XK_threequarters, +XK_questiondown, XK_Agrave, XK_Aacute, XK_Acircumflex, XK_Atilde, +XK_Adiaeresis, XK_Aring, XK_AE, XK_Ccedilla, XK_Egrave, XK_Eacute, +XK_Ecircumflex, XK_Ediaeresis, XK_Igrave, XK_Iacute, XK_Icircumflex, +XK_Idiaeresis, XK_ETH, XK_Eth, XK_Ntilde, XK_Ograve, XK_Oacute, +XK_Ocircumflex, XK_Otilde, XK_Odiaeresis, XK_multiply, XK_Ooblique, +XK_Ugrave, XK_Uacute, XK_Ucircumflex, XK_Udiaeresis, XK_Yacute, XK_THORN, +XK_Thorn, XK_ssharp, XK_agrave, XK_aacute, XK_acircumflex, XK_atilde, +XK_adiaeresis, XK_aring, XK_ae, XK_ccedilla, XK_egrave, XK_eacute, +XK_ecircumflex, XK_ediaeresis, XK_igrave, XK_iacute, XK_icircumflex, +XK_idiaeresis, XK_eth, XK_ntilde, XK_ograve, XK_oacute, XK_ocircumflex, +XK_otilde, XK_odiaeresis, XK_division, XK_oslash, XK_ugrave, XK_uacute, +XK_ucircumflex, XK_udiaeresis, XK_yacute, XK_thorn, XK_ydiaeresis, +XK_Aogonek, XK_breve, XK_Lstroke, XK_Lcaron, XK_Sacute, XK_Scaron, +XK_Scedilla, XK_Tcaron, XK_Zacute, XK_Zcaron, XK_Zabovedot, XK_aogonek, +XK_ogonek, XK_lstroke, XK_lcaron, XK_sacute, XK_caron, XK_scaron, +XK_scedilla, XK_tcaron, XK_zacute, XK_doubleacute, XK_zcaron, XK_zabovedot, +XK_Racute, XK_Abreve, XK_Lacute, XK_Cacute, XK_Ccaron, XK_Eogonek, +XK_Ecaron, XK_Dcaron, XK_Dstroke, XK_Nacute, XK_Ncaron, XK_Odoubleacute, +XK_Rcaron, XK_Uring, XK_Udoubleacute, XK_Tcedilla, XK_racute, XK_abreve, +XK_lacute, XK_cacute, XK_ccaron, XK_eogonek, XK_ecaron, XK_dcaron, +XK_dstroke, XK_nacute, XK_ncaron, XK_odoubleacute, XK_rcaron, XK_uring, +XK_udoubleacute, XK_tcedilla, XK_abovedot, XK_Hstroke, XK_Hcircumflex, +XK_Iabovedot, XK_Gbreve, XK_Jcircumflex, XK_hstroke, XK_hcircumflex, +XK_idotless, XK_gbreve, XK_jcircumflex, XK_Cabovedot, XK_Ccircumflex, +XK_Gabovedot, XK_Gcircumflex, XK_Ubreve, XK_Scircumflex, XK_cabovedot, +XK_ccircumflex, XK_gabovedot, XK_gcircumflex, XK_ubreve, XK_scircumflex, +XK_kra, XK_kappa, XK_Rcedilla, XK_Itilde, XK_Lcedilla, XK_Emacron, +XK_Gcedilla, XK_Tslash, XK_rcedilla, XK_itilde, XK_lcedilla, XK_emacron, +XK_gcedilla, XK_tslash, XK_ENG, XK_eng, XK_Amacron, XK_Iogonek, +XK_Eabovedot, XK_Imacron, XK_Ncedilla, XK_Omacron, XK_Kcedilla, XK_Uogonek, +XK_Utilde, XK_Umacron, XK_amacron, XK_iogonek, XK_eabovedot, XK_imacron, +XK_ncedilla, XK_omacron, XK_kcedilla, XK_uogonek, XK_utilde, XK_umacron, +XK_overline, XK_kana_fullstop, XK_kana_openingbracket, +XK_kana_closingbracket, XK_kana_comma, XK_kana_conjunctive, +XK_kana_middledot, XK_kana_WO, XK_kana_a, XK_kana_i, XK_kana_u, XK_kana_e, +XK_kana_o, XK_kana_ya, XK_kana_yu, XK_kana_yo, XK_kana_tsu, XK_kana_tu, +XK_prolongedsound, XK_kana_A, XK_kana_I, XK_kana_U, XK_kana_E, XK_kana_O, +XK_kana_KA, XK_kana_KI, XK_kana_KU, XK_kana_KE, XK_kana_KO, XK_kana_SA, +XK_kana_SHI, XK_kana_SU, XK_kana_SE, XK_kana_SO, XK_kana_TA, XK_kana_CHI, +XK_kana_TI, XK_kana_TSU, XK_kana_TU, XK_kana_TE, XK_kana_TO, XK_kana_NA, +XK_kana_NI, XK_kana_NU, XK_kana_NE, XK_kana_NO, XK_kana_HA, XK_kana_HI, +XK_kana_FU, XK_kana_HU, XK_kana_HE, XK_kana_HO, XK_kana_MA, XK_kana_MI, +XK_kana_MU, XK_kana_ME, XK_kana_MO, XK_kana_YA, XK_kana_YU, XK_kana_YO, +XK_kana_RA, XK_kana_RI, XK_kana_RU, XK_kana_RE, XK_kana_RO, XK_kana_WA, +XK_kana_N, XK_voicedsound, XK_semivoicedsound, XK_kana_switch, +XK_Arabic_comma, XK_Arabic_semicolon, XK_Arabic_question_mark, +XK_Arabic_hamza, XK_Arabic_maddaonalef, XK_Arabic_hamzaonalef, +XK_Arabic_hamzaonwaw, XK_Arabic_hamzaunderalef, XK_Arabic_hamzaonyeh, +XK_Arabic_alef, XK_Arabic_beh, XK_Arabic_tehmarbuta, XK_Arabic_teh, +XK_Arabic_theh, XK_Arabic_jeem, XK_Arabic_hah, XK_Arabic_khah, +XK_Arabic_dal, XK_Arabic_thal, XK_Arabic_ra, XK_Arabic_zain, XK_Arabic_seen, +XK_Arabic_sheen, XK_Arabic_sad, XK_Arabic_dad, XK_Arabic_tah, XK_Arabic_zah, +XK_Arabic_ain, XK_Arabic_ghain, XK_Arabic_tatweel, XK_Arabic_feh, +XK_Arabic_qaf, XK_Arabic_kaf, XK_Arabic_lam, XK_Arabic_meem, XK_Arabic_noon, +XK_Arabic_ha, XK_Arabic_heh, XK_Arabic_waw, XK_Arabic_alefmaksura, +XK_Arabic_yeh, XK_Arabic_fathatan, XK_Arabic_dammatan, XK_Arabic_kasratan, +XK_Arabic_fatha, XK_Arabic_damma, XK_Arabic_kasra, XK_Arabic_shadda, +XK_Arabic_sukun, XK_Arabic_switch, XK_Serbian_dje, XK_Macedonia_gje, +XK_Cyrillic_io, XK_Ukrainian_ie, XK_Ukranian_je, XK_Macedonia_dse, +XK_Ukrainian_i, XK_Ukranian_i, XK_Ukrainian_yi, XK_Ukranian_yi, +XK_Cyrillic_je, XK_Serbian_je, XK_Cyrillic_lje, XK_Serbian_lje, +XK_Cyrillic_nje, XK_Serbian_nje, XK_Serbian_tshe, XK_Macedonia_kje, +XK_Byelorussian_shortu, XK_Cyrillic_dzhe, XK_Serbian_dze, XK_numerosign, +XK_Serbian_DJE, XK_Macedonia_GJE, XK_Cyrillic_IO, XK_Ukrainian_IE, +XK_Ukranian_JE, XK_Macedonia_DSE, XK_Ukrainian_I, XK_Ukranian_I, +XK_Ukrainian_YI, XK_Ukranian_YI, XK_Cyrillic_JE, XK_Serbian_JE, +XK_Cyrillic_LJE, XK_Serbian_LJE, XK_Cyrillic_NJE, XK_Serbian_NJE, +XK_Serbian_TSHE, XK_Macedonia_KJE, XK_Byelorussian_SHORTU, XK_Cyrillic_DZHE, +XK_Serbian_DZE, XK_Cyrillic_yu, XK_Cyrillic_a, XK_Cyrillic_be, +XK_Cyrillic_tse, XK_Cyrillic_de, XK_Cyrillic_ie, XK_Cyrillic_ef, +XK_Cyrillic_ghe, XK_Cyrillic_ha, XK_Cyrillic_i, XK_Cyrillic_shorti, +XK_Cyrillic_ka, XK_Cyrillic_el, XK_Cyrillic_em, XK_Cyrillic_en, +XK_Cyrillic_o, XK_Cyrillic_pe, XK_Cyrillic_ya, XK_Cyrillic_er, +XK_Cyrillic_es, XK_Cyrillic_te, XK_Cyrillic_u, XK_Cyrillic_zhe, +XK_Cyrillic_ve, XK_Cyrillic_softsign, XK_Cyrillic_yeru, XK_Cyrillic_ze, +XK_Cyrillic_sha, XK_Cyrillic_e, XK_Cyrillic_shcha, XK_Cyrillic_che, +XK_Cyrillic_hardsign, XK_Cyrillic_YU, XK_Cyrillic_A, XK_Cyrillic_BE, +XK_Cyrillic_TSE, XK_Cyrillic_DE, XK_Cyrillic_IE, XK_Cyrillic_EF, +XK_Cyrillic_GHE, XK_Cyrillic_HA, XK_Cyrillic_I, XK_Cyrillic_SHORTI, +XK_Cyrillic_KA, XK_Cyrillic_EL, XK_Cyrillic_EM, XK_Cyrillic_EN, +XK_Cyrillic_O, XK_Cyrillic_PE, XK_Cyrillic_YA, XK_Cyrillic_ER, +XK_Cyrillic_ES, XK_Cyrillic_TE, XK_Cyrillic_U, XK_Cyrillic_ZHE, +XK_Cyrillic_VE, XK_Cyrillic_SOFTSIGN, XK_Cyrillic_YERU, XK_Cyrillic_ZE, +XK_Cyrillic_SHA, XK_Cyrillic_E, XK_Cyrillic_SHCHA, XK_Cyrillic_CHE, +XK_Cyrillic_HARDSIGN, XK_Greek_ALPHAaccent, XK_Greek_EPSILONaccent, +XK_Greek_ETAaccent, XK_Greek_IOTAaccent, XK_Greek_IOTAdiaeresis, +XK_Greek_OMICRONaccent, XK_Greek_UPSILONaccent, XK_Greek_UPSILONdieresis, +XK_Greek_OMEGAaccent, XK_Greek_accentdieresis, XK_Greek_horizbar, +XK_Greek_alphaaccent, XK_Greek_epsilonaccent, XK_Greek_etaaccent, +XK_Greek_iotaaccent, XK_Greek_iotadieresis, XK_Greek_iotaaccentdieresis, +XK_Greek_omicronaccent, XK_Greek_upsilonaccent, XK_Greek_upsilondieresis, +XK_Greek_upsilonaccentdieresis, XK_Greek_omegaaccent, XK_Greek_ALPHA, +XK_Greek_BETA, XK_Greek_GAMMA, XK_Greek_DELTA, XK_Greek_EPSILON, +XK_Greek_ZETA, XK_Greek_ETA, XK_Greek_THETA, XK_Greek_IOTA, XK_Greek_KAPPA, +XK_Greek_LAMDA, XK_Greek_LAMBDA, XK_Greek_MU, XK_Greek_NU, XK_Greek_XI, +XK_Greek_OMICRON, XK_Greek_PI, XK_Greek_RHO, XK_Greek_SIGMA, XK_Greek_TAU, +XK_Greek_UPSILON, XK_Greek_PHI, XK_Greek_CHI, XK_Greek_PSI, XK_Greek_OMEGA, +XK_Greek_alpha, XK_Greek_beta, XK_Greek_gamma, XK_Greek_delta, +XK_Greek_epsilon, XK_Greek_zeta, XK_Greek_eta, XK_Greek_theta, +XK_Greek_iota, XK_Greek_kappa, XK_Greek_lamda, XK_Greek_lambda, XK_Greek_mu, +XK_Greek_nu, XK_Greek_xi, XK_Greek_omicron, XK_Greek_pi, XK_Greek_rho, +XK_Greek_sigma, XK_Greek_finalsmallsigma, XK_Greek_tau, XK_Greek_upsilon, +XK_Greek_phi, XK_Greek_chi, XK_Greek_psi, XK_Greek_omega, XK_Greek_switch, +XK_leftradical, XK_topleftradical, XK_horizconnector, XK_topintegral, +XK_botintegral, XK_vertconnector, XK_topleftsqbracket, XK_botleftsqbracket, +XK_toprightsqbracket, XK_botrightsqbracket, XK_topleftparens, +XK_botleftparens, XK_toprightparens, XK_botrightparens, +XK_leftmiddlecurlybrace, XK_rightmiddlecurlybrace, XK_topleftsummation, +XK_botleftsummation, XK_topvertsummationconnector, +XK_botvertsummationconnector, XK_toprightsummation, XK_botrightsummation, +XK_rightmiddlesummation, XK_lessthanequal, XK_notequal, XK_greaterthanequal, +XK_integral, XK_therefore, XK_variation, XK_infinity, XK_nabla, +XK_approximate, XK_similarequal, XK_ifonlyif, XK_implies, XK_identical, +XK_radical, XK_includedin, XK_includes, XK_intersection, XK_union, +XK_logicaland, XK_logicalor, XK_partialderivative, XK_function, +XK_leftarrow, XK_uparrow, XK_rightarrow, XK_downarrow, XK_blank, +XK_soliddiamond, XK_checkerboard, XK_ht, XK_ff, XK_cr, XK_lf, XK_nl, XK_vt, +XK_lowrightcorner, XK_uprightcorner, XK_upleftcorner, XK_lowleftcorner, +XK_crossinglines, XK_horizlinescan1, XK_horizlinescan3, XK_horizlinescan5, +XK_horizlinescan7, XK_horizlinescan9, XK_leftt, XK_rightt, XK_bott, XK_topt, +XK_vertbar, XK_emspace, XK_enspace, XK_em3space, XK_em4space, XK_digitspace, +XK_punctspace, XK_thinspace, XK_hairspace, XK_emdash, XK_endash, +XK_signifblank, XK_ellipsis, XK_doubbaselinedot, XK_onethird, XK_twothirds, +XK_onefifth, XK_twofifths, XK_threefifths, XK_fourfifths, XK_onesixth, +XK_fivesixths, XK_careof, XK_figdash, XK_leftanglebracket, XK_decimalpoint, +XK_rightanglebracket, XK_marker, XK_oneeighth, XK_threeeighths, +XK_fiveeighths, XK_seveneighths, XK_trademark, XK_signaturemark, +XK_trademarkincircle, XK_leftopentriangle, XK_rightopentriangle, +XK_emopencircle, XK_emopenrectangle, XK_leftsinglequotemark, +XK_rightsinglequotemark, XK_leftdoublequotemark, XK_rightdoublequotemark, +XK_prescription, XK_minutes, XK_seconds, XK_latincross, XK_hexagram, +XK_filledrectbullet, XK_filledlefttribullet, XK_filledrighttribullet, +XK_emfilledcircle, XK_emfilledrect, XK_enopencircbullet, +XK_enopensquarebullet, XK_openrectbullet, XK_opentribulletup, +XK_opentribulletdown, XK_openstar, XK_enfilledcircbullet, +XK_enfilledsqbullet, XK_filledtribulletup, XK_filledtribulletdown, +XK_leftpointer, XK_rightpointer, XK_club, XK_diamond, XK_heart, +XK_maltesecross, XK_dagger, XK_doubledagger, XK_checkmark, XK_ballotcross, +XK_musicalsharp, XK_musicalflat, XK_malesymbol, XK_femalesymbol, +XK_telephone, XK_telephonerecorder, XK_phonographcopyright, XK_caret, +XK_singlelowquotemark, XK_doublelowquotemark, XK_cursor, XK_leftcaret, +XK_rightcaret, XK_downcaret, XK_upcaret, XK_overbar, XK_downtack, XK_upshoe, +XK_downstile, XK_underbar, XK_jot, XK_quad, XK_uptack, XK_circle, +XK_upstile, XK_downshoe, XK_rightshoe, XK_leftshoe, XK_lefttack, +XK_righttack, XK_hebrew_doublelowline, XK_hebrew_aleph, XK_hebrew_bet, +XK_hebrew_beth, XK_hebrew_gimel, XK_hebrew_gimmel, XK_hebrew_dalet, +XK_hebrew_daleth, XK_hebrew_he, XK_hebrew_waw, XK_hebrew_zain, +XK_hebrew_zayin, XK_hebrew_chet, XK_hebrew_het, XK_hebrew_tet, +XK_hebrew_teth, XK_hebrew_yod, XK_hebrew_finalkaph, XK_hebrew_kaph, +XK_hebrew_lamed, XK_hebrew_finalmem, XK_hebrew_mem, XK_hebrew_finalnun, +XK_hebrew_nun, XK_hebrew_samech, XK_hebrew_samekh, XK_hebrew_ayin, +XK_hebrew_finalpe, XK_hebrew_pe, XK_hebrew_finalzade, XK_hebrew_finalzadi, +XK_hebrew_zade, XK_hebrew_zadi, XK_hebrew_qoph, XK_hebrew_kuf, +XK_hebrew_resh, XK_hebrew_shin, XK_hebrew_taw, XK_hebrew_taf, +XK_Hebrew_switch, }; From e8963fd5965c02058ad1e285637c9a412990f5be Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sun, 19 Jan 2025 18:00:57 -0500 Subject: [PATCH 25/27] run clippy with nightly --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a065e3b..4120aae 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ ifdef FIX endif clippy: - cargo clippy --workspace --all-targets --all-features --tests $(clippy_args) + cargo +nightly clippy --workspace --all-targets --all-features --tests $(clippy_args) doc: cargo doc --open From 9ca30fb29e275bb33a7765ab185056c9a61e0985 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sun, 19 Jan 2025 18:02:06 -0500 Subject: [PATCH 26/27] fix new operator precedence lint --- src/core.rs | 10 +++++----- src/key_handlers.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core.rs b/src/core.rs index 3c4b3f8..28558c8 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1468,7 +1468,7 @@ pub fn drawbar(state: &mut State, m: *mut Monitor) { let w = textw(&mut state.drw, &text, state.lrpad); drw::setscheme( &mut state.drw, - state.scheme[if ((*m).tagset[(*m).seltags as usize] & 1 << i) + state.scheme[if ((*m).tagset[(*m).seltags as usize] & (1 << i)) != 0 { Scheme::Sel @@ -1486,10 +1486,10 @@ pub fn drawbar(state: &mut State, m: *mut Monitor) { state.bh as u32, state.lrpad as u32 / 2, &text, - (urg as i32) & 1 << i, + (urg as i32) & (1 << i), ); - if (occ & 1 << i) != 0 { + if (occ & (1 << i)) != 0 { drw::rect( &mut state.drw, x + boxs as i32, @@ -1498,9 +1498,9 @@ pub fn drawbar(state: &mut State, m: *mut Monitor) { boxw, (m == state.selmon && !(*state.selmon).sel.is_null() - && ((*(*state.selmon).sel).tags & 1 << i) != 0) + && ((*(*state.selmon).sel).tags & (1 << i)) != 0) as c_int, - (urg & 1 << i) != 0, + (urg & (1 << i)) != 0, ); } x += w as i32; diff --git a/src/key_handlers.rs b/src/key_handlers.rs index 59e339b..f93494d 100644 --- a/src/key_handlers.rs +++ b/src/key_handlers.rs @@ -183,7 +183,7 @@ pub(crate) fn view(state: &mut State, arg: *const Arg) { pertag.curtag = 0; } else { let mut i; - cfor!((i = 0; ((*arg).ui() & 1 << i) == 0; i += 1) {}); + cfor!((i = 0; ((*arg).ui() & (1 << i)) == 0; i += 1) {}); pertag.curtag = i + 1; } } else { @@ -442,10 +442,10 @@ pub(crate) fn toggleview(state: &mut State, arg: *const Arg) { } // test if the user did not select the same tag - if (newtagset & 1 << ((*state.selmon).pertag.curtag - 1)) == 0 { + if (newtagset & (1 << ((*state.selmon).pertag.curtag - 1))) == 0 { (*state.selmon).pertag.prevtag = (*state.selmon).pertag.curtag; let mut i; - cfor!((i = 0; (newtagset & 1 << i) == 0; i += 1) {}); + cfor!((i = 0; (newtagset & (1 << i)) == 0; i += 1) {}); (*state.selmon).pertag.curtag = i + 1; } From 1175fa0feb92d7a25435caa0500d163d4d1b00d0 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Sun, 19 Jan 2025 18:47:21 -0500 Subject: [PATCH 27/27] include xf86 keys too --- scripts/keysyms.py | 202 ++++++++++++++-- src/config/fig_env.rs | 526 +++++++++++++++++++++++------------------- 2 files changed, 473 insertions(+), 255 deletions(-) diff --git a/scripts/keysyms.py b/scripts/keysyms.py index 87b71c6..1867a16 100644 --- a/scripts/keysyms.py +++ b/scripts/keysyms.py @@ -985,31 +985,203 @@ "XK_zerosubscript", "XK_zerosuperior", "XK_zstroke", + "XF86XK_10ChannelsDown", + "XF86XK_10ChannelsUp", + "XF86XK_3DMode", + "XF86XK_ALSToggle", + "XF86XK_Addressbook", + "XF86XK_AppSelect", + "XF86XK_AspectRatio", + "XF86XK_Assistant", + "XF86XK_AttendantOff", + "XF86XK_AttendantOn", + "XF86XK_AttendantToggle", + "XF86XK_Audio", + "XF86XK_AudioDesc", + "XF86XK_AudioPreset", + "XF86XK_AutopilotEngageToggle", + "XF86XK_Break", + "XF86XK_BrightnessAuto", + "XF86XK_BrightnessMax", + "XF86XK_BrightnessMin", + "XF86XK_Buttonconfig", + "XF86XK_CameraAccessDisable", + "XF86XK_CameraAccessEnable", + "XF86XK_CameraAccessToggle", + "XF86XK_CameraDown", + "XF86XK_CameraFocus", + "XF86XK_CameraLeft", + "XF86XK_CameraRight", + "XF86XK_CameraUp", + "XF86XK_CameraZoomIn", + "XF86XK_CameraZoomOut", + "XF86XK_ChannelDown", + "XF86XK_ChannelUp", + "XF86XK_ClearvuSonar", + "XF86XK_ContextMenu", + "XF86XK_ControlPanel", + "XF86XK_DVD", + "XF86XK_Data", + "XF86XK_Database", + "XF86XK_Dictate", + "XF86XK_DisplayOff", + "XF86XK_DisplayToggle", + "XF86XK_DualRangeRadar", + "XF86XK_Editor", + "XF86XK_EmojiPicker", + "XF86XK_FastReverse", + "XF86XK_FishingChart", + "XF86XK_Fn", + "XF86XK_FnRightShift", + "XF86XK_Fn_Esc", + "XF86XK_FullScreen", + "XF86XK_GraphicsEditor", + "XF86XK_HangupPhone", + "XF86XK_Images", + "XF86XK_Info", + "XF86XK_Journal", + "XF86XK_KbdInputAssistAccept", + "XF86XK_KbdInputAssistCancel", + "XF86XK_KbdInputAssistNext", + "XF86XK_KbdInputAssistNextgroup", + "XF86XK_KbdInputAssistPrev", + "XF86XK_KbdInputAssistPrevgroup", + "XF86XK_KbdLcdMenu1", + "XF86XK_KbdLcdMenu2", + "XF86XK_KbdLcdMenu3", + "XF86XK_KbdLcdMenu4", + "XF86XK_KbdLcdMenu5", + "XF86XK_Keyboard", + "XF86XK_LeftDown", + "XF86XK_LeftUp", + "XF86XK_LightsToggle", + "XF86XK_Macro1", + "XF86XK_Macro10", + "XF86XK_Macro11", + "XF86XK_Macro12", + "XF86XK_Macro13", + "XF86XK_Macro14", + "XF86XK_Macro15", + "XF86XK_Macro16", + "XF86XK_Macro17", + "XF86XK_Macro18", + "XF86XK_Macro19", + "XF86XK_Macro2", + "XF86XK_Macro20", + "XF86XK_Macro21", + "XF86XK_Macro22", + "XF86XK_Macro23", + "XF86XK_Macro24", + "XF86XK_Macro25", + "XF86XK_Macro26", + "XF86XK_Macro27", + "XF86XK_Macro28", + "XF86XK_Macro29", + "XF86XK_Macro3", + "XF86XK_Macro30", + "XF86XK_Macro4", + "XF86XK_Macro5", + "XF86XK_Macro6", + "XF86XK_Macro7", + "XF86XK_Macro8", + "XF86XK_Macro9", + "XF86XK_MacroPreset1", + "XF86XK_MacroPreset2", + "XF86XK_MacroPreset3", + "XF86XK_MacroPresetCycle", + "XF86XK_MacroRecordStart", + "XF86XK_MacroRecordStop", + "XF86XK_MarkWaypoint", + "XF86XK_MediaRepeat", + "XF86XK_MediaTopMenu", + "XF86XK_MonBrightnessCycle", + "XF86XK_NavChart", + "XF86XK_NavInfo", + "XF86XK_NextElement", + "XF86XK_NextFavorite", + "XF86XK_NotificationCenter", + "XF86XK_Numeric0", + "XF86XK_Numeric1", + "XF86XK_Numeric11", + "XF86XK_Numeric12", + "XF86XK_Numeric2", + "XF86XK_Numeric3", + "XF86XK_Numeric4", + "XF86XK_Numeric5", + "XF86XK_Numeric6", + "XF86XK_Numeric7", + "XF86XK_Numeric8", + "XF86XK_Numeric9", + "XF86XK_NumericA", + "XF86XK_NumericB", + "XF86XK_NumericC", + "XF86XK_NumericD", + "XF86XK_NumericPound", + "XF86XK_NumericStar", + "XF86XK_OnScreenKeyboard", + "XF86XK_PauseRecord", + "XF86XK_PickupPhone", + "XF86XK_Presentation", + "XF86XK_PreviousElement", + "XF86XK_PrivacyScreenToggle", + "XF86XK_RFKill", + "XF86XK_RadarOverlay", + "XF86XK_RightDown", + "XF86XK_RightUp", + "XF86XK_RootMenu", + "XF86XK_RotationLockToggle", + "XF86XK_Screensaver", + "XF86XK_SelectiveScreenshot", + "XF86XK_SidevuSonar", + "XF86XK_SingleRangeRadar", + "XF86XK_SlowReverse", + "XF86XK_Sos", + "XF86XK_SpellCheck", + "XF86XK_StopRecord", + "XF86XK_Taskmanager", + "XF86XK_TraditionalSonar", + "XF86XK_Unmute", + "XF86XK_VOD", + "XF86XK_VideoPhone", + "XF86XK_VoiceCommand", + "XF86XK_Voicemail", + "XF86XK_WPSButton", + "XF86XK_WWAN", + "XF86XK_ZoomReset", } def main(): # leave a format specifier to patch up with the length later - s = StringIO() + body = StringIO() # writing replaces the initial value?? so I have to do this out here - s.write("pub(super) static XKEYS: [(&str, u32); {}] = keys! {{\n") + body.write("") count = 0 with open("/usr/include/X11/keysymdef.h") as f: - for line in f: - if line.startswith("#define XK_"): - _def, xkey, *rest = line.split() - if xkey in SKIP: - continue - s.write(xkey + ", ") - count += 1 - s.write("\n}};") + lines = f.readlines() + with open("/usr/include/X11/XF86keysym.h") as f: + lines.extend(f.readlines()) + for line in lines: + if line.startswith("#define XK_") or line.startswith( + "#define XF86XK_" + ): + _def, xkey, *rest = line.split() + if xkey in SKIP: + continue + body.write(xkey + ", ") + count += 1 + + body = textwrap.fill( + body.getvalue(), + width=80, + initial_indent=" " * 4, + subsequent_indent=" " * 4, + ) print( - textwrap.fill( - s.getvalue(), - width=80, - subsequent_indent=" " * 4, - ).format(count) + f"""pub(super) static XKEYS: [(&str, u32); {count}] = keys! {{ +{body} +}};""" ) diff --git a/src/config/fig_env.rs b/src/config/fig_env.rs index d033c56..f442ccf 100644 --- a/src/config/fig_env.rs +++ b/src/config/fig_env.rs @@ -54,243 +54,289 @@ pub(super) static KEYS: [(&str, u32); 6] = keys! { }; /// All of the keysyms in [`x11::keysym`], generated by `scripts/keysyms.py`. -pub(super) static XKEYS: [(&str, u32); 1134] = keys! { XK_BackSpace, XK_Tab, -XK_Linefeed, XK_Clear, XK_Return, XK_Pause, XK_Scroll_Lock, XK_Sys_Req, -XK_Escape, XK_Delete, XK_Multi_key, XK_Kanji, XK_Muhenkan, XK_Henkan_Mode, -XK_Henkan, XK_Romaji, XK_Hiragana, XK_Katakana, XK_Hiragana_Katakana, -XK_Zenkaku, XK_Hankaku, XK_Zenkaku_Hankaku, XK_Touroku, XK_Massyo, -XK_Kana_Lock, XK_Kana_Shift, XK_Eisu_Shift, XK_Eisu_toggle, XK_Home, -XK_Left, XK_Up, XK_Right, XK_Down, XK_Prior, XK_Page_Up, XK_Next, -XK_Page_Down, XK_End, XK_Begin, XK_Select, XK_Print, XK_Execute, XK_Insert, -XK_Undo, XK_Redo, XK_Menu, XK_Find, XK_Cancel, XK_Help, XK_Break, -XK_Mode_switch, XK_script_switch, XK_Num_Lock, XK_KP_Space, XK_KP_Tab, -XK_KP_Enter, XK_KP_F1, XK_KP_F2, XK_KP_F3, XK_KP_F4, XK_KP_Home, XK_KP_Left, -XK_KP_Up, XK_KP_Right, XK_KP_Down, XK_KP_Prior, XK_KP_Page_Up, XK_KP_Next, -XK_KP_Page_Down, XK_KP_End, XK_KP_Begin, XK_KP_Insert, XK_KP_Delete, -XK_KP_Equal, XK_KP_Multiply, XK_KP_Add, XK_KP_Separator, XK_KP_Subtract, -XK_KP_Decimal, XK_KP_Divide, XK_KP_0, XK_KP_1, XK_KP_2, XK_KP_3, XK_KP_4, -XK_KP_5, XK_KP_6, XK_KP_7, XK_KP_8, XK_KP_9, XK_F1, XK_F2, XK_F3, XK_F4, -XK_F5, XK_F6, XK_F7, XK_F8, XK_F9, XK_F10, XK_F11, XK_L1, XK_F12, XK_L2, -XK_F13, XK_L3, XK_F14, XK_L4, XK_F15, XK_L5, XK_F16, XK_L6, XK_F17, XK_L7, -XK_F18, XK_L8, XK_F19, XK_L9, XK_F20, XK_L10, XK_F21, XK_R1, XK_F22, XK_R2, -XK_F23, XK_R3, XK_F24, XK_R4, XK_F25, XK_R5, XK_F26, XK_R6, XK_F27, XK_R7, -XK_F28, XK_R8, XK_F29, XK_R9, XK_F30, XK_R10, XK_F31, XK_R11, XK_F32, -XK_R12, XK_F33, XK_R13, XK_F34, XK_R14, XK_F35, XK_R15, XK_Shift_L, -XK_Shift_R, XK_Control_L, XK_Control_R, XK_Caps_Lock, XK_Shift_Lock, -XK_Meta_L, XK_Meta_R, XK_Alt_L, XK_Alt_R, XK_Super_L, XK_Super_R, -XK_Hyper_L, XK_Hyper_R, XK_ISO_Lock, XK_ISO_Level2_Latch, -XK_ISO_Level3_Shift, XK_ISO_Level3_Latch, XK_ISO_Level3_Lock, -XK_ISO_Level5_Shift, XK_ISO_Level5_Latch, XK_ISO_Level5_Lock, -XK_ISO_Group_Shift, XK_ISO_Group_Latch, XK_ISO_Group_Lock, -XK_ISO_Next_Group, XK_ISO_Next_Group_Lock, XK_ISO_Prev_Group, -XK_ISO_Prev_Group_Lock, XK_ISO_First_Group, XK_ISO_First_Group_Lock, -XK_ISO_Last_Group, XK_ISO_Last_Group_Lock, XK_ISO_Left_Tab, -XK_ISO_Move_Line_Up, XK_ISO_Move_Line_Down, XK_ISO_Partial_Line_Up, -XK_ISO_Partial_Line_Down, XK_ISO_Partial_Space_Left, -XK_ISO_Partial_Space_Right, XK_ISO_Set_Margin_Left, XK_ISO_Set_Margin_Right, -XK_ISO_Release_Margin_Left, XK_ISO_Release_Margin_Right, -XK_ISO_Release_Both_Margins, XK_ISO_Fast_Cursor_Left, -XK_ISO_Fast_Cursor_Right, XK_ISO_Fast_Cursor_Up, XK_ISO_Fast_Cursor_Down, -XK_ISO_Continuous_Underline, XK_ISO_Discontinuous_Underline, -XK_ISO_Emphasize, XK_ISO_Center_Object, XK_ISO_Enter, XK_dead_grave, -XK_dead_acute, XK_dead_circumflex, XK_dead_tilde, XK_dead_perispomeni, -XK_dead_macron, XK_dead_breve, XK_dead_abovedot, XK_dead_diaeresis, -XK_dead_abovering, XK_dead_doubleacute, XK_dead_caron, XK_dead_cedilla, -XK_dead_ogonek, XK_dead_iota, XK_dead_voiced_sound, -XK_dead_semivoiced_sound, XK_dead_belowdot, XK_dead_hook, XK_dead_horn, -XK_dead_stroke, XK_dead_abovecomma, XK_dead_psili, -XK_dead_abovereversedcomma, XK_dead_dasia, XK_dead_doublegrave, -XK_dead_belowring, XK_dead_belowmacron, XK_dead_belowcircumflex, -XK_dead_belowtilde, XK_dead_belowbreve, XK_dead_belowdiaeresis, -XK_dead_invertedbreve, XK_dead_belowcomma, XK_dead_currency, -XK_dead_lowline, XK_dead_aboveverticalline, XK_dead_belowverticalline, -XK_dead_longsolidusoverlay, XK_dead_a, XK_dead_A, XK_dead_e, XK_dead_E, -XK_dead_i, XK_dead_I, XK_dead_o, XK_dead_O, XK_dead_u, XK_dead_U, -XK_dead_small_schwa, XK_dead_capital_schwa, XK_dead_greek, -XK_First_Virtual_Screen, XK_Prev_Virtual_Screen, XK_Next_Virtual_Screen, -XK_Last_Virtual_Screen, XK_Terminate_Server, XK_AccessX_Enable, -XK_AccessX_Feedback_Enable, XK_RepeatKeys_Enable, XK_SlowKeys_Enable, -XK_BounceKeys_Enable, XK_StickyKeys_Enable, XK_MouseKeys_Enable, -XK_MouseKeys_Accel_Enable, XK_Overlay1_Enable, XK_Overlay2_Enable, -XK_AudibleBell_Enable, XK_Pointer_Left, XK_Pointer_Right, XK_Pointer_Up, -XK_Pointer_Down, XK_Pointer_UpLeft, XK_Pointer_UpRight, XK_Pointer_DownLeft, -XK_Pointer_DownRight, XK_Pointer_Button_Dflt, XK_Pointer_Button1, -XK_Pointer_Button2, XK_Pointer_Button3, XK_Pointer_Button4, -XK_Pointer_Button5, XK_Pointer_DblClick_Dflt, XK_Pointer_DblClick1, -XK_Pointer_DblClick2, XK_Pointer_DblClick3, XK_Pointer_DblClick4, -XK_Pointer_DblClick5, XK_Pointer_Drag_Dflt, XK_Pointer_Drag1, -XK_Pointer_Drag2, XK_Pointer_Drag3, XK_Pointer_Drag4, XK_Pointer_Drag5, -XK_Pointer_EnableKeys, XK_Pointer_Accelerate, XK_Pointer_DfltBtnNext, -XK_Pointer_DfltBtnPrev, XK_ch, XK_Ch, XK_CH, XK_c_h, XK_C_h, XK_C_H, -XK_space, XK_exclam, XK_quotedbl, XK_numbersign, XK_dollar, XK_percent, -XK_ampersand, XK_apostrophe, XK_quoteright, XK_parenleft, XK_parenright, -XK_asterisk, XK_plus, XK_comma, XK_minus, XK_period, XK_slash, XK_0, XK_1, -XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, XK_colon, XK_semicolon, -XK_less, XK_equal, XK_greater, XK_question, XK_at, XK_A, XK_B, XK_C, XK_D, -XK_E, XK_F, XK_G, XK_H, XK_I, XK_J, XK_K, XK_L, XK_M, XK_N, XK_O, XK_P, -XK_Q, XK_R, XK_S, XK_T, XK_U, XK_V, XK_W, XK_X, XK_Y, XK_Z, XK_bracketleft, -XK_backslash, XK_bracketright, XK_asciicircum, XK_underscore, XK_grave, -XK_quoteleft, XK_a, XK_b, XK_c, XK_d, XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, -XK_k, XK_l, XK_m, XK_n, XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, -XK_w, XK_x, XK_y, XK_z, XK_braceleft, XK_bar, XK_braceright, XK_asciitilde, -XK_nobreakspace, XK_exclamdown, XK_cent, XK_sterling, XK_currency, XK_yen, -XK_brokenbar, XK_section, XK_diaeresis, XK_copyright, XK_ordfeminine, -XK_guillemotleft, XK_notsign, XK_hyphen, XK_registered, XK_macron, -XK_degree, XK_plusminus, XK_twosuperior, XK_threesuperior, XK_acute, XK_mu, -XK_paragraph, XK_periodcentered, XK_cedilla, XK_onesuperior, XK_masculine, -XK_guillemotright, XK_onequarter, XK_onehalf, XK_threequarters, -XK_questiondown, XK_Agrave, XK_Aacute, XK_Acircumflex, XK_Atilde, -XK_Adiaeresis, XK_Aring, XK_AE, XK_Ccedilla, XK_Egrave, XK_Eacute, -XK_Ecircumflex, XK_Ediaeresis, XK_Igrave, XK_Iacute, XK_Icircumflex, -XK_Idiaeresis, XK_ETH, XK_Eth, XK_Ntilde, XK_Ograve, XK_Oacute, -XK_Ocircumflex, XK_Otilde, XK_Odiaeresis, XK_multiply, XK_Ooblique, -XK_Ugrave, XK_Uacute, XK_Ucircumflex, XK_Udiaeresis, XK_Yacute, XK_THORN, -XK_Thorn, XK_ssharp, XK_agrave, XK_aacute, XK_acircumflex, XK_atilde, -XK_adiaeresis, XK_aring, XK_ae, XK_ccedilla, XK_egrave, XK_eacute, -XK_ecircumflex, XK_ediaeresis, XK_igrave, XK_iacute, XK_icircumflex, -XK_idiaeresis, XK_eth, XK_ntilde, XK_ograve, XK_oacute, XK_ocircumflex, -XK_otilde, XK_odiaeresis, XK_division, XK_oslash, XK_ugrave, XK_uacute, -XK_ucircumflex, XK_udiaeresis, XK_yacute, XK_thorn, XK_ydiaeresis, -XK_Aogonek, XK_breve, XK_Lstroke, XK_Lcaron, XK_Sacute, XK_Scaron, -XK_Scedilla, XK_Tcaron, XK_Zacute, XK_Zcaron, XK_Zabovedot, XK_aogonek, -XK_ogonek, XK_lstroke, XK_lcaron, XK_sacute, XK_caron, XK_scaron, -XK_scedilla, XK_tcaron, XK_zacute, XK_doubleacute, XK_zcaron, XK_zabovedot, -XK_Racute, XK_Abreve, XK_Lacute, XK_Cacute, XK_Ccaron, XK_Eogonek, -XK_Ecaron, XK_Dcaron, XK_Dstroke, XK_Nacute, XK_Ncaron, XK_Odoubleacute, -XK_Rcaron, XK_Uring, XK_Udoubleacute, XK_Tcedilla, XK_racute, XK_abreve, -XK_lacute, XK_cacute, XK_ccaron, XK_eogonek, XK_ecaron, XK_dcaron, -XK_dstroke, XK_nacute, XK_ncaron, XK_odoubleacute, XK_rcaron, XK_uring, -XK_udoubleacute, XK_tcedilla, XK_abovedot, XK_Hstroke, XK_Hcircumflex, -XK_Iabovedot, XK_Gbreve, XK_Jcircumflex, XK_hstroke, XK_hcircumflex, -XK_idotless, XK_gbreve, XK_jcircumflex, XK_Cabovedot, XK_Ccircumflex, -XK_Gabovedot, XK_Gcircumflex, XK_Ubreve, XK_Scircumflex, XK_cabovedot, -XK_ccircumflex, XK_gabovedot, XK_gcircumflex, XK_ubreve, XK_scircumflex, -XK_kra, XK_kappa, XK_Rcedilla, XK_Itilde, XK_Lcedilla, XK_Emacron, -XK_Gcedilla, XK_Tslash, XK_rcedilla, XK_itilde, XK_lcedilla, XK_emacron, -XK_gcedilla, XK_tslash, XK_ENG, XK_eng, XK_Amacron, XK_Iogonek, -XK_Eabovedot, XK_Imacron, XK_Ncedilla, XK_Omacron, XK_Kcedilla, XK_Uogonek, -XK_Utilde, XK_Umacron, XK_amacron, XK_iogonek, XK_eabovedot, XK_imacron, -XK_ncedilla, XK_omacron, XK_kcedilla, XK_uogonek, XK_utilde, XK_umacron, -XK_overline, XK_kana_fullstop, XK_kana_openingbracket, -XK_kana_closingbracket, XK_kana_comma, XK_kana_conjunctive, -XK_kana_middledot, XK_kana_WO, XK_kana_a, XK_kana_i, XK_kana_u, XK_kana_e, -XK_kana_o, XK_kana_ya, XK_kana_yu, XK_kana_yo, XK_kana_tsu, XK_kana_tu, -XK_prolongedsound, XK_kana_A, XK_kana_I, XK_kana_U, XK_kana_E, XK_kana_O, -XK_kana_KA, XK_kana_KI, XK_kana_KU, XK_kana_KE, XK_kana_KO, XK_kana_SA, -XK_kana_SHI, XK_kana_SU, XK_kana_SE, XK_kana_SO, XK_kana_TA, XK_kana_CHI, -XK_kana_TI, XK_kana_TSU, XK_kana_TU, XK_kana_TE, XK_kana_TO, XK_kana_NA, -XK_kana_NI, XK_kana_NU, XK_kana_NE, XK_kana_NO, XK_kana_HA, XK_kana_HI, -XK_kana_FU, XK_kana_HU, XK_kana_HE, XK_kana_HO, XK_kana_MA, XK_kana_MI, -XK_kana_MU, XK_kana_ME, XK_kana_MO, XK_kana_YA, XK_kana_YU, XK_kana_YO, -XK_kana_RA, XK_kana_RI, XK_kana_RU, XK_kana_RE, XK_kana_RO, XK_kana_WA, -XK_kana_N, XK_voicedsound, XK_semivoicedsound, XK_kana_switch, -XK_Arabic_comma, XK_Arabic_semicolon, XK_Arabic_question_mark, -XK_Arabic_hamza, XK_Arabic_maddaonalef, XK_Arabic_hamzaonalef, -XK_Arabic_hamzaonwaw, XK_Arabic_hamzaunderalef, XK_Arabic_hamzaonyeh, -XK_Arabic_alef, XK_Arabic_beh, XK_Arabic_tehmarbuta, XK_Arabic_teh, -XK_Arabic_theh, XK_Arabic_jeem, XK_Arabic_hah, XK_Arabic_khah, -XK_Arabic_dal, XK_Arabic_thal, XK_Arabic_ra, XK_Arabic_zain, XK_Arabic_seen, -XK_Arabic_sheen, XK_Arabic_sad, XK_Arabic_dad, XK_Arabic_tah, XK_Arabic_zah, -XK_Arabic_ain, XK_Arabic_ghain, XK_Arabic_tatweel, XK_Arabic_feh, -XK_Arabic_qaf, XK_Arabic_kaf, XK_Arabic_lam, XK_Arabic_meem, XK_Arabic_noon, -XK_Arabic_ha, XK_Arabic_heh, XK_Arabic_waw, XK_Arabic_alefmaksura, -XK_Arabic_yeh, XK_Arabic_fathatan, XK_Arabic_dammatan, XK_Arabic_kasratan, -XK_Arabic_fatha, XK_Arabic_damma, XK_Arabic_kasra, XK_Arabic_shadda, -XK_Arabic_sukun, XK_Arabic_switch, XK_Serbian_dje, XK_Macedonia_gje, -XK_Cyrillic_io, XK_Ukrainian_ie, XK_Ukranian_je, XK_Macedonia_dse, -XK_Ukrainian_i, XK_Ukranian_i, XK_Ukrainian_yi, XK_Ukranian_yi, -XK_Cyrillic_je, XK_Serbian_je, XK_Cyrillic_lje, XK_Serbian_lje, -XK_Cyrillic_nje, XK_Serbian_nje, XK_Serbian_tshe, XK_Macedonia_kje, -XK_Byelorussian_shortu, XK_Cyrillic_dzhe, XK_Serbian_dze, XK_numerosign, -XK_Serbian_DJE, XK_Macedonia_GJE, XK_Cyrillic_IO, XK_Ukrainian_IE, -XK_Ukranian_JE, XK_Macedonia_DSE, XK_Ukrainian_I, XK_Ukranian_I, -XK_Ukrainian_YI, XK_Ukranian_YI, XK_Cyrillic_JE, XK_Serbian_JE, -XK_Cyrillic_LJE, XK_Serbian_LJE, XK_Cyrillic_NJE, XK_Serbian_NJE, -XK_Serbian_TSHE, XK_Macedonia_KJE, XK_Byelorussian_SHORTU, XK_Cyrillic_DZHE, -XK_Serbian_DZE, XK_Cyrillic_yu, XK_Cyrillic_a, XK_Cyrillic_be, -XK_Cyrillic_tse, XK_Cyrillic_de, XK_Cyrillic_ie, XK_Cyrillic_ef, -XK_Cyrillic_ghe, XK_Cyrillic_ha, XK_Cyrillic_i, XK_Cyrillic_shorti, -XK_Cyrillic_ka, XK_Cyrillic_el, XK_Cyrillic_em, XK_Cyrillic_en, -XK_Cyrillic_o, XK_Cyrillic_pe, XK_Cyrillic_ya, XK_Cyrillic_er, -XK_Cyrillic_es, XK_Cyrillic_te, XK_Cyrillic_u, XK_Cyrillic_zhe, -XK_Cyrillic_ve, XK_Cyrillic_softsign, XK_Cyrillic_yeru, XK_Cyrillic_ze, -XK_Cyrillic_sha, XK_Cyrillic_e, XK_Cyrillic_shcha, XK_Cyrillic_che, -XK_Cyrillic_hardsign, XK_Cyrillic_YU, XK_Cyrillic_A, XK_Cyrillic_BE, -XK_Cyrillic_TSE, XK_Cyrillic_DE, XK_Cyrillic_IE, XK_Cyrillic_EF, -XK_Cyrillic_GHE, XK_Cyrillic_HA, XK_Cyrillic_I, XK_Cyrillic_SHORTI, -XK_Cyrillic_KA, XK_Cyrillic_EL, XK_Cyrillic_EM, XK_Cyrillic_EN, -XK_Cyrillic_O, XK_Cyrillic_PE, XK_Cyrillic_YA, XK_Cyrillic_ER, -XK_Cyrillic_ES, XK_Cyrillic_TE, XK_Cyrillic_U, XK_Cyrillic_ZHE, -XK_Cyrillic_VE, XK_Cyrillic_SOFTSIGN, XK_Cyrillic_YERU, XK_Cyrillic_ZE, -XK_Cyrillic_SHA, XK_Cyrillic_E, XK_Cyrillic_SHCHA, XK_Cyrillic_CHE, -XK_Cyrillic_HARDSIGN, XK_Greek_ALPHAaccent, XK_Greek_EPSILONaccent, -XK_Greek_ETAaccent, XK_Greek_IOTAaccent, XK_Greek_IOTAdiaeresis, -XK_Greek_OMICRONaccent, XK_Greek_UPSILONaccent, XK_Greek_UPSILONdieresis, -XK_Greek_OMEGAaccent, XK_Greek_accentdieresis, XK_Greek_horizbar, -XK_Greek_alphaaccent, XK_Greek_epsilonaccent, XK_Greek_etaaccent, -XK_Greek_iotaaccent, XK_Greek_iotadieresis, XK_Greek_iotaaccentdieresis, -XK_Greek_omicronaccent, XK_Greek_upsilonaccent, XK_Greek_upsilondieresis, -XK_Greek_upsilonaccentdieresis, XK_Greek_omegaaccent, XK_Greek_ALPHA, -XK_Greek_BETA, XK_Greek_GAMMA, XK_Greek_DELTA, XK_Greek_EPSILON, -XK_Greek_ZETA, XK_Greek_ETA, XK_Greek_THETA, XK_Greek_IOTA, XK_Greek_KAPPA, -XK_Greek_LAMDA, XK_Greek_LAMBDA, XK_Greek_MU, XK_Greek_NU, XK_Greek_XI, -XK_Greek_OMICRON, XK_Greek_PI, XK_Greek_RHO, XK_Greek_SIGMA, XK_Greek_TAU, -XK_Greek_UPSILON, XK_Greek_PHI, XK_Greek_CHI, XK_Greek_PSI, XK_Greek_OMEGA, -XK_Greek_alpha, XK_Greek_beta, XK_Greek_gamma, XK_Greek_delta, -XK_Greek_epsilon, XK_Greek_zeta, XK_Greek_eta, XK_Greek_theta, -XK_Greek_iota, XK_Greek_kappa, XK_Greek_lamda, XK_Greek_lambda, XK_Greek_mu, -XK_Greek_nu, XK_Greek_xi, XK_Greek_omicron, XK_Greek_pi, XK_Greek_rho, -XK_Greek_sigma, XK_Greek_finalsmallsigma, XK_Greek_tau, XK_Greek_upsilon, -XK_Greek_phi, XK_Greek_chi, XK_Greek_psi, XK_Greek_omega, XK_Greek_switch, -XK_leftradical, XK_topleftradical, XK_horizconnector, XK_topintegral, -XK_botintegral, XK_vertconnector, XK_topleftsqbracket, XK_botleftsqbracket, -XK_toprightsqbracket, XK_botrightsqbracket, XK_topleftparens, -XK_botleftparens, XK_toprightparens, XK_botrightparens, -XK_leftmiddlecurlybrace, XK_rightmiddlecurlybrace, XK_topleftsummation, -XK_botleftsummation, XK_topvertsummationconnector, -XK_botvertsummationconnector, XK_toprightsummation, XK_botrightsummation, -XK_rightmiddlesummation, XK_lessthanequal, XK_notequal, XK_greaterthanequal, -XK_integral, XK_therefore, XK_variation, XK_infinity, XK_nabla, -XK_approximate, XK_similarequal, XK_ifonlyif, XK_implies, XK_identical, -XK_radical, XK_includedin, XK_includes, XK_intersection, XK_union, -XK_logicaland, XK_logicalor, XK_partialderivative, XK_function, -XK_leftarrow, XK_uparrow, XK_rightarrow, XK_downarrow, XK_blank, -XK_soliddiamond, XK_checkerboard, XK_ht, XK_ff, XK_cr, XK_lf, XK_nl, XK_vt, -XK_lowrightcorner, XK_uprightcorner, XK_upleftcorner, XK_lowleftcorner, -XK_crossinglines, XK_horizlinescan1, XK_horizlinescan3, XK_horizlinescan5, -XK_horizlinescan7, XK_horizlinescan9, XK_leftt, XK_rightt, XK_bott, XK_topt, -XK_vertbar, XK_emspace, XK_enspace, XK_em3space, XK_em4space, XK_digitspace, -XK_punctspace, XK_thinspace, XK_hairspace, XK_emdash, XK_endash, -XK_signifblank, XK_ellipsis, XK_doubbaselinedot, XK_onethird, XK_twothirds, -XK_onefifth, XK_twofifths, XK_threefifths, XK_fourfifths, XK_onesixth, -XK_fivesixths, XK_careof, XK_figdash, XK_leftanglebracket, XK_decimalpoint, -XK_rightanglebracket, XK_marker, XK_oneeighth, XK_threeeighths, -XK_fiveeighths, XK_seveneighths, XK_trademark, XK_signaturemark, -XK_trademarkincircle, XK_leftopentriangle, XK_rightopentriangle, -XK_emopencircle, XK_emopenrectangle, XK_leftsinglequotemark, -XK_rightsinglequotemark, XK_leftdoublequotemark, XK_rightdoublequotemark, -XK_prescription, XK_minutes, XK_seconds, XK_latincross, XK_hexagram, -XK_filledrectbullet, XK_filledlefttribullet, XK_filledrighttribullet, -XK_emfilledcircle, XK_emfilledrect, XK_enopencircbullet, -XK_enopensquarebullet, XK_openrectbullet, XK_opentribulletup, -XK_opentribulletdown, XK_openstar, XK_enfilledcircbullet, -XK_enfilledsqbullet, XK_filledtribulletup, XK_filledtribulletdown, -XK_leftpointer, XK_rightpointer, XK_club, XK_diamond, XK_heart, -XK_maltesecross, XK_dagger, XK_doubledagger, XK_checkmark, XK_ballotcross, -XK_musicalsharp, XK_musicalflat, XK_malesymbol, XK_femalesymbol, -XK_telephone, XK_telephonerecorder, XK_phonographcopyright, XK_caret, -XK_singlelowquotemark, XK_doublelowquotemark, XK_cursor, XK_leftcaret, -XK_rightcaret, XK_downcaret, XK_upcaret, XK_overbar, XK_downtack, XK_upshoe, -XK_downstile, XK_underbar, XK_jot, XK_quad, XK_uptack, XK_circle, -XK_upstile, XK_downshoe, XK_rightshoe, XK_leftshoe, XK_lefttack, -XK_righttack, XK_hebrew_doublelowline, XK_hebrew_aleph, XK_hebrew_bet, -XK_hebrew_beth, XK_hebrew_gimel, XK_hebrew_gimmel, XK_hebrew_dalet, -XK_hebrew_daleth, XK_hebrew_he, XK_hebrew_waw, XK_hebrew_zain, -XK_hebrew_zayin, XK_hebrew_chet, XK_hebrew_het, XK_hebrew_tet, -XK_hebrew_teth, XK_hebrew_yod, XK_hebrew_finalkaph, XK_hebrew_kaph, -XK_hebrew_lamed, XK_hebrew_finalmem, XK_hebrew_mem, XK_hebrew_finalnun, -XK_hebrew_nun, XK_hebrew_samech, XK_hebrew_samekh, XK_hebrew_ayin, -XK_hebrew_finalpe, XK_hebrew_pe, XK_hebrew_finalzade, XK_hebrew_finalzadi, -XK_hebrew_zade, XK_hebrew_zadi, XK_hebrew_qoph, XK_hebrew_kuf, -XK_hebrew_resh, XK_hebrew_shin, XK_hebrew_taw, XK_hebrew_taf, -XK_Hebrew_switch, }; +pub(super) static XKEYS: [(&str, u32); 1311] = keys! { + XK_BackSpace, XK_Tab, XK_Linefeed, XK_Clear, XK_Return, XK_Pause, + XK_Scroll_Lock, XK_Sys_Req, XK_Escape, XK_Delete, XK_Multi_key, XK_Kanji, + XK_Muhenkan, XK_Henkan_Mode, XK_Henkan, XK_Romaji, XK_Hiragana, XK_Katakana, + XK_Hiragana_Katakana, XK_Zenkaku, XK_Hankaku, XK_Zenkaku_Hankaku, + XK_Touroku, XK_Massyo, XK_Kana_Lock, XK_Kana_Shift, XK_Eisu_Shift, + XK_Eisu_toggle, XK_Home, XK_Left, XK_Up, XK_Right, XK_Down, XK_Prior, + XK_Page_Up, XK_Next, XK_Page_Down, XK_End, XK_Begin, XK_Select, XK_Print, + XK_Execute, XK_Insert, XK_Undo, XK_Redo, XK_Menu, XK_Find, XK_Cancel, + XK_Help, XK_Break, XK_Mode_switch, XK_script_switch, XK_Num_Lock, + XK_KP_Space, XK_KP_Tab, XK_KP_Enter, XK_KP_F1, XK_KP_F2, XK_KP_F3, XK_KP_F4, + XK_KP_Home, XK_KP_Left, XK_KP_Up, XK_KP_Right, XK_KP_Down, XK_KP_Prior, + XK_KP_Page_Up, XK_KP_Next, XK_KP_Page_Down, XK_KP_End, XK_KP_Begin, + XK_KP_Insert, XK_KP_Delete, XK_KP_Equal, XK_KP_Multiply, XK_KP_Add, + XK_KP_Separator, XK_KP_Subtract, XK_KP_Decimal, XK_KP_Divide, XK_KP_0, + XK_KP_1, XK_KP_2, XK_KP_3, XK_KP_4, XK_KP_5, XK_KP_6, XK_KP_7, XK_KP_8, + XK_KP_9, XK_F1, XK_F2, XK_F3, XK_F4, XK_F5, XK_F6, XK_F7, XK_F8, XK_F9, + XK_F10, XK_F11, XK_L1, XK_F12, XK_L2, XK_F13, XK_L3, XK_F14, XK_L4, XK_F15, + XK_L5, XK_F16, XK_L6, XK_F17, XK_L7, XK_F18, XK_L8, XK_F19, XK_L9, XK_F20, + XK_L10, XK_F21, XK_R1, XK_F22, XK_R2, XK_F23, XK_R3, XK_F24, XK_R4, XK_F25, + XK_R5, XK_F26, XK_R6, XK_F27, XK_R7, XK_F28, XK_R8, XK_F29, XK_R9, XK_F30, + XK_R10, XK_F31, XK_R11, XK_F32, XK_R12, XK_F33, XK_R13, XK_F34, XK_R14, + XK_F35, XK_R15, XK_Shift_L, XK_Shift_R, XK_Control_L, XK_Control_R, + XK_Caps_Lock, XK_Shift_Lock, XK_Meta_L, XK_Meta_R, XK_Alt_L, XK_Alt_R, + XK_Super_L, XK_Super_R, XK_Hyper_L, XK_Hyper_R, XK_ISO_Lock, + XK_ISO_Level2_Latch, XK_ISO_Level3_Shift, XK_ISO_Level3_Latch, + XK_ISO_Level3_Lock, XK_ISO_Level5_Shift, XK_ISO_Level5_Latch, + XK_ISO_Level5_Lock, XK_ISO_Group_Shift, XK_ISO_Group_Latch, + XK_ISO_Group_Lock, XK_ISO_Next_Group, XK_ISO_Next_Group_Lock, + XK_ISO_Prev_Group, XK_ISO_Prev_Group_Lock, XK_ISO_First_Group, + XK_ISO_First_Group_Lock, XK_ISO_Last_Group, XK_ISO_Last_Group_Lock, + XK_ISO_Left_Tab, XK_ISO_Move_Line_Up, XK_ISO_Move_Line_Down, + XK_ISO_Partial_Line_Up, XK_ISO_Partial_Line_Down, XK_ISO_Partial_Space_Left, + XK_ISO_Partial_Space_Right, XK_ISO_Set_Margin_Left, XK_ISO_Set_Margin_Right, + XK_ISO_Release_Margin_Left, XK_ISO_Release_Margin_Right, + XK_ISO_Release_Both_Margins, XK_ISO_Fast_Cursor_Left, + XK_ISO_Fast_Cursor_Right, XK_ISO_Fast_Cursor_Up, XK_ISO_Fast_Cursor_Down, + XK_ISO_Continuous_Underline, XK_ISO_Discontinuous_Underline, + XK_ISO_Emphasize, XK_ISO_Center_Object, XK_ISO_Enter, XK_dead_grave, + XK_dead_acute, XK_dead_circumflex, XK_dead_tilde, XK_dead_perispomeni, + XK_dead_macron, XK_dead_breve, XK_dead_abovedot, XK_dead_diaeresis, + XK_dead_abovering, XK_dead_doubleacute, XK_dead_caron, XK_dead_cedilla, + XK_dead_ogonek, XK_dead_iota, XK_dead_voiced_sound, + XK_dead_semivoiced_sound, XK_dead_belowdot, XK_dead_hook, XK_dead_horn, + XK_dead_stroke, XK_dead_abovecomma, XK_dead_psili, + XK_dead_abovereversedcomma, XK_dead_dasia, XK_dead_doublegrave, + XK_dead_belowring, XK_dead_belowmacron, XK_dead_belowcircumflex, + XK_dead_belowtilde, XK_dead_belowbreve, XK_dead_belowdiaeresis, + XK_dead_invertedbreve, XK_dead_belowcomma, XK_dead_currency, + XK_dead_lowline, XK_dead_aboveverticalline, XK_dead_belowverticalline, + XK_dead_longsolidusoverlay, XK_dead_a, XK_dead_A, XK_dead_e, XK_dead_E, + XK_dead_i, XK_dead_I, XK_dead_o, XK_dead_O, XK_dead_u, XK_dead_U, + XK_dead_small_schwa, XK_dead_capital_schwa, XK_dead_greek, + XK_First_Virtual_Screen, XK_Prev_Virtual_Screen, XK_Next_Virtual_Screen, + XK_Last_Virtual_Screen, XK_Terminate_Server, XK_AccessX_Enable, + XK_AccessX_Feedback_Enable, XK_RepeatKeys_Enable, XK_SlowKeys_Enable, + XK_BounceKeys_Enable, XK_StickyKeys_Enable, XK_MouseKeys_Enable, + XK_MouseKeys_Accel_Enable, XK_Overlay1_Enable, XK_Overlay2_Enable, + XK_AudibleBell_Enable, XK_Pointer_Left, XK_Pointer_Right, XK_Pointer_Up, + XK_Pointer_Down, XK_Pointer_UpLeft, XK_Pointer_UpRight, XK_Pointer_DownLeft, + XK_Pointer_DownRight, XK_Pointer_Button_Dflt, XK_Pointer_Button1, + XK_Pointer_Button2, XK_Pointer_Button3, XK_Pointer_Button4, + XK_Pointer_Button5, XK_Pointer_DblClick_Dflt, XK_Pointer_DblClick1, + XK_Pointer_DblClick2, XK_Pointer_DblClick3, XK_Pointer_DblClick4, + XK_Pointer_DblClick5, XK_Pointer_Drag_Dflt, XK_Pointer_Drag1, + XK_Pointer_Drag2, XK_Pointer_Drag3, XK_Pointer_Drag4, XK_Pointer_Drag5, + XK_Pointer_EnableKeys, XK_Pointer_Accelerate, XK_Pointer_DfltBtnNext, + XK_Pointer_DfltBtnPrev, XK_ch, XK_Ch, XK_CH, XK_c_h, XK_C_h, XK_C_H, + XK_space, XK_exclam, XK_quotedbl, XK_numbersign, XK_dollar, XK_percent, + XK_ampersand, XK_apostrophe, XK_quoteright, XK_parenleft, XK_parenright, + XK_asterisk, XK_plus, XK_comma, XK_minus, XK_period, XK_slash, XK_0, XK_1, + XK_2, XK_3, XK_4, XK_5, XK_6, XK_7, XK_8, XK_9, XK_colon, XK_semicolon, + XK_less, XK_equal, XK_greater, XK_question, XK_at, XK_A, XK_B, XK_C, XK_D, + XK_E, XK_F, XK_G, XK_H, XK_I, XK_J, XK_K, XK_L, XK_M, XK_N, XK_O, XK_P, + XK_Q, XK_R, XK_S, XK_T, XK_U, XK_V, XK_W, XK_X, XK_Y, XK_Z, XK_bracketleft, + XK_backslash, XK_bracketright, XK_asciicircum, XK_underscore, XK_grave, + XK_quoteleft, XK_a, XK_b, XK_c, XK_d, XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, + XK_k, XK_l, XK_m, XK_n, XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, + XK_w, XK_x, XK_y, XK_z, XK_braceleft, XK_bar, XK_braceright, XK_asciitilde, + XK_nobreakspace, XK_exclamdown, XK_cent, XK_sterling, XK_currency, XK_yen, + XK_brokenbar, XK_section, XK_diaeresis, XK_copyright, XK_ordfeminine, + XK_guillemotleft, XK_notsign, XK_hyphen, XK_registered, XK_macron, + XK_degree, XK_plusminus, XK_twosuperior, XK_threesuperior, XK_acute, XK_mu, + XK_paragraph, XK_periodcentered, XK_cedilla, XK_onesuperior, XK_masculine, + XK_guillemotright, XK_onequarter, XK_onehalf, XK_threequarters, + XK_questiondown, XK_Agrave, XK_Aacute, XK_Acircumflex, XK_Atilde, + XK_Adiaeresis, XK_Aring, XK_AE, XK_Ccedilla, XK_Egrave, XK_Eacute, + XK_Ecircumflex, XK_Ediaeresis, XK_Igrave, XK_Iacute, XK_Icircumflex, + XK_Idiaeresis, XK_ETH, XK_Eth, XK_Ntilde, XK_Ograve, XK_Oacute, + XK_Ocircumflex, XK_Otilde, XK_Odiaeresis, XK_multiply, XK_Ooblique, + XK_Ugrave, XK_Uacute, XK_Ucircumflex, XK_Udiaeresis, XK_Yacute, XK_THORN, + XK_Thorn, XK_ssharp, XK_agrave, XK_aacute, XK_acircumflex, XK_atilde, + XK_adiaeresis, XK_aring, XK_ae, XK_ccedilla, XK_egrave, XK_eacute, + XK_ecircumflex, XK_ediaeresis, XK_igrave, XK_iacute, XK_icircumflex, + XK_idiaeresis, XK_eth, XK_ntilde, XK_ograve, XK_oacute, XK_ocircumflex, + XK_otilde, XK_odiaeresis, XK_division, XK_oslash, XK_ugrave, XK_uacute, + XK_ucircumflex, XK_udiaeresis, XK_yacute, XK_thorn, XK_ydiaeresis, + XK_Aogonek, XK_breve, XK_Lstroke, XK_Lcaron, XK_Sacute, XK_Scaron, + XK_Scedilla, XK_Tcaron, XK_Zacute, XK_Zcaron, XK_Zabovedot, XK_aogonek, + XK_ogonek, XK_lstroke, XK_lcaron, XK_sacute, XK_caron, XK_scaron, + XK_scedilla, XK_tcaron, XK_zacute, XK_doubleacute, XK_zcaron, XK_zabovedot, + XK_Racute, XK_Abreve, XK_Lacute, XK_Cacute, XK_Ccaron, XK_Eogonek, + XK_Ecaron, XK_Dcaron, XK_Dstroke, XK_Nacute, XK_Ncaron, XK_Odoubleacute, + XK_Rcaron, XK_Uring, XK_Udoubleacute, XK_Tcedilla, XK_racute, XK_abreve, + XK_lacute, XK_cacute, XK_ccaron, XK_eogonek, XK_ecaron, XK_dcaron, + XK_dstroke, XK_nacute, XK_ncaron, XK_odoubleacute, XK_rcaron, XK_uring, + XK_udoubleacute, XK_tcedilla, XK_abovedot, XK_Hstroke, XK_Hcircumflex, + XK_Iabovedot, XK_Gbreve, XK_Jcircumflex, XK_hstroke, XK_hcircumflex, + XK_idotless, XK_gbreve, XK_jcircumflex, XK_Cabovedot, XK_Ccircumflex, + XK_Gabovedot, XK_Gcircumflex, XK_Ubreve, XK_Scircumflex, XK_cabovedot, + XK_ccircumflex, XK_gabovedot, XK_gcircumflex, XK_ubreve, XK_scircumflex, + XK_kra, XK_kappa, XK_Rcedilla, XK_Itilde, XK_Lcedilla, XK_Emacron, + XK_Gcedilla, XK_Tslash, XK_rcedilla, XK_itilde, XK_lcedilla, XK_emacron, + XK_gcedilla, XK_tslash, XK_ENG, XK_eng, XK_Amacron, XK_Iogonek, + XK_Eabovedot, XK_Imacron, XK_Ncedilla, XK_Omacron, XK_Kcedilla, XK_Uogonek, + XK_Utilde, XK_Umacron, XK_amacron, XK_iogonek, XK_eabovedot, XK_imacron, + XK_ncedilla, XK_omacron, XK_kcedilla, XK_uogonek, XK_utilde, XK_umacron, + XK_overline, XK_kana_fullstop, XK_kana_openingbracket, + XK_kana_closingbracket, XK_kana_comma, XK_kana_conjunctive, + XK_kana_middledot, XK_kana_WO, XK_kana_a, XK_kana_i, XK_kana_u, XK_kana_e, + XK_kana_o, XK_kana_ya, XK_kana_yu, XK_kana_yo, XK_kana_tsu, XK_kana_tu, + XK_prolongedsound, XK_kana_A, XK_kana_I, XK_kana_U, XK_kana_E, XK_kana_O, + XK_kana_KA, XK_kana_KI, XK_kana_KU, XK_kana_KE, XK_kana_KO, XK_kana_SA, + XK_kana_SHI, XK_kana_SU, XK_kana_SE, XK_kana_SO, XK_kana_TA, XK_kana_CHI, + XK_kana_TI, XK_kana_TSU, XK_kana_TU, XK_kana_TE, XK_kana_TO, XK_kana_NA, + XK_kana_NI, XK_kana_NU, XK_kana_NE, XK_kana_NO, XK_kana_HA, XK_kana_HI, + XK_kana_FU, XK_kana_HU, XK_kana_HE, XK_kana_HO, XK_kana_MA, XK_kana_MI, + XK_kana_MU, XK_kana_ME, XK_kana_MO, XK_kana_YA, XK_kana_YU, XK_kana_YO, + XK_kana_RA, XK_kana_RI, XK_kana_RU, XK_kana_RE, XK_kana_RO, XK_kana_WA, + XK_kana_N, XK_voicedsound, XK_semivoicedsound, XK_kana_switch, + XK_Arabic_comma, XK_Arabic_semicolon, XK_Arabic_question_mark, + XK_Arabic_hamza, XK_Arabic_maddaonalef, XK_Arabic_hamzaonalef, + XK_Arabic_hamzaonwaw, XK_Arabic_hamzaunderalef, XK_Arabic_hamzaonyeh, + XK_Arabic_alef, XK_Arabic_beh, XK_Arabic_tehmarbuta, XK_Arabic_teh, + XK_Arabic_theh, XK_Arabic_jeem, XK_Arabic_hah, XK_Arabic_khah, + XK_Arabic_dal, XK_Arabic_thal, XK_Arabic_ra, XK_Arabic_zain, XK_Arabic_seen, + XK_Arabic_sheen, XK_Arabic_sad, XK_Arabic_dad, XK_Arabic_tah, XK_Arabic_zah, + XK_Arabic_ain, XK_Arabic_ghain, XK_Arabic_tatweel, XK_Arabic_feh, + XK_Arabic_qaf, XK_Arabic_kaf, XK_Arabic_lam, XK_Arabic_meem, XK_Arabic_noon, + XK_Arabic_ha, XK_Arabic_heh, XK_Arabic_waw, XK_Arabic_alefmaksura, + XK_Arabic_yeh, XK_Arabic_fathatan, XK_Arabic_dammatan, XK_Arabic_kasratan, + XK_Arabic_fatha, XK_Arabic_damma, XK_Arabic_kasra, XK_Arabic_shadda, + XK_Arabic_sukun, XK_Arabic_switch, XK_Serbian_dje, XK_Macedonia_gje, + XK_Cyrillic_io, XK_Ukrainian_ie, XK_Ukranian_je, XK_Macedonia_dse, + XK_Ukrainian_i, XK_Ukranian_i, XK_Ukrainian_yi, XK_Ukranian_yi, + XK_Cyrillic_je, XK_Serbian_je, XK_Cyrillic_lje, XK_Serbian_lje, + XK_Cyrillic_nje, XK_Serbian_nje, XK_Serbian_tshe, XK_Macedonia_kje, + XK_Byelorussian_shortu, XK_Cyrillic_dzhe, XK_Serbian_dze, XK_numerosign, + XK_Serbian_DJE, XK_Macedonia_GJE, XK_Cyrillic_IO, XK_Ukrainian_IE, + XK_Ukranian_JE, XK_Macedonia_DSE, XK_Ukrainian_I, XK_Ukranian_I, + XK_Ukrainian_YI, XK_Ukranian_YI, XK_Cyrillic_JE, XK_Serbian_JE, + XK_Cyrillic_LJE, XK_Serbian_LJE, XK_Cyrillic_NJE, XK_Serbian_NJE, + XK_Serbian_TSHE, XK_Macedonia_KJE, XK_Byelorussian_SHORTU, XK_Cyrillic_DZHE, + XK_Serbian_DZE, XK_Cyrillic_yu, XK_Cyrillic_a, XK_Cyrillic_be, + XK_Cyrillic_tse, XK_Cyrillic_de, XK_Cyrillic_ie, XK_Cyrillic_ef, + XK_Cyrillic_ghe, XK_Cyrillic_ha, XK_Cyrillic_i, XK_Cyrillic_shorti, + XK_Cyrillic_ka, XK_Cyrillic_el, XK_Cyrillic_em, XK_Cyrillic_en, + XK_Cyrillic_o, XK_Cyrillic_pe, XK_Cyrillic_ya, XK_Cyrillic_er, + XK_Cyrillic_es, XK_Cyrillic_te, XK_Cyrillic_u, XK_Cyrillic_zhe, + XK_Cyrillic_ve, XK_Cyrillic_softsign, XK_Cyrillic_yeru, XK_Cyrillic_ze, + XK_Cyrillic_sha, XK_Cyrillic_e, XK_Cyrillic_shcha, XK_Cyrillic_che, + XK_Cyrillic_hardsign, XK_Cyrillic_YU, XK_Cyrillic_A, XK_Cyrillic_BE, + XK_Cyrillic_TSE, XK_Cyrillic_DE, XK_Cyrillic_IE, XK_Cyrillic_EF, + XK_Cyrillic_GHE, XK_Cyrillic_HA, XK_Cyrillic_I, XK_Cyrillic_SHORTI, + XK_Cyrillic_KA, XK_Cyrillic_EL, XK_Cyrillic_EM, XK_Cyrillic_EN, + XK_Cyrillic_O, XK_Cyrillic_PE, XK_Cyrillic_YA, XK_Cyrillic_ER, + XK_Cyrillic_ES, XK_Cyrillic_TE, XK_Cyrillic_U, XK_Cyrillic_ZHE, + XK_Cyrillic_VE, XK_Cyrillic_SOFTSIGN, XK_Cyrillic_YERU, XK_Cyrillic_ZE, + XK_Cyrillic_SHA, XK_Cyrillic_E, XK_Cyrillic_SHCHA, XK_Cyrillic_CHE, + XK_Cyrillic_HARDSIGN, XK_Greek_ALPHAaccent, XK_Greek_EPSILONaccent, + XK_Greek_ETAaccent, XK_Greek_IOTAaccent, XK_Greek_IOTAdiaeresis, + XK_Greek_OMICRONaccent, XK_Greek_UPSILONaccent, XK_Greek_UPSILONdieresis, + XK_Greek_OMEGAaccent, XK_Greek_accentdieresis, XK_Greek_horizbar, + XK_Greek_alphaaccent, XK_Greek_epsilonaccent, XK_Greek_etaaccent, + XK_Greek_iotaaccent, XK_Greek_iotadieresis, XK_Greek_iotaaccentdieresis, + XK_Greek_omicronaccent, XK_Greek_upsilonaccent, XK_Greek_upsilondieresis, + XK_Greek_upsilonaccentdieresis, XK_Greek_omegaaccent, XK_Greek_ALPHA, + XK_Greek_BETA, XK_Greek_GAMMA, XK_Greek_DELTA, XK_Greek_EPSILON, + XK_Greek_ZETA, XK_Greek_ETA, XK_Greek_THETA, XK_Greek_IOTA, XK_Greek_KAPPA, + XK_Greek_LAMDA, XK_Greek_LAMBDA, XK_Greek_MU, XK_Greek_NU, XK_Greek_XI, + XK_Greek_OMICRON, XK_Greek_PI, XK_Greek_RHO, XK_Greek_SIGMA, XK_Greek_TAU, + XK_Greek_UPSILON, XK_Greek_PHI, XK_Greek_CHI, XK_Greek_PSI, XK_Greek_OMEGA, + XK_Greek_alpha, XK_Greek_beta, XK_Greek_gamma, XK_Greek_delta, + XK_Greek_epsilon, XK_Greek_zeta, XK_Greek_eta, XK_Greek_theta, + XK_Greek_iota, XK_Greek_kappa, XK_Greek_lamda, XK_Greek_lambda, XK_Greek_mu, + XK_Greek_nu, XK_Greek_xi, XK_Greek_omicron, XK_Greek_pi, XK_Greek_rho, + XK_Greek_sigma, XK_Greek_finalsmallsigma, XK_Greek_tau, XK_Greek_upsilon, + XK_Greek_phi, XK_Greek_chi, XK_Greek_psi, XK_Greek_omega, XK_Greek_switch, + XK_leftradical, XK_topleftradical, XK_horizconnector, XK_topintegral, + XK_botintegral, XK_vertconnector, XK_topleftsqbracket, XK_botleftsqbracket, + XK_toprightsqbracket, XK_botrightsqbracket, XK_topleftparens, + XK_botleftparens, XK_toprightparens, XK_botrightparens, + XK_leftmiddlecurlybrace, XK_rightmiddlecurlybrace, XK_topleftsummation, + XK_botleftsummation, XK_topvertsummationconnector, + XK_botvertsummationconnector, XK_toprightsummation, XK_botrightsummation, + XK_rightmiddlesummation, XK_lessthanequal, XK_notequal, XK_greaterthanequal, + XK_integral, XK_therefore, XK_variation, XK_infinity, XK_nabla, + XK_approximate, XK_similarequal, XK_ifonlyif, XK_implies, XK_identical, + XK_radical, XK_includedin, XK_includes, XK_intersection, XK_union, + XK_logicaland, XK_logicalor, XK_partialderivative, XK_function, + XK_leftarrow, XK_uparrow, XK_rightarrow, XK_downarrow, XK_blank, + XK_soliddiamond, XK_checkerboard, XK_ht, XK_ff, XK_cr, XK_lf, XK_nl, XK_vt, + XK_lowrightcorner, XK_uprightcorner, XK_upleftcorner, XK_lowleftcorner, + XK_crossinglines, XK_horizlinescan1, XK_horizlinescan3, XK_horizlinescan5, + XK_horizlinescan7, XK_horizlinescan9, XK_leftt, XK_rightt, XK_bott, XK_topt, + XK_vertbar, XK_emspace, XK_enspace, XK_em3space, XK_em4space, XK_digitspace, + XK_punctspace, XK_thinspace, XK_hairspace, XK_emdash, XK_endash, + XK_signifblank, XK_ellipsis, XK_doubbaselinedot, XK_onethird, XK_twothirds, + XK_onefifth, XK_twofifths, XK_threefifths, XK_fourfifths, XK_onesixth, + XK_fivesixths, XK_careof, XK_figdash, XK_leftanglebracket, XK_decimalpoint, + XK_rightanglebracket, XK_marker, XK_oneeighth, XK_threeeighths, + XK_fiveeighths, XK_seveneighths, XK_trademark, XK_signaturemark, + XK_trademarkincircle, XK_leftopentriangle, XK_rightopentriangle, + XK_emopencircle, XK_emopenrectangle, XK_leftsinglequotemark, + XK_rightsinglequotemark, XK_leftdoublequotemark, XK_rightdoublequotemark, + XK_prescription, XK_minutes, XK_seconds, XK_latincross, XK_hexagram, + XK_filledrectbullet, XK_filledlefttribullet, XK_filledrighttribullet, + XK_emfilledcircle, XK_emfilledrect, XK_enopencircbullet, + XK_enopensquarebullet, XK_openrectbullet, XK_opentribulletup, + XK_opentribulletdown, XK_openstar, XK_enfilledcircbullet, + XK_enfilledsqbullet, XK_filledtribulletup, XK_filledtribulletdown, + XK_leftpointer, XK_rightpointer, XK_club, XK_diamond, XK_heart, + XK_maltesecross, XK_dagger, XK_doubledagger, XK_checkmark, XK_ballotcross, + XK_musicalsharp, XK_musicalflat, XK_malesymbol, XK_femalesymbol, + XK_telephone, XK_telephonerecorder, XK_phonographcopyright, XK_caret, + XK_singlelowquotemark, XK_doublelowquotemark, XK_cursor, XK_leftcaret, + XK_rightcaret, XK_downcaret, XK_upcaret, XK_overbar, XK_downtack, XK_upshoe, + XK_downstile, XK_underbar, XK_jot, XK_quad, XK_uptack, XK_circle, + XK_upstile, XK_downshoe, XK_rightshoe, XK_leftshoe, XK_lefttack, + XK_righttack, XK_hebrew_doublelowline, XK_hebrew_aleph, XK_hebrew_bet, + XK_hebrew_beth, XK_hebrew_gimel, XK_hebrew_gimmel, XK_hebrew_dalet, + XK_hebrew_daleth, XK_hebrew_he, XK_hebrew_waw, XK_hebrew_zain, + XK_hebrew_zayin, XK_hebrew_chet, XK_hebrew_het, XK_hebrew_tet, + XK_hebrew_teth, XK_hebrew_yod, XK_hebrew_finalkaph, XK_hebrew_kaph, + XK_hebrew_lamed, XK_hebrew_finalmem, XK_hebrew_mem, XK_hebrew_finalnun, + XK_hebrew_nun, XK_hebrew_samech, XK_hebrew_samekh, XK_hebrew_ayin, + XK_hebrew_finalpe, XK_hebrew_pe, XK_hebrew_finalzade, XK_hebrew_finalzadi, + XK_hebrew_zade, XK_hebrew_zadi, XK_hebrew_qoph, XK_hebrew_kuf, + XK_hebrew_resh, XK_hebrew_shin, XK_hebrew_taw, XK_hebrew_taf, + XK_Hebrew_switch, XF86XK_ModeLock, XF86XK_MonBrightnessUp, + XF86XK_MonBrightnessDown, XF86XK_KbdLightOnOff, XF86XK_KbdBrightnessUp, + XF86XK_KbdBrightnessDown, XF86XK_Standby, XF86XK_AudioLowerVolume, + XF86XK_AudioMute, XF86XK_AudioRaiseVolume, XF86XK_AudioPlay, + XF86XK_AudioStop, XF86XK_AudioPrev, XF86XK_AudioNext, XF86XK_HomePage, + XF86XK_Mail, XF86XK_Start, XF86XK_Search, XF86XK_AudioRecord, + XF86XK_Calculator, XF86XK_Memo, XF86XK_ToDoList, XF86XK_Calendar, + XF86XK_PowerDown, XF86XK_ContrastAdjust, XF86XK_RockerUp, XF86XK_RockerDown, + XF86XK_RockerEnter, XF86XK_Back, XF86XK_Forward, XF86XK_Stop, + XF86XK_Refresh, XF86XK_PowerOff, XF86XK_WakeUp, XF86XK_Eject, + XF86XK_ScreenSaver, XF86XK_WWW, XF86XK_Sleep, XF86XK_Favorites, + XF86XK_AudioPause, XF86XK_AudioMedia, XF86XK_MyComputer, XF86XK_VendorHome, + XF86XK_LightBulb, XF86XK_Shop, XF86XK_History, XF86XK_OpenURL, + XF86XK_AddFavorite, XF86XK_HotLinks, XF86XK_BrightnessAdjust, + XF86XK_Finance, XF86XK_Community, XF86XK_AudioRewind, XF86XK_BackForward, + XF86XK_Launch0, XF86XK_Launch1, XF86XK_Launch2, XF86XK_Launch3, + XF86XK_Launch4, XF86XK_Launch5, XF86XK_Launch6, XF86XK_Launch7, + XF86XK_Launch8, XF86XK_Launch9, XF86XK_LaunchA, XF86XK_LaunchB, + XF86XK_LaunchC, XF86XK_LaunchD, XF86XK_LaunchE, XF86XK_LaunchF, + XF86XK_ApplicationLeft, XF86XK_ApplicationRight, XF86XK_Book, XF86XK_CD, + XF86XK_Calculater, XF86XK_Clear, XF86XK_Close, XF86XK_Copy, XF86XK_Cut, + XF86XK_Display, XF86XK_DOS, XF86XK_Documents, XF86XK_Excel, XF86XK_Explorer, + XF86XK_Game, XF86XK_Go, XF86XK_iTouch, XF86XK_LogOff, XF86XK_Market, + XF86XK_Meeting, XF86XK_MenuKB, XF86XK_MenuPB, XF86XK_MySites, XF86XK_New, + XF86XK_News, XF86XK_OfficeHome, XF86XK_Open, XF86XK_Option, XF86XK_Paste, + XF86XK_Phone, XF86XK_Q, XF86XK_Reply, XF86XK_Reload, XF86XK_RotateWindows, + XF86XK_RotationPB, XF86XK_RotationKB, XF86XK_Save, XF86XK_ScrollUp, + XF86XK_ScrollDown, XF86XK_ScrollClick, XF86XK_Send, XF86XK_Spell, + XF86XK_SplitScreen, XF86XK_Support, XF86XK_TaskPane, XF86XK_Terminal, + XF86XK_Tools, XF86XK_Travel, XF86XK_UserPB, XF86XK_User1KB, XF86XK_User2KB, + XF86XK_Video, XF86XK_WheelButton, XF86XK_Word, XF86XK_Xfer, XF86XK_ZoomIn, + XF86XK_ZoomOut, XF86XK_Away, XF86XK_Messenger, XF86XK_WebCam, + XF86XK_MailForward, XF86XK_Pictures, XF86XK_Music, XF86XK_Battery, + XF86XK_Bluetooth, XF86XK_WLAN, XF86XK_UWB, XF86XK_AudioForward, + XF86XK_AudioRepeat, XF86XK_AudioRandomPlay, XF86XK_Subtitle, + XF86XK_AudioCycleTrack, XF86XK_CycleAngle, XF86XK_FrameBack, + XF86XK_FrameForward, XF86XK_Time, XF86XK_Select, XF86XK_View, + XF86XK_TopMenu, XF86XK_Red, XF86XK_Green, XF86XK_Yellow, XF86XK_Blue, + XF86XK_Suspend, XF86XK_Hibernate, XF86XK_TouchpadToggle, XF86XK_TouchpadOn, + XF86XK_TouchpadOff, XF86XK_AudioMicMute, XF86XK_Switch_VT_1, + XF86XK_Switch_VT_2, XF86XK_Switch_VT_3, XF86XK_Switch_VT_4, + XF86XK_Switch_VT_5, XF86XK_Switch_VT_6, XF86XK_Switch_VT_7, + XF86XK_Switch_VT_8, XF86XK_Switch_VT_9, XF86XK_Switch_VT_10, + XF86XK_Switch_VT_11, XF86XK_Switch_VT_12, XF86XK_Ungrab, XF86XK_ClearGrab, + XF86XK_Next_VMode, XF86XK_Prev_VMode, XF86XK_LogWindowTree, + XF86XK_LogGrabInfo, +};