From 670c9845129fc0c4b6e0192d657eea05e1a64b49 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Wed, 13 May 2026 19:47:00 +0200 Subject: Expose key bindings over IPC so a launcher can list them Asked whether srdwm's bindings show in the AGS launcher. They could not: srd.bind lives entirely in the Lua engine, and nothing published a binding anywhere a client could read it. There was no IPC command, no field in any response, and bound_keys() was used only by main.rs to register grabs. srd.bind now takes an optional third argument, a description, and the loaded set is copied into the WindowManager after the initial load and after every reload. Core neither owns nor interprets them - it has no Lua state and never dispatches a key - it holds them so the IPC layer, which is handed a WindowManager and nothing else, can serve them. New `srd keybindings` returns combo and description pairs, sorted so a UI listing them does not reshuffle on every refresh. Every binding in the shipped config now carries a description, so the feature is useful without the user writing any. Verified live in a nested instance: 46 bindings published, 0 without a description, and editing the config file updated the list without a restart (which also exercised reload-on-write again). Also verified, for the separate report that windows cannot be moved to another workspace from the AGS workspace pills: the compositor side works. `srd dispatch move workspace 2` moved a window from workspace 1 to 2 and correctly hid it, since workspace 1 was active. Nothing to fix here; the missing piece is on the shell side. 515 tests pass, clippy clean. --- crates/config/src/engine/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'crates/config/src/engine/mod.rs') diff --git a/crates/config/src/engine/mod.rs b/crates/config/src/engine/mod.rs index ac7bdb8..8facd56 100644 --- a/crates/config/src/engine/mod.rs +++ b/crates/config/src/engine/mod.rs @@ -26,6 +26,15 @@ struct SharedState { wm: Rc>, values: HashMap, key_bindings: HashMap, + /// Human-readable description for a binding, from `srd.bind`'s optional + /// third argument. Separate from `key_bindings` so a binding without one + /// costs nothing, and cleared alongside it on reload. + /// + /// Exists so a launcher or cheat-sheet can list what the keys actually + /// do - a bare combo is close to useless in a UI. Published to + /// `WindowManager` by `main.rs` and served over IPC as + /// `srd keybindings`. + key_descriptions: HashMap, /// Combos registered with `srd.bind_repeat`, which fire repeatedly while /// held (Hyprland's `binde`). A subset of `key_bindings`. repeat_keys: std::collections::HashSet, @@ -67,6 +76,7 @@ impl Engine { wm, values: default_config(), key_bindings: HashMap::new(), + key_descriptions: HashMap::new(), repeat_keys: std::collections::HashSet::new(), event_handlers: HashMap::new(), config_dir: config_dir.into(), @@ -189,6 +199,18 @@ impl Engine { self.state.borrow().key_bindings.keys().cloned().collect() } + /// Every binding as `(combo, description)`, sorted by combo so the list + /// is stable between calls - a UI listing them should not reshuffle on + /// every refresh. `description` is empty when the binding did not give + /// one. + pub fn bound_keys_with_descriptions(&self) -> Vec<(String, String)> { + let state = self.state.borrow(); + let mut out: Vec<(String, String)> = + state.key_bindings.keys().map(|combo| (combo.clone(), state.key_descriptions.get(combo).cloned().unwrap_or_default())).collect(); + out.sort(); + out + } + /// Combos that should auto-repeat while held. pub fn repeat_keys(&self) -> Vec { self.state.borrow().repeat_keys.iter().cloned().collect() -- cgit v1.2.3