diff options
| author | srdusr <[email protected]> | 2024-05-14 00:42:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2024-05-14 00:42:00 +0200 |
| commit | dd31bf5ac2a692617d478478d36c733586b93cf8 (patch) | |
| tree | 2adb42ad8b24172cbbea3bf362a0d284acd54439 /crates/config/src | |
| parent | a9dd8a6d4947cb537f7919c5a66ba4624af61800 (diff) | |
| download | srdwm-dd31bf5ac2a692617d478478d36c733586b93cf8.tar.gz srdwm-dd31bf5ac2a692617d478478d36c733586b93cf8.zip | |
Draw a cursor, handle the lid, and everything a real config needs
Groundwork for actually daily-driving this: porting the user's Hyprland
config exposed what srdwm couldn't yet express, and testing on a bare TTY
exposed something worse.
A visible mouse cursor. Nothing drew a pointer at all - on a bare TTY the
mouse was simply invisible. It hid because the nested backend runs inside
another compositor, which draws a cursor over srdwm's window; only the DRM
backend, i.e. the actual session path, was affected. A built-in arrow is now
composited above everything on the output the pointer is on. It's a
reviewable ASCII bitmap rather than an XCursor theme: a cursor that is always
present beats a prettier one that sometimes isn't, the same reasoning as
decoration.rs's font fallback. Client-set cursor surfaces and named shapes
are still not rendered, so an app asking for an I-beam gets the arrow.
Lid switch. libinput switch events are handled and surfaced to config as
srd.on("lid_closed"/"lid_open", fn), so closing the lid can lock and suspend
instead of doing nothing.
Config-driven additions, each needed by a binding in the ported config and
none of which existed: fullscreen (Window.fullscreen was a dead field --
declared, never read or written), directional window move that swaps with
the neighbour and reorders the stack so tiling follows, focus cycling,
modifier+drag to move/resize anywhere in a window rather than only by the
titlebar, modifier+scroll to change workspace, and 8 XF86 media/power
keysyms taken from the system's own XF86keysym.h. The keysym tables are
hand-maintained in both directions and a key missing from either fails
silently, so a round-trip test now covers every one the configs bind.
Verified in the QEMU VM: a bare-TTY screendump shows a recognisable arrow at
the pointer position (113 white fill + 58 black outline pixels at screen
centre, where the pointer starts).
Diffstat (limited to 'crates/config/src')
| -rw-r--r-- | crates/config/src/lib.rs | 102 |
1 files changed, 95 insertions, 7 deletions
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 911f259..ec8c7e8 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -27,6 +27,11 @@ struct SharedState { wm: Rc<RefCell<WindowManager>>, values: HashMap<String, ConfigValue>, key_bindings: HashMap<String, RegistryKey>, + /// Handlers for non-key events (currently the lid switch), registered + /// via `srd.on(...)`. Kept separate from `key_bindings` because the + /// backends use that map to decide which *keypresses* to withhold from + /// clients - a pseudo-entry there would be grabbed as if it were a key. + event_handlers: HashMap<String, RegistryKey>, config_dir: PathBuf, log: Vec<String>, running: Rc<std::cell::Cell<bool>>, @@ -60,6 +65,7 @@ impl Engine { wm, values: default_config(), key_bindings: HashMap::new(), + event_handlers: HashMap::new(), config_dir: config_dir.into(), log: Vec::new(), running: Rc::new(std::cell::Cell::new(true)), @@ -110,6 +116,24 @@ impl Engine { /// Runs the Lua function bound to `combo` (e.g. `"Mod4+Return"`), if any. /// Returns `true` if a binding existed and ran without erroring. + /// Runs the `srd.on(name, ...)` handler for a non-key event, if any. + /// Returns false when nothing is registered, so callers can log it. + pub fn dispatch_event(&self, name: &str) -> bool { + let func = { + let state = self.state.borrow(); + state.event_handlers.get(name).and_then(|key| self.lua.registry_value::<mlua::Function>(key).ok()) + }; + match func { + Some(f) => { + if let Err(e) = f.call::<_, ()>(()) { + log::error!("event handler '{name}' errored: {e}"); + } + true + } + None => false, + } + } + pub fn dispatch_keybinding(&self, combo: &str) -> bool { let func = { let state = self.state.borrow(); @@ -140,6 +164,7 @@ impl Engine { srd.set("reset_all", self.fn_reset_all()?)?; srd.set("reset_category", self.fn_reset_category()?)?; srd.set("bind", self.fn_bind()?)?; + srd.set("on", self.fn_on()?)?; srd.set("rule", self.fn_rule()?)?; srd.set("load", self.fn_load()?)?; srd.set("spawn", self.fn_spawn()?)?; @@ -160,7 +185,11 @@ impl Engine { window.set("close", self.fn_window_action(WindowAction::Close)?)?; window.set("minimize", self.fn_window_action(WindowAction::Minimize)?)?; window.set("maximize", self.fn_window_action(WindowAction::Maximize)?)?; + window.set("fullscreen", self.fn_window_action(WindowAction::Fullscreen)?)?; window.set("focus", self.fn_window_focus_direction()?)?; + window.set("move", self.fn_window_move_direction()?)?; + window.set("next", self.fn_window_cycle(true)?)?; + window.set("prev", self.fn_window_cycle(false)?)?; window.set("set_decorations", self.fn_window_set_decorations()?)?; window.set("set_border_color", self.fn_window_set_border_color()?)?; window.set("set_border_width", self.fn_window_set_border_width()?)?; @@ -269,6 +298,24 @@ impl Engine { })?) } + /// `srd.on("lid_closed", function() ... end)` - registers a handler for + /// a non-key event. Currently `"lid_closed"` and `"lid_open"`. + fn fn_on(&self) -> Result<mlua::Function<'_>> { + let state = self.state.clone(); + Ok(self.lua.create_function(move |lua, (name, f): (String, mlua::Function)| { + const KNOWN: [&str; 2] = ["lid_closed", "lid_open"]; + if !KNOWN.contains(&name.as_str()) { + return Err(mlua::Error::RuntimeError(format!( + "srd.on: unknown event '{name}' (known: {})", + KNOWN.join(", ") + ))); + } + let key = lua.create_registry_value(f)?; + state.borrow_mut().event_handlers.insert(name, key); + Ok(()) + })?) + } + fn fn_bind(&self) -> Result<mlua::Function<'_>> { let state = self.state.clone(); Ok(self.lua.create_function(move |lua, (combo, f): (String, mlua::Function)| { @@ -475,6 +522,7 @@ impl Engine { WindowAction::Close => wm.close_window(id), WindowAction::Minimize => wm.minimize_window(id), WindowAction::Maximize => wm.toggle_maximize(id), + WindowAction::Fullscreen => wm.toggle_fullscreen(id), WindowAction::ToggleFloating => wm.toggle_floating(id), } } @@ -482,16 +530,43 @@ impl Engine { })?) } + /// `srd.window.move("left")` - swap the focused window with its + /// neighbour in that direction (Hyprland's `movewindow l/r/u/d`). + fn fn_window_move_direction(&self) -> Result<mlua::Function<'_>> { + let state = self.state.clone(); + Ok(self.lua.create_function(move |_, direction: String| { + let dir = parse_direction(&direction, "srd.window.move")?; + let wm = state.borrow().wm.clone(); + wm.borrow_mut().move_window_direction(dir); + Ok(()) + })?) + } + + /// `srd.window.next()` / `srd.window.prev()` - cycle focus through the + /// windows on the current workspace (Hyprland's `cyclenext`). + fn fn_window_cycle(&self, forward: bool) -> Result<mlua::Function<'_>> { + let state = self.state.clone(); + Ok(self.lua.create_function(move |_, ()| { + let wm = state.borrow().wm.clone(); + let mut wm = wm.borrow_mut(); + if forward { + wm.focus_next(); + } else { + wm.focus_previous(); + } + // Bring it to the top, matching the `bringactivetotop` the + // Hyprland binding pairs with `cyclenext`. + if let Some(id) = wm.focused_id() { + wm.raise_window(id); + } + Ok(()) + })?) + } + fn fn_window_focus_direction(&self) -> Result<mlua::Function<'_>> { let state = self.state.clone(); Ok(self.lua.create_function(move |_, direction: String| { - let dir = match direction.as_str() { - "left" => Direction::Left, - "right" => Direction::Right, - "up" => Direction::Up, - "down" => Direction::Down, - other => return Err(mlua::Error::RuntimeError(format!("srd.window.focus: unknown direction '{other}'"))), - }; + let dir = parse_direction(&direction, "srd.window.focus")?; let wm = state.borrow().wm.clone(); wm.borrow_mut().focus_direction(dir); Ok(()) @@ -643,11 +718,24 @@ impl Engine { } } +/// Shared by `srd.window.focus` and `srd.window.move` so both accept +/// exactly the same direction names and report the same error. +fn parse_direction(name: &str, caller: &str) -> mlua::Result<Direction> { + match name { + "left" => Ok(Direction::Left), + "right" => Ok(Direction::Right), + "up" => Ok(Direction::Up), + "down" => Ok(Direction::Down), + other => Err(mlua::Error::RuntimeError(format!("{caller}: unknown direction '{other}'"))), + } +} + #[derive(Clone, Copy)] enum WindowAction { Close, Minimize, Maximize, + Fullscreen, ToggleFloating, } |