srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/config/src
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-05-14 00:42:00 +0200
committersrdusr <[email protected]>2024-05-14 00:42:00 +0200
commitdd31bf5ac2a692617d478478d36c733586b93cf8 (patch)
tree2adb42ad8b24172cbbea3bf362a0d284acd54439 /crates/config/src
parenta9dd8a6d4947cb537f7919c5a66ba4624af61800 (diff)
downloadsrdwm-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.rs102
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,
}