diff options
| -rw-r--r-- | docs/superpowers/specs/2026-09-25-keybinds-design.md | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-09-25-keybinds-design.md b/docs/superpowers/specs/2026-09-25-keybinds-design.md new file mode 100644 index 0000000..e516c77 --- /dev/null +++ b/docs/superpowers/specs/2026-09-25-keybinds-design.md @@ -0,0 +1,64 @@ +# Keybind reminder design + +A full-screen overlay listing every Hyprland keybind with what it does, opened +with `SUPER + k`. It exists to recall shortcuts, so it must never show a bind +that is not live or miss one that is. + +## Source of truth + +`hyprctl binds -j`, run on every opening. This is what Hyprland has loaded, +including binds generated in Lua loops, and it stays correct when +`keybindings.lua` has been edited but not reloaded. Parsing the Lua file was +rejected for exactly those two cases. No cache: the call is cheap and a cache +is the only way the list could go stale. + +`hyprctl` reports every Lua action as `dispatcher: "__lua"` with an opaque +numeric `arg`, so the action text comes from the bind's `description`, a +documented `HL.BindOptions` field (`/usr/share/hypr/stubs/hl.meta.lua`). + +## Hypr side (`hypr-theme` repo) + +- Every `hl.bind` gains `{ description = "Group: Label" }`, merged into any + options it already has (`locked`, `repeating`, `mouse`). Loop-generated + binds build theirs in the loop, e.g. `"Workspaces: Go to 3"`. +- The text before the first `": "` is the group heading in the overlay. +- New bind: `SUPER + k` → `qs -p <repo>/keybinds ipc call keybinds toggle`, + description `"Help: Keybind reminder"`. +- `autostart.lua` starts `qs -p <repo>/keybinds`. +- `decorations.lua` gains a `blur-keybinds` layer rule matching namespace + `^(quickshell-keybinds)$`, copied from `blur-window-switcher`. +- `SUPER + s` is defined in `dashboard.lua`, a symlink into the + `conky-theme-udt` repo; its description is a commit there. + +## Quickshell side: `keybinds/` + +- `shell.qml`: `ShellRoot` with the overlay and an `IpcHandler` target + `keybinds` exposing `toggle`, `show`, `close`. +- `Keybinds.qml`: `Scope` holding the 1x1 click-through keepalive + `PanelWindow` and a `LazyLoader` overlay on `root.open`. Overlay layer, + namespace `quickshell-keybinds`, `ExclusionMode.Ignore`, exclusive keyboard. + A focused `Item` handles Esc; a click anywhere closes. +- Opening sets `running = false` then `true` on a `Process` running + `hyprctl binds -j`; `StdioCollector` output is parsed on exit. +- `Binds.js` (pure, `.pragma library`): `mods(mask)` decodes SHIFT=1, + CTRL=4, ALT=8, SUPER=64 in the order SUPER, CTRL, ALT, SHIFT; + `group(binds)` returns `[{ name, rows: [{ keys, label }] }]`, groups in + first-seen order. A bind with no description goes to group `Undescribed` + with its raw key as label, so gaps stay visible. Mouse binds show as + written (`mouse:272`). +- Layout: groups flow in a `Flow` of columns, each a heading plus + `keys label` rows. Theme from the `Theme.qml` symlink to + `../shared/Theme.qml`; glyph-free. +- While loading or on `hyprctl` failure, the overlay says so rather than + drawing empty. +- `README.md`, GPLv2 headers on every file. + +## Testing + +`keybinds/test_binds.js`, runnable with `node` (it strips the `.pragma` line and evaluates `Binds.js`), asserts `mods` and `group` +against a fixture shaped like `hyprctl binds -j`. Visual check by the user. + +## Out of scope + +Search box (add if the list outgrows a screen, ~80 binds today). Submaps (none +in use). |
