srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/core/src/lib.rs1
-rw-r--r--crates/core/src/manager/lock.rs34
-rw-r--r--crates/core/src/manager/mod.rs29
-rw-r--r--crates/platform/src/ipc/dispatch.rs7
-rw-r--r--crates/platform/src/ipc/types.rs5
-rw-r--r--crates/srdwm/src/main.rs27
-rw-r--r--docs/DEFAULTS.md17
7 files changed, 106 insertions, 14 deletions
diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs
index e9d31a5..9a2c3bc 100644
--- a/crates/core/src/lib.rs
+++ b/crates/core/src/lib.rs
@@ -19,6 +19,7 @@ pub use layout::{Layout, MasterStackLayout, NoOpLayout, TilingConfig};
pub use lock_config::LockConfig;
pub use manager::{CaptureRequest, ColorFilter, Direction, WindowManager};
pub use monitor::{Monitor, MonitorId};
+pub use manager::KeyBinding;
pub use placement::{centered_in, PlacementConfig, SmartPlacement, SnapZoneKind, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, SNAP_FLYOUT_EDGE};
pub use regex::Regex;
pub use rules::{WindowMatch, WindowRule, WindowRuleActions};
diff --git a/crates/core/src/manager/lock.rs b/crates/core/src/manager/lock.rs
index 15c0a9f..ecea5fe 100644
--- a/crates/core/src/manager/lock.rs
+++ b/crates/core/src/manager/lock.rs
@@ -52,6 +52,18 @@ impl WindowManager {
std::mem::take(&mut self.refresh_requested)
}
+ /// Records the combos the backend was given at startup, so
+ /// `KeyBinding::grabbed` can be answered. Called once, from `main.rs`,
+ /// right where that same list is handed to the platform.
+ pub fn set_grabbed_keys(&mut self, combos: &[String]) {
+ self.grabbed_keys = combos.iter().cloned().collect();
+ }
+
+ /// Whether `combo` is one the backend actually grabs.
+ pub fn is_grabbed(&self, combo: &str) -> bool {
+ self.grabbed_keys.contains(combo)
+ }
+
/// Records that `key` was set live to `value_json`. See
/// `live_settings`' own doc comment.
///
@@ -83,6 +95,28 @@ mod tests {
}
#[test]
+ fn only_the_combos_handed_to_the_backend_count_as_grabbed() {
+ // The whole point of the flag: a binding added after startup is
+ // registered by the config engine but never grabbed, so pressing it
+ // goes to the focused client instead.
+ let mut wm = WindowManager::new();
+ assert!(!wm.is_grabbed("Mod4+Return"), "nothing grabbed before startup records a set");
+ wm.set_grabbed_keys(&["Mod4+Return".to_string(), "Ctrl+Mod4+l".to_string()]);
+ assert!(wm.is_grabbed("Mod4+Return"));
+ assert!(wm.is_grabbed("Ctrl+Mod4+l"));
+ assert!(!wm.is_grabbed("Ctrl+Shift+Mod4+F9"), "added since startup: bound, not grabbed");
+ }
+
+ #[test]
+ fn recording_the_grabbed_set_again_replaces_it_rather_than_accumulating() {
+ let mut wm = WindowManager::new();
+ wm.set_grabbed_keys(&["Mod4+a".to_string()]);
+ wm.set_grabbed_keys(&["Mod4+b".to_string()]);
+ assert!(!wm.is_grabbed("Mod4+a"), "the previous set must not linger");
+ assert!(wm.is_grabbed("Mod4+b"));
+ }
+
+ #[test]
fn drain_refresh_request_is_true_once_then_false() {
let mut wm = WindowManager::new();
assert!(!wm.drain_refresh_request(), "nothing requested yet");
diff --git a/crates/core/src/manager/mod.rs b/crates/core/src/manager/mod.rs
index a225220..64cf5c3 100644
--- a/crates/core/src/manager/mod.rs
+++ b/crates/core/src/manager/mod.rs
@@ -85,6 +85,29 @@ struct ResizeState {
/// state and layout policy. Backends (X11, Wayland, ...) drive this via
/// `add_window`/`remove_window`/input events, and apply the `Rect`s it
/// computes back onto real surfaces.
+/// One key binding, as reported by `srd keybindings`.
+#[derive(Debug, Clone)]
+pub struct KeyBinding {
+ /// Canonical form (`"Ctrl+Mod4+l"`), which is what a real keypress is
+ /// matched against - not necessarily how it was written in the config.
+ pub combo: String,
+ /// `srd.bind`'s optional third argument, empty when it gave none.
+ pub description: String,
+ /// Whether the compositor actually intercepts this key.
+ ///
+ /// `main.rs` hands the backend one combo list, once, before connecting:
+ /// the X11 backend turns it into `XGrabKey` calls and the Wayland one
+ /// into its intercept set. A reload re-registers the *actions* but
+ /// cannot re-register the grabs, so a combination added to the config
+ /// since startup is bound as far as the config engine is concerned and
+ /// still goes straight to the focused client when pressed.
+ ///
+ /// Reported here so a launcher or cheat-sheet can say so rather than
+ /// listing a shortcut that silently does nothing - which is exactly
+ /// the failure this field exists to make visible.
+ pub grabbed: bool,
+}
+
pub struct WindowManager {
windows: HashMap<WindowId, Window>,
order: Vec<WindowId>,
@@ -449,7 +472,10 @@ pub struct WindowManager {
/// Asked for as whether "our bindings show in ags's launcher": they
/// could not, because nothing published them anywhere a client could
/// read.
- pub keybindings: Vec<(String, String)>,
+ pub keybindings: Vec<KeyBinding>,
+ /// The exact combo list handed to the backend at startup, which is the
+ /// only set it ever grabs - see `KeyBinding::grabbed`.
+ grabbed_keys: std::collections::HashSet<String>,
/// Set by `request_refresh`, drained by the main loop. Same
/// cross-boundary queued-request shape as `lock_requested`.
refresh_requested: bool,
@@ -640,6 +666,7 @@ impl WindowManager {
theme: ThemeConfig::default(),
lock: LockConfig::default(),
keybindings: Vec::new(),
+ grabbed_keys: std::collections::HashSet::new(),
maximize_covers_dock: true,
refresh_requested: false,
live_settings: std::collections::BTreeMap::new(),
diff --git a/crates/platform/src/ipc/dispatch.rs b/crates/platform/src/ipc/dispatch.rs
index ad79b98..b0f0cba 100644
--- a/crates/platform/src/ipc/dispatch.rs
+++ b/crates/platform/src/ipc/dispatch.rs
@@ -424,8 +424,11 @@ pub(crate) fn handle_request(line: &[u8], wm: &std::rc::Rc<std::cell::RefCell<Wi
// outside this process could see a binding at all before this.
"keybindings" => {
let wm = wm.borrow();
- let list: Vec<KeybindingInfo> =
- wm.keybindings.iter().map(|(combo, description)| KeybindingInfo { combo: combo.clone(), description: description.clone() }).collect();
+ let list: Vec<KeybindingInfo> = wm
+ .keybindings
+ .iter()
+ .map(|b| KeybindingInfo { combo: b.combo.clone(), description: b.description.clone(), grabbed: b.grabbed })
+ .collect();
(serde_json::to_vec(&KeybindingsResponse { keybindings: list }).unwrap_or_default(), false)
}
"set" => handle_set(&req, wm),
diff --git a/crates/platform/src/ipc/types.rs b/crates/platform/src/ipc/types.rs
index 286df20..3243e31 100644
--- a/crates/platform/src/ipc/types.rs
+++ b/crates/platform/src/ipc/types.rs
@@ -129,6 +129,11 @@ pub(crate) struct MonitorsResponse {
pub(crate) struct KeybindingInfo {
pub(crate) combo: String,
pub(crate) description: String,
+ /// `false` when the config binds this combo but the compositor does not
+ /// actually intercept it - see `srdwm_core::KeyBinding::grabbed`. A UI
+ /// listing bindings should say so rather than showing a shortcut that
+ /// silently does nothing.
+ pub(crate) grabbed: bool,
}
#[derive(Serialize)]
diff --git a/crates/srdwm/src/main.rs b/crates/srdwm/src/main.rs
index 85e5427..6c2f04b 100644
--- a/crates/srdwm/src/main.rs
+++ b/crates/srdwm/src/main.rs
@@ -111,7 +111,15 @@ const CONFIG_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_secs
/// shows up without a restart - unlike the *grab*, which `main.rs` only
/// registers once at startup (see `Engine::reload`'s own doc comment).
fn publish_keybindings(engine: &Engine, wm: &Rc<RefCell<WindowManager>>) {
- wm.borrow_mut().keybindings = engine.bound_keys_with_descriptions();
+ let listed = engine.bound_keys_with_descriptions();
+ let mut wm = wm.borrow_mut();
+ wm.keybindings = listed
+ .into_iter()
+ .map(|(combo, description)| {
+ let grabbed = wm.is_grabbed(&combo);
+ srdwm_core::KeyBinding { combo, description, grabbed }
+ })
+ .collect();
}
/// Puts a config error in front of the user instead of only in a log they
@@ -723,20 +731,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut last_config_mtime = config_mtime(&dir);
let mut last_config_poll = std::time::Instant::now();
+ // One snapshot, handed to whichever backend is built below and also
+ // recorded on the `WindowManager` so `srd keybindings` can report which
+ // combos are really grabbed. Taken here rather than inside each arm so
+ // the reported set and the grabbed set cannot drift apart.
+ let startup_combos = engine.bound_keys();
+ wm.borrow_mut().set_grabbed_keys(&startup_combos);
+ publish_keybindings(&engine, &wm);
let mut platform: Box<dyn Platform> = match kind {
#[cfg(all(unix, not(target_os = "macos")))]
PlatformKind::X11 => {
let mut p = srdwm_x11::X11Platform::connect(wm.clone())?;
- let combos = engine.bound_keys();
- p.grab_keybindings(&combos)?;
- log::info!("grabbed {} keybinding(s)", combos.len());
+ p.grab_keybindings(&startup_combos)?;
+ log::info!("grabbed {} keybinding(s)", startup_combos.len());
Box::new(p)
}
#[cfg(all(unix, not(target_os = "macos")))]
PlatformKind::Wayland => {
- let combos = engine.bound_keys();
- log::info!("{} keybinding(s) will be intercepted from clients", combos.len());
- srdwm_wayland::connect(wm.clone(), &combos, &engine.repeat_keys())?
+ log::info!("{} keybinding(s) will be intercepted from clients", startup_combos.len());
+ srdwm_wayland::connect(wm.clone(), &startup_combos, &engine.repeat_keys())?
}
#[cfg(windows)]
PlatformKind::Windows => Box::new(srdwm_windows::WindowsPlatform::new()?),
diff --git a/docs/DEFAULTS.md b/docs/DEFAULTS.md
index abcdad1..daa65d8 100644
--- a/docs/DEFAULTS.md
+++ b/docs/DEFAULTS.md
@@ -942,9 +942,18 @@ with the window that spawned it.
description if it gave one:
```
-{"keybindings":[{"combo":"Mod4+Return","description":"Open a terminal"}, ...]}
+{"keybindings":[{"combo":"Mod4+Return","description":"Open a terminal","grabbed":true}, ...]}
```
+`grabbed` is `false` when the config binds that combo but the compositor
+does not actually intercept it. The backend is handed one combo list, once,
+before connecting - X11 turns it into `XGrabKey` calls, Wayland into its
+intercept set. A reload re-registers the *actions* but cannot re-register
+the grabs, so a combination added to the config since startup is bound as
+far as the config engine is concerned and still goes straight to the focused
+client when pressed. Anything listing bindings should show that rather than
+offering a shortcut that silently does nothing.
+
`srd.bind` takes an optional third argument for that description:
```lua
@@ -955,9 +964,9 @@ Bindings live entirely inside the Lua engine, so before this nothing outside
the compositor could see that a binding existed at all - a launcher or
cheat-sheet had no way to show the user their own keys. The list is
republished after every config reload, so a rebound key appears without a
-restart. Note that a brand new *combination* still needs a restart before the
-compositor grabs that key at all; an existing combination picks up its new
-action immediately.
+restart - with `grabbed: false` until the compositor is restarted, since an
+existing combination picks up its new action immediately but a brand new one
+is not intercepted until the next startup.
Combos are reported in canonical order (`Ctrl+Mod4+l`), which is what the
compositor matches a real keypress against - not necessarily how the combo