Skip to content

Add named switches with normal and ordered hold actions - #731

Merged
enaboapps merged 13 commits into
mainfrom
feat/usahp-switch-assignments
Sep 12, 2026
Merged

enaboapps merged 13 commits into
mainfrom
feat/usahp-switch-assignments

Conversation

@enaboapps

@enaboapps enaboapps commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Add Settings → Switches for named keyboard switches with normal release actions and ordered hold actions. Switchify now owns local capture through platform adapters; USAHP is no longer a dependency.

Settings UI

Each switch is a collapsed row showing its name, key badge and an action summary, with Edit and Remove as the only controls. Edit expands an inline editor: name, key with Change key, press action, and hold actions as numbered rows with reorder and remove icons plus a live preview of when each hold action is offered. Add switch starts learning immediately and Save stays disabled until both name and key exist. Hold timing shows five presets, an exact-interval select and the resulting emergency hold duration. Guidance, save status and errors use the shared Settings note and error styles.

While learning, a modal dialog holds keyboard focus on itself and swallows every key event, because on Windows a reserved key's release still reaches the WebView and would click a focused button or scroll Settings. Escape cancels through the backend hotkey; Cancel capture is mouse-only. Key errors are shown under the row that was learning, focus returns to the row after collapsing, and an unavailable key is announced as well as coloured.

Platform behaviour

Windows reserves plain assigned keys with global hotkeys and receives releases through Raw Input. This addresses the observed setup where hardware Space never reached the low-level keyboard hook. Registration conflicts prevent enabling and roll back reservations. Cancellation discards pending actions and drains held keys. macOS retains event-tap capture. Existing settings, scanning techniques and gesture behavior remain compatible.

On Windows the learning command checks that the main window is the foreground window rather than tao's focus flag. That flag turns false once keyboard focus moves into the WebView2 child, which refused every capture started after the first click in the page.

The shared scanner dispatches Select, Next, Previous, Reverse, Stop and Pause/resume. Holding freezes movement and displays a native nonactivating release prompt. Settings migrate old point-scan bindings once and save before scanning starts.

Closes #730.

Validation

At 2cabfb1: tsc, 130 frontend tests plus 5 Node tests, production build, cargo fmt, Clippy all targets with warnings denied, 300 Rust unit tests and 7 integration tests passed on Windows. Tests use fake input and do not send desktop input. A review of 39de3a1 found two frontend state bugs and several accessibility gaps; all are fixed in 2cabfb1 with tests.

Manual evidence: in the dev build with the window focused, Add switch opened the capture modal against the real backend, and Cancel capture returned it to idle; no key events were sent by the agent. A standalone probe earlier received hardware Space through Raw Input and global hotkeys while the low-level hook received no events. Hold timing, final click behavior, a real switch press through the modal and broader foreground-app compatibility remain manual checks.

Known Windows limitations are shown in Settings and documented: key releases may reach other apps; modified combinations are not reserved; F12 is unavailable. This adapter does not claim complete key suppression. macOS interaction needs validation on macOS.

Open question: focus-loss cancellation still relies on tao's Focused(false) event, which may not fire on Windows once the WebView2 child owns keyboard focus, so learning may not end when another app comes to the front. Needs a manual check and possibly a foreground poll in the capture watchdog.

@enaboapps
enaboapps marked this pull request as ready for review September 12, 2026 13:51
@greptile-apps

greptile-apps Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Greptile Summary

Adds named keyboard switches, ordered hold actions, local Windows/macOS input adapters, switch configuration UI, persistence, and shared scanning gesture dispatch.

  • Replaces the global-shortcut dependency with platform-specific switch capture.
  • Adds normal-release and ordered hold-action processing with native prompts.
  • Migrates legacy point-scan bindings and integrates switch assignments with scanning.
  • Restores focus to the new-switch draft after a removal and correctly counts exact-length automatic-scan wraps.

Confidence Score: 5/5

Safe to merge.

The prior draft-focus issue is fixed in the current implementation. enaboapps manually resolved the automatic-scan cycle-counting thread without explanation; the current implementation counts a wrap when scanning leaves the final or initial grid slot, including an exact-length step.

Reviews (7): Last reviewed commit: "Count exact-length passes and keep focus..." | Re-trigger Greptile

@enaboapps
enaboapps marked this pull request as draft September 12, 2026 16:03
@enaboapps
enaboapps marked this pull request as ready for review September 12, 2026 16:11
OwenMcGirr and others added 5 commits September 12, 2026 17:54
Each switch is now a collapsed row showing name, key badge and an action
summary, with Edit and Remove as the only controls. Expanding a row opens a
stacked editor: name, key with Change key, press action, and hold actions
as numbered rows with icon reorder buttons and a live timing preview.
Adding a switch starts learning immediately and Save stays disabled until
both name and key exist. Learning shows a highlighted capture panel inside
the affected row, and key errors sit under the key field.

Guidance moves into the shared note and disclosure style, save state and
errors use the existing note and error band, and the empty list uses the
standard empty state. Hold timing gets five presets, an exact-interval
select, and the emergency hold duration alongside it. Fixes the unstyled
fieldset border around the interval group.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The hook records a capture refusal as both the general error and the
capture error, so the Switches tab showed the same message twice when
the window was not focused. The error band now carries save errors only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On Windows the reserved press never reaches the page but its release
does, and a Space release on a focused button clicks it, while an
unreserved key would scroll Settings. A separate window is not an
option because losing main-window focus cancels the capture. While
learning, a dialog now holds focus on itself rather than a control and
swallows every key event at the document capture phase. Escape still
reaches the backend through its hotkey; Cancel capture is mouse-only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tao tracks focus with a flag flipped by WM_SETFOCUS and WM_KILLFOCUS on
the top-level window, but keyboard focus lives in the WebView2 child.
After the first click inside the page is_focused stays false while the
app is plainly in front, so begin_switch_capture refused every mouse
user with "Focus Switchify PC before learning a switch." Compare the
window against GetForegroundWindow instead; macOS keeps is_focused.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A capture that ended without a key left its target set, so a later view
carrying the backend's remembered key could assign it to the refused row
or raise a false duplicate. The target is now released whenever a
capture ends keyless, and the error is recorded against the row that was
learning rather than shown under whichever row is open next.

Done, Save switch, Cancel and Remove unmounted the clicked button and
dropped focus to the page; focus now returns to the row's Edit button or
to Add switch. Edit gains aria-controls, the capture dialog links its
body text and says Cancel is mouse-only, and an unavailable key is
announced in the collapsed row instead of shown by colour alone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread src/settings/SwitchesSection.tsx
An unattended automatic scan swept forever. The technique now reports
when automatic movement has wrapped the current phase MAX_SCAN_CYCLES
times and the session resets to idle, exactly as Stop would. Manual
Next and Back steps never count, and each Select starts a fresh count
for the phase it enters. The limit is a constant of three for now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread src-tauri/src/point_scan.rs Outdated
OwenMcGirr and others added 2 commits September 12, 2026 21:09
Tauri rewrites the autogenerated permission files on every dev build,
and with core.autocrlf on Windows git then reported them as modified
despite identical content. A .gitattributes rule fixes their EOL. The
docs and a test name also used a word the identity check rejects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A step of exactly the region length wrapped back to the same edge, so
the before/after comparison missed the pass and small grid cells at
the top speed could scan past the three-pass limit. The wrap flag now
comes from the overflow itself, and cyclic indexes wrap on leaving
their last slot. Removing a switch while a new one is being drafted
sent focus to the hidden Add button; it now lands on the draft's name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@enaboapps
enaboapps merged commit 6acf17f into main Sep 12, 2026
7 checks passed
@enaboapps enaboapps added this to the v1.0.0-rc.7 milestone Sep 13, 2026
@enaboapps
enaboapps deleted the feat/usahp-switch-assignments branch September 22, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add named switch assignments and hold actions with app-owned keyboard capture

2 participants