8.1 KiB
Input redesign — keycaps, search, and honest state
Approved mock: home-mocks/input.html (scratchpad, :8642). Spec wins over mock on conflict.
Goals
- Shortcuts browser: the 130-row wall becomes one searchable card — keycap chips, group
headers with counts, hover-revealed Change/Reset, inline capture. The rebinding engine
(
Keybinds.rebind, conflicts refused,keybindOverrides) already exists and is untouched. - Typing card modernized: dropdowns instead of ChoiceGrid tile walls; raw XKB string and variant into a collapsed Advanced section (still discoverable — a contract requires the raw string not be hidden away, collapsed-but-present satisfies it; C verifies the needle).
- Mouse & Touchpad: dropdowns over wide segmented rows, four new option groups (below), a live try-it area, and a phantom-filtered device list.
- Dictation status-first: ready hero, hotkeys as keycaps (looked up live from Keybinds so a rebind shows truthfully; literal fallback), guided setup steps with a real progress bar, and an on-page test field that uses the existing typing pipeline.
Non-goals: per-device settings (phantom-heavy device list, zero plumbing — deliberately
skipped), editing keybinds.lua actions (chords only, as today), touch/tablet options
(no hardware), accel custom curves.
New schema keys (group / hypr option — agent A verifies each option's exact name, type, and
value map against hyprctl descriptions before writing the entry; enum-hypr-map-contract and
schema-hypr-shape-contract must hold; every key also gets its prefs.get() read-back in
config/dot/hypr/input.lua with matching defaults — hypr-prefs-contract):
| Key | Group | Hyprland option | UI |
|---|---|---|---|
focusOnClose |
pointer | input:focus_on_close |
dropdown: "Most recently used" / "Under the pointer" |
cursorHideWhileTyping |
pointer | cursor:hide_on_key_press |
toggle "Hide pointer while typing" |
cursorWarpOnWorkspaceChange |
pointer | cursor:warp_on_change_workspace |
toggle "Jump pointer to the focused display" |
scrollMethod |
pointer | input:scroll_method |
dropdown (offer only map-published values; include "On a held button" only if the map allows) |
scrollButton |
pointer | input:scroll_button |
int row, visible only when scrollMethod is the button one |
touchpadClickfinger |
touchpad | input:touchpad:clickfinger_behavior |
toggle "Two-finger right-click" |
touchpadTapAndDrag |
touchpad | input:touchpad:tap-and-drag |
toggle "Tap and drag" |
Defaults = today's effective Hyprland/input.lua values so shipping changes nothing. If an option's published map/type makes a row above impossible as specced (e.g. scroll_method values), implement what the map allows and flag the difference loudly.
Service work (A)
InputDevices.qml: addrealMice/realKeyboards— name-filtered lists (exclude substrings:consumer-control,virtual,video-bus,power-button,webcam,audio,uinput, plus dedupe transceiver siblings by prefix), each entry{ name, pretty }(pretty = title-cased, dashes to spaces). Keep the existing flat lists andhasTouchpaduntouched (contract-pinned behavior).- Expose
mainKeyboardLayout(from the main keyboard's live layout string) for the devices card.
UI (B)
ShortcutsPage.qml (title stays "Keyboard"):
- Typing card: Layout dropdown (curated common layouts: English (US), English (UK), German,
French, Spanish, Nordic…, mapping to
keyboardLayoutcodes; a stored code outside the list renders as the raw code and the dropdown offers "Custom…" which reveals Advanced), Caps Lock dropdown, Compose dropdown, Layout-switching dropdown (all four presets keep writingkeyboardOptionsthrough the existing page-local XKB helpers — xkb-presets-contract), combined Key repeat row (delay + rate sliders), Num Lock toggle, then Advanced expander: rawkeyboardOptionsmono field +keyboardVariant. - Shortcuts card: header "130 bound · N changed" (live counts), subtitle "Click Change and
press the new keys. A shortcut another action holds is refused, never stolen.", filter field
(matches description + group, case-insensitive), grouped rows in
Keybinds.groupOrderwith "showing N of M" when filtered; each row: description, CHANGED badge when overridden, hover-revealed Change/Reset (Reset only on overridden), keycap chord. Capture swaps the chord area for the existingShortcutCaptureinline. "Restore every shipped shortcut" row stays, with the live differs-count detail. Keep the no-GNOME-handoff comment. - New component KeycapChord.qml: parses a display chord ("SUPER + SHIFT + Q") into keycap chips — mono font, tabular figures, modifier caps tinted accent, "+" separators muted. Reused by DictationPage.
MousePage.qml: Mouse card (speed slow/fast, Acceleration dropdown, Scroll speed, Natural
scrolling, Left-handed, Middle-click paste, Scroll method dropdown + conditional Scroll button
row) · Touchpad card (existing rows + the two new toggles; visible on hasTouchpad) ·
Pointer behavior card (Focus dropdown — the 4 followMouse values, focusOnClose dropdown,
hide-while-typing, hide-after slider with "Never" zero, warp toggle, Pointer size) ·
Try it card (new InputTestArea.qml: a scribble Canvas — repaints only on pointer motion,
cleared by a corner button — and a scrollable text strip; purely local, no compositor writes) ·
Connected devices card from InputDevices.realKeyboards/realMice (+ touchpad when
present), with the subtitle noting phantoms are filtered. Gestures rows fold into the Touchpad
card (swipe distance + invert) — the separate Gestures card goes.
DictationPage.qml: ready hero (state tile ✓ / … / ✗, title, model+size line) · hotkey rows
with KeycapChord, chords looked up from Keybinds.binds by matching the dictate descriptions
(fallback literals if not found) · Try it row: a read-only-styled TextField + "Test
dictation" button that focuses the field and drives panama-dictate start/stop through the
existing Dictation service — dictated text lands in the field via the normal wtype pipeline, no
new script plumbing; detail explains it types here instead of your document · Microphone
ActionRow → Sound (unchanged) · setup state replaces the hero with numbered steps (container
image / speech model with progress bar from downloadFraction / first transcription), driven
by the existing phase fields; errors keep their row.
Search & docs (C)
Extra entries: "Rebind a shortcut" → shortcuts; "Key repeat" auto via schema; "Pointer test area" → mouse; "Scroll method" auto; "Connected input devices" → mouse. Existing entries stay. Docs + launcher commands regenerate after schema lands (orchestrator).
Contracts (C — write, never run)
xkb-presets-contract: verify/adjust needles for the Advanced placement (raw string must remain present in the page source).keybinds-contract/keybind-rebind-contract: UI needles (Change/Reset/ShortcutCapture usage) reconciled with the rebuilt page; count-match and conflict rules unchanged.enum-hypr-map-contract/schema-hypr-shape-contract/hypr-prefs-contract: the seven new keys must satisfy all three (C statically replays where possible).settings-pages-contract,gnome-handoff-contractneedles re-verified.dictation-contract: confirm the test-field flow doesn't violate the "where text lands" pins (it uses the normal pipeline; the page merely owns focus). Flag, don't force, if it conflicts.- Backlog spec: Phase 8 section.
Agent ownership (parallel)
- A:
config/PreferenceSchema.qml(7 new keys),config/dot/hypr/input.lua,services/InputDevices.qml. - B:
modules/settings/ShortcutsPage.qml,MousePage.qml,DictationPage.qml, new components (KeycapChord.qml,InputTestArea.qml, others as needed) +modules/settings/qmldir. - C:
services/SettingsSearch.qml, the contracts above, backlog spec, README count line only if the count changes.
B programs against the schema keys and InputDevices API above; A must not change them without updating this spec.