keyhop 0.3.0

System-wide keyboard navigation overlay: drive your desktop without the mouse.
Documentation

keyhop

Drive your entire desktop from the keyboard. Press a leader chord, see hint labels on every clickable thing on screen, type the hint, done.

keyhop is a system-wide keyboard navigation layer that lets you control your whole computer without ever touching the mouse. Reaching for the mouse forces a constant context switch between thinking and pointing — your hands leave the home row, your eyes hunt for a cursor, and your flow breaks. keyhop keeps you on the keyboard so you stay fast, focused, and productive, using OS accessibility APIs (UI Automation on Windows) to target native UI elements semantically.

Status: v0.3.0 — Windows backend with full webpage content detection (Chromium browsers), multi-monitor window picker, lifecycle controls (keyhop --close, --clear-logs, daily log rotation), visual Settings dialog, and a real MSI installer (no MSVC toolchain required to install). Linux backend planned.

ci crates.io docs.rs license

Goals

  • Native performance and instant feel (sub-50ms hint overlay).
  • Semantic targeting via OS accessibility trees (UI Automation on Windows; AT-SPI on Linux when that backend lands).
  • Single, easy-to-install crate with platform backends gated behind cfg.

Crate layout

keyhop/
├─ src/
│  ├─ lib.rs               # public API: Action, Backend, Element, HintEngine
│  ├─ main.rs              # the `keyhop` binary (CLI flags, IPC, message loop)
│  ├─ model.rs
│  ├─ action.rs
│  ├─ backend.rs
│  ├─ hint.rs
│  ├─ config.rs            # TOML config (%APPDATA%/keyhop/config.toml)
│  └─ windows/             # Windows backend (cfg(windows) only)
│     ├─ mod.rs            # WindowsBackend (UI Automation tree walk + browser activation)
│     ├─ hotkey.rs         # global leader hotkeys + chord parser
│     ├─ overlay.rs        # transparent layered hint overlay + color parser
│     ├─ tray.rs           # system tray icon + context menu
│     ├─ settings_window.rs # visual Settings dialog (Win32)
│     ├─ startup.rs        # "launch at login" via HKCU Run key
│     ├─ notification.rs   # MessageBox-backed user notifications
│     ├─ window_picker.rs  # Alt-Tab-style window picker (multi-monitor, restores minimized)
│     ├─ single_instance.rs # named-mutex guard so only one keyhop runs
│     └─ ipc.rs            # hidden message-only window for `keyhop --close`
├─ wix/
│  └─ main.wxs             # WiX 3 source for the MSI installer
├─ docs/
│  └─ CODE_SIGNING.md      # Microsoft Trusted Signing setup (deferred)
└─ examples/
   └─ enumerate_foreground.rs

One package, one publish: keyhop ships both the binary and a reusable library API. Linux / Wayland / macOS backends will land as additional cfg-gated modules under src/.

Install

Windows (recommended) — MSI installer

Grab Keyhop-<version>-x86_64.msi from the latest GitHub Release and double-click it. The installer:

  • Installs to %ProgramFiles%\Keyhop\bin\keyhop.exe
  • Adds Keyhop to the system PATH so keyhop --close, keyhop --clear-logs, etc. work from any terminal
  • Creates a Start Menu shortcut
  • Registers a single Add/Remove Programs entry (uninstall via appwiz.cpl or Settings → Apps)
  • Cleanly closes a running instance during upgrades (Windows Installer's RestartManager)

For unattended installs (CI, fleet rollout, MDM, Microsoft Store automated validation):

msiexec /i Keyhop-0.3.0-x86_64.msi /qn

UAC will prompt once for admin rights (per-machine install).

Windows — portable EXE

If you don't want an installer, download keyhop.exe from the same release page, drop it anywhere, and run it. No registry footprint, no Start Menu entry — you're on your own for placing it on PATH and for upgrades.

Build from source (developers)

You'll need the MSVC toolchain (the regular Rust install on Windows pulls this in for you):

cargo install keyhop                    # from crates.io
# — or —
git clone https://github.com/rsaz/keyhop
cd keyhop
cargo install --path .

Requires:

  • Rust stable with the x86_64-pc-windows-msvc toolchain
  • Visual Studio Build Tools with the "Desktop development with C++" workload (provides link.exe)

If you see error: linker 'link.exe' not found, install the Build Tools — or just use the MSI installer above, which has no toolchain requirement.

Run

keyhop                         # release install (no console, logs to file)
cargo run --release            # from source (no console, logs to file)
cargo run                      # debug build (shows console with live logs)
cargo run --example enumerate_foreground

The binary uses the Windows GUI subsystem in release builds, running silently in the background with no console window. Logs are written to %LOCALAPPDATA%\keyhop\keyhop.log and can be viewed via the tray menu's "View Log" option. Debug builds (cargo run without --release) still show a console window for development convenience.

Flags

Flag What it does
-h, --help Print usage and exit.
-V, --version Print version and exit.
--no-tray Run without the system tray icon (hotkeys-only mode).
--close, --quit Cleanly shut down the running keyhop instance (uses a hidden IPC window). Exits 0.
--clear-logs Delete every keyhop*.log under %LOCALAPPDATA%\keyhop\. Run --close first if the live instance has the file open.

Using it

After launching, keyhop registers two global hotkeys and a tray icon. The default chords below can be changed from Settings... in the tray menu — see Configuration.

Action Default keys What it does
Pick element Ctrl + Shift + Space Hints every interactable control inside the focused window. Type one to invoke it.
Pick window Ctrl + Alt + Space Hints every visible top-level window across all monitors. Type one to focus it.
Confirm type the hint label Commits the selection.
Backspace Backspace Drops the last typed character (inside an overlay).
Cancel Esc Dismisses the current overlay without doing anything.
Settings Tray menu → Settings... Open the visual configuration dialog.
Quit Tray menu → Quit Cleanly exits keyhop.

The tray icon's right-click menu mirrors the two leader chords, gives you a Settings... entry to customize keyhop visually, and (in release builds) a View Log shortcut so you can debug without leaving the keyboard.

Yellow badges = element picker (will invoke the control). Orange badges = window picker (will focus the window). Both show the hint letters on the home row by default — colors and the alphabet are editable from Settings....

Web browsers

Ctrl+Shift+Space works inside Chromium-based browsers (Chrome, Edge, Brave, Opera, Vivaldi, Arc) and finds the actual links, buttons, file rows, nav tabs, and form inputs inside the rendered page — not just the browser chrome. keyhop wakes up Chromium's accessibility tree on demand by sending WM_GETOBJECT to every renderer HWND before walking, and descends 32 levels deep on browser foregrounds (vs 12 for desktop apps) since DOM trees routinely nest 15–25 deep. Firefox is on the roadmap for v0.4.x; today it surfaces only chrome elements.

Configuration

keyhop is configured through a small visual dialog reachable from the tray icon (Settings...), so you never have to touch a config file unless you want to. The dialog lets you change:

  • Hotkeys — both leader chords. Type any combination of Ctrl, Shift, Alt, Win/Super modifiers plus a key (A-Z, 0-9, F1-F24, Space, arrow keys, punctuation, etc.). Example: Ctrl+Alt+K.
  • Hint alphabet — the characters used to build hint labels. Default asdfghjkl (the home row).
  • Overlay colors — element badge background and window badge background, as #RRGGBB hex.
  • Overlay opacity — per-badge-style translucency from 0 to 100 (where 0 means "use the preset default" and 100 means fully opaque). Lower values let the underlying UI bleed through so you can see what you're about to invoke; the defaults sit around 90% for the element picker and 94% for the window picker.
  • Target indicator — when enabled, every element badge paints a thin outline (in the badge's background color) around the actual click target so it's instantly clear which underlying control each card represents — yellow badge, yellow rectangle around the matching element. When smart positioning had to push the badge off the element to dodge a collision, a connector line ending in a small filled triangle is also drawn from the badge to the element. On by default for the element picker; off for the window picker (which already shows a title pill). Toggle per picker via colors.element.show_leader / colors.window.show_leader in config.toml, or via the "Draw arrow from each badge to its target element" checkbox in the Settings dialog.
  • Launch at Windows startup — toggles a per-user entry in HKCU\Software\Microsoft\Windows\CurrentVersion\Run (no admin rights required).

Save validates everything (invalid hotkey strings or hex colors are rejected up-front), writes %APPDATA%\keyhop\config.toml, and shows a confirmation. Reset to Defaults deletes the config file. Hotkey, alphabet, and color changes apply on the next launch; the startup toggle takes effect immediately.

If a configured hotkey is already in use by another app at startup, keyhop reports the conflict via a notification dialog and continues running with the surviving chord (e.g. if pick-window conflicts but pick-element doesn't, the latter still works). Open Settings... to choose a different combination.

config.toml format (for power users)

The Settings dialog is the recommended path, but %APPDATA%\keyhop\config.toml is plain TOML if you prefer to script it:

[hotkeys]

pick_element = "Ctrl+Shift+Space"

pick_window  = "Ctrl+Alt+Space"



[hints]

alphabet = "asdfghjkl"



[colors.element]

badge_bg     = "#FFE500"  # leave empty to keep the default

badge_fg     = ""

border       = ""

opacity      = 0          # 0 = preset default; 1..100 = explicit percent

show_leader  = true       # omit the line entirely to keep the preset default

leader_color = ""         # hex pen color for the arrow; empty = preset default



[colors.window]

badge_bg     = "#33AAFF"

badge_fg     = ""

border       = ""

opacity      = 0

show_leader  = false

leader_color = ""



[startup]

launch_at_startup = false

Missing keys, missing sections, and an entirely missing file all fall back to the v0.1.0 defaults. Malformed TOML is logged and ignored — keyhop will start with defaults rather than refuse to run.

Library use

Beyond the binary, keyhop exposes a small library API. The Windows backend implements Backend, and the HintEngine is platform-agnostic.

use keyhop::{Backend, HintEngine};

#[cfg(windows)]
fn enumerate() -> anyhow::Result<()> {
    let mut backend = keyhop::windows::WindowsBackend::new()?;
    let elements = backend.enumerate_foreground()?;
    let hints = HintEngine::default().generate(elements.len());
    for (el, hint) in elements.iter().zip(hints.iter()) {
        println!("{hint}: {:?} {:?}", el.role, el.name);
    }
    Ok(())
}

The library surface is experimental while we're pre-1.0 — minor releases may break it. Pin a specific version if you embed it.

Roadmap

Shipped (v0.1.0)

  • Single-crate scaffold publishable to crates.io
  • Foreground window UI Automation tree walk
  • Global leader hotkeys + modal input
  • Hint overlay (transparent layered window) with collision resolution
  • Invoke action dispatch
  • Window picker mode (Alt-Tab with hints, all monitors)
  • Multi-monitor coordinate handling
  • System tray icon + context menu
  • CLI flags (--version, --help, --no-tray)
  • Single-instance guard
  • GUI-subsystem release builds (hidden console, file logging)

Shipped (v0.2.0)

  • Visual Settings dialog (no .toml editing required)
  • Configurable hotkeys via %APPDATA%\keyhop\config.toml with chord parser
  • Configurable hint alphabet
  • Configurable overlay colors (#RRGGBB)
  • Hotkey conflict detection with user notification
  • Windows startup integration via HKCU\...\Run (no admin)
  • Scroll action wired through UIA UIScrollPattern
  • User-facing notifications (no elements found, hotkey conflict, action failed)

Shipped (v0.3.0)

  • Browser webpage content detection (Chrome / Edge / Brave / Opera / Vivaldi / Arc) via on-demand Chromium accessibility-tree activation (WM_GETOBJECT to every renderer HWND) plus a 32-level UIA walk on browser foregrounds
  • Multi-monitor window picker fixes: badges anchor inside the title bar (visible on maximized windows), DWM-cloaked background UWP apps are filtered out, and selecting a minimized window restores it before focusing
  • Element-picker badge UX: smaller font, layout pass tries OutsideTop first so the underlying control stays visible
  • keyhop --close / --quit — cleanly shut down a running instance from any terminal (hidden message-only IPC window translates WM_CLOSE into the same path the tray's "Quit" entry uses)
  • keyhop --clear-logs — wipes log files under %LOCALAPPDATA%\keyhop\
  • Daily log rotation with 7-file retention (replaces the unbounded single keyhop.log)
  • Walk diagnostics — every enumerate_foreground logs nodes visited, deepest depth, and whether the depth/element cap was hit
  • MSI installer (Keyhop-<version>-x86_64.msi) — silent-install, single ARP entry, Start Menu shortcut, PATH integration; built by cargo wix and attached to every GitHub Release
  • CI: release.yml builds and attaches both keyhop.exe and the MSI; actions/checkout and softprops/action-gh-release bumped to Node.js 24 versions
  • Dependabot configured (cargo + github-actions, weekly)

Next up (v0.4.0) — Linux backend + signing

  • Linux backend via AT-SPI (X11 first, then Wayland)
  • Linux global hotkey integration (X11 XGrabKey / Wayland portal)
  • Linux overlay rendering (X11 _NET_WM_STATE_ABOVE layered window first; layer-shell on Wayland later)
  • Linux tray icon via the StatusNotifierItem / AppIndicator protocol
  • Smoke-test on GNOME (Wayland), KDE (X11), and a tiling WM (i3 or Sway)
  • Microsoft Trusted Signing for the MSI + EXE (kills the SmartScreen "unknown publisher" warning; see docs/CODE_SIGNING.md)
  • Firefox accessibility-tree activation (Gecko uses a different IPC dance than Chromium's WM_GETOBJECT)

v0.5.0 — macOS backend

  • macOS backend via the Accessibility API (AXUIElement)
  • macOS global hotkeys via RegisterEventHotKey / MASShortcut-style API
  • macOS overlay (NSWindow with NSWindowCollectionBehaviorCanJoinAllSpaces)
  • macOS menu bar item with the same Settings / Pick / Quit affordances as the Windows tray
  • Notarized .app bundle build pipeline

v0.6.0 — Polish + cross-distro install

  • Polished tray icon (multi-resolution .ico instead of the procedural badge)
  • Hot-reload config without restarting (re-register hotkeys at runtime)
  • Per-color overrides exposed in the Settings dialog (today only badge backgrounds are editable)
  • Click-through overlay so non-target apps still see the mouse
  • Winget manifest (Windows)
  • Linux installer story: .deb + .rpm packages, plus a Flatpak manifest

See CHANGELOG.md for release history.

Contributing

Issues and PRs welcome. Please run before pushing:

cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo test

Building the installer locally

The repo ships a Scripts.toml for cargo-run that wraps the MSI build:

cargo install --locked cargo-run cargo-wix
scoop install wixtoolset3              # or grab WiX 3.14 from wixtoolset.org

cargo script msi                       # full: cargo build --release + cargo wix
cargo script msi-only                  # MSI only (assumes target/release/keyhop.exe)
cargo script msi-show                  # ls target/wix/*.msi
cargo script msi-clean                 # rm target/wix

The msi script auto-detects WiX from $env:WIX, scoop's wixtoolset3, and the standard Program Files install paths.

License

Dual-licensed under either of:

at your option.