Skip to content
BeForce1Public

About

Keyboard-only clicking on Windows. Press a hotkey, every clickable thing gets a letter, type it. One C# file, 18 KB, no dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hop

Click anything on Windows without touching the mouse. Press a hotkey, every clickable thing gets a letter, type the letter.

license build size deps

                    ┌─────────────────────────────────────────┐
   Ctrl+Alt+Space → │  [f] File   [g] Edit   [h] View         │
                    │                                         │
                    │   ┌──────────┐        ┌──────────┐      │
                    │   │ [j] Save │        │ [k] Open │      │
                    │   └──────────┘        └──────────┘      │
                    │                                         │
   press  j      →  │   ...clicked Save. No mouse involved.   │
                    └─────────────────────────────────────────┘

macOS has Homerow for this, paid. On Windows, hunt-n-peck and keyhop do similar things. hop is the tiny one: a single file, an 18 KB exe, and arrow-key steering.

Install

Download hop.exe from Releases and run it. No installer, no runtime and no admin rights needed. To build it yourself instead:

git clone https://github.com/BeForce1/hop && cd hop
.\build.ps1
.\hop.exe

Then press Ctrl+Alt+Space in any window. A tray icon tells you which hotkey bound: it tries five and takes the first one that's free.

Autostart it:

Set-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' hop "$PWD\hop.exe"

Controls

Key Does
Ctrl+Alt+Space Label every clickable thing in the focused window
f g h j k l … Type a label to click it
W A S D / arrows Steer the green selection to the nearest target that way
Enter / Space Click the green one
Backspace Un-type a keystroke
Esc Cancel

Two ways to land on something: type its label if you can see it, or steer if you'd rather look than read. The selected target turns green and gets its whole control outlined, so you can see exactly what you're about to click.

Labels never use w a s d; those steer. That costs 4 of 26 letters, leaving 22 single-key labels and 484 two-key ones.

Build

csc.exe ships inside Windows at %WINDIR%\Microsoft.NET\Framework64\v4.0.30319, so this compiles on a clean machine with nothing installed: no SDK, no NuGet, no project file. Output is an 18,944-byte exe that needs no runtime, because .NET Framework 4.x is part of the OS.

One source file, 445 lines. build.ps1 is 21 lines.

See what it finds

.\hop.exe --dump 3 | Out-Host   # focus a window, wait 3s, print every target detected
hwnd 786950 -> 8 clickable in 126ms
  f   MenuItem     [0,0 44x44]         System
  g   TabItem      [16,-1 480x64]      pwsh
  h   Button       [422,8 64x48]       Close Tab
  j   TabItem      [496,-1 480x64]     bash
  k   Button       [902,8 64x48]       Close Tab
  l   TabItem      [968,-1 496x64]     cmd
  e   SplitButton  [1468,7 126x48]     New Tab

Useful for filing a bug: run it against the window that misbehaved, paste the output. The | Out-Host makes PowerShell wait for the output. Without it the prompt comes back first and the list prints over it.

How it clicks

UI Automation first, synthetic mouse last:

  1. Edit controls get SetFocus(): you want the caret, not a click
  2. InvokePattern: buttons, links, menu items
  3. TogglePattern: checkboxes
  4. ExpandCollapsePattern: combo boxes, tree nodes
  5. SelectionItemPattern: list items, tabs
  6. Otherwise: move the cursor and click for real

Pattern invocation doesn't move your mouse and works even when the window isn't focused, which is why it's tried first.

The one performance trick that matters: a CacheRequest batches every property read into a single cross-process call. Without it, a busy window takes seconds; with it, a live Windows Terminal window enumerates in 126 ms.

The hotkey path has a hard 1.5 s budget and a 484-element cap. Past that, targets are dropped rather than making you wait. --dump uses a 5 s budget instead, on the grounds that a diagnostic should show you everything it can find.

Waking Chromium

Chrome, Edge and Electron build their accessibility tree lazily, and the first UIA query is itself what triggers the build, so it returns browser chrome and no page content. Measured on a cold Chrome showing 30 links:

pass elements links
1 13 0
2, +250 ms 33 20

So on a Chrome_WidgetWin_* window, if nothing landed inside the document's rect, hop sleeps 250 ms and rescans, keeping whichever pass found more. The trigger is document emptiness rather than a low count: an already-awake small window measured 19 targets, so any threshold would tax it forever.

Cost: cold 498 ms, awake 143-159 ms, non-Chromium 131-156 ms (unchanged).

Requirements

  • System: Windows 10 or 11. It needs .NET Framework 4.x, which ships with Windows.
  • CPU: 0% while idle. It only works when you press the hotkey.
  • Memory: about 23 MB while idle, almost all of it the .NET Framework runtime.
  • Disk: one 18 KB .exe.
  • Startup: about 55 ms.

Measured on Windows 11 with .NET Framework 4.8.1: 30 s idle sample, 5 launches.

Prior art

  • Homerow (macOS, paid): the thing this imitates. Better polished.
  • hunt-n-peck (Windows): the same idea on UI Automation, around for years.
  • keyhop (Windows, Rust): hint labels over every clickable thing, home-row alphabet.
  • vimium-everywhere (Windows): an AutoHotkey script its own README calls unstable.
  • Vimium / Vimium-C: same idea, browsers only, excellent at it.
  • PowerToys Mouse Jump teleports the cursor to a screen region. Different problem.

If you want this inside a browser only, use Vimium. It's better at that than hop is.

Limits

Stated up front so nobody has to discover them:

  • A cold Chromium window pays one 250 ms wake, once. See Waking Chromium. Nothing else is affected.
  • ControlType.Custom is excluded. Electron exposes thousands of them and they bury the real controls. If something has no label, this is usually why.
  • Scrollbar parts are excluded. UIA reports the trough as a Button, one 32x1697 "target" that scrolls rather than clicks. Dropped by AutomationId, which is not localised. On a terminal window that was 3 of 11 labels reclaimed.
  • WASD steers the selection; it does not scroll the page. Only on-screen controls are ever labelled, so anything below the fold is invisible until you scroll there.
  • Steering is geometric, not tab-order. Nearest target in the direction pressed, penalising sideways drift 3:1. Good on grids and columns, mediocre on scattered layouts.
  • Per-monitor mixed DPI can misplace labels. SetProcessDPIAware() handles uniform scaling, not per-monitor v2.
  • Left click and focus only. No right-click, no drag, no scroll.
  • Windows running as administrator can't be read (Task Manager, Registry Editor, elevated terminals) unless hop also runs as administrator. Windows blocks UI Automation across that boundary, so the hotkey finds nothing there.

License

MIT. See LICENSE.

About

Keyboard-only clicking on Windows. Press a hotkey, every clickable thing gets a letter, type it. One C# file, 18 KB, no dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages