Your hands are on the home row. The button you want is on the far side of the screen. Between the two sits a mouse, and reaching for it is the slowest thing you will do this minute.
Mouseless is a keyboard-driven pointer for Omarchy. Press a key, the screen fills with labels, you type three letters, and the pointer lands on a 15x14px target and clicks. That is close-button precision in fewer keystrokes than it takes to find where the cursor wandered off to. It does what warpd does, as a native overlay plugin for the Omarchy shell.
Under the hood: two letters pick a cell from a 26x26 grid on a 1920x1080 screen, then one letter from a keyboard-shaped block picks the exact spot inside it. Arrow keys, double click, right and middle click, move-without-click, and a scroll mode are all one modifier or one key away.
Full disclosure: this idea is not new, and the section below points you at the alternatives. Mouseless started as an excuse to learn Quickshell and see how far an Omarchy plugin could go, and it was mostly vibe coded with an AI pair. The security section further down exists because of that: every shell call was reviewed by hand and is spelled out there. If it also turns out to be the pointer you reach for every day, even better.
The marketplace also lists OmaGrid
by GrzeskoByte, which takes the same approach with a coarser 16x9 grid,
Shift-to-zoom refinement across up to five levels, and clicks through
ydotool. Mouseless reaches finer precision in fewer keys, adds right, middle,
and double clicks, move-only, and scrolling, and clicks through wlrctl, which
needs no daemon and no /dev/uinput access. If you prefer arbitrary zoom depth
or per-column grid weights, use OmaGrid.
- Omarchy with the Quickshell-based shell (
omarchy-shell) on Hyprland. The plugin uses Hyprland's Lua dispatcher to warp the pointer and a Hyprland submap for scrolling, so it is not portable to other compositors as is. wlrctlfrom the AUR for clicks and scrolling. It speaks thewlr-virtual-pointerprotocol: no daemon, no root, no uinput. Install it withomarchy pkg aur add wlrctl.
omarchy pkg aur add wlrctl
omarchy plugin add https://github.com/wkuehler/mouseless.git --enableAdd to ~/.config/hypr/bindings.lua (SUPER+M is unbound in stock Omarchy):
o.bind("SUPER + M", "Mouse: hint grid", "omarchy-shell shell toggle wkuehler.mouseless")Then hyprctl reload.
Pick a target. Labels cover the screen in a grid. Type the row letter,
then the column letter. The cell splits into a block of single-letter subcells
laid out like the left of a keyboard (q w e r t / a s d f g / z x c v b)
and a third letter picks one. Or move a highlight with the arrow keys: it
starts on the cell under the pointer, Enter drills into the cell, the arrows
move within the block, and Enter picks the subcell. Letters and arrows mix
freely. Backspace steps back one level, + and - resize the grid, and Esc
closes without doing anything.
Act on it. The final key or Enter does the action:
| Modifier | Action |
|---|---|
| none | left click |
| Shift | right click |
| Ctrl | middle click |
| Alt | double click |
| Space (instead of the final key) | move the pointer there, no click |
| Tab (instead of the final key) | move the pointer there and enter scroll mode |
The overlay closes, the pointer warps, and the click is sent to whatever is underneath.
Scroll. Tab parks the pointer and enters a Hyprland submap named
mouseless-scroll; a pill at the bottom of the screen shows while it is
active. Arrows or hjkl scroll, Shift scrolls faster, PgUp and PgDn page,
Enter clicks where the pointer sits (Shift right, Ctrl middle). Esc, Space, or
Tab leave the submap. The submap is defined by the plugin at runtime, so
nothing needs adding to bindings.lua.
Press ? inside the overlay for the settings card. Each row has a key.
Changes save immediately to ~/.config/omarchy/mouseless.json, which is also
watched, so hand edits apply on the next open.
| Key | Setting | Values |
|---|---|---|
+ - |
Grid cell size (minimum px per cell; also works outside the card) | 24 to 200 |
1 |
Label keys | alphabet: a-z rows x a-z columns, up to 26x26. homerow: asdfghjkl; rows x qwertyuiop columns, 10x10 |
2 |
Fine pass | on: third key refines inside the cell. off: second key clicks the cell centre |
3 |
Fine block rows | 3 (qwert / asdfg / zxcvb) or 2 (qwer / asdf) |
4 |
Background dim | 0%, 25%, 50% |
5 |
Status bar | shown or hidden |
6 |
Arrow highlight starts | under the pointer or at screen centre |
7 |
Third level | off, or on: after the fine key the subcell splits into an unlabeled 4x3 keyed qwer / asdf / zxcv, Enter clicks the subcell centre. Pairs well with home-row labels or a coarser grid, where a subcell is 30px or more |
0 |
Reset to defaults |
The overlay answers the shell's standard plugin calls:
omarchy-shell shell toggle wkuehler.mouseless
omarchy-shell shell summon wkuehler.mouseless
omarchy-shell shell hide wkuehler.mouselessOmarchy plugins run unsandboxed inside the shell process, so here is exactly what this one does:
- Runs
/usr/bin/hyprctl cursorposwhen the overlay opens, to start the arrow highlight under the pointer (only when that setting is on). - On a click, move, or scroll request runs
/usr/bin/hyprctl dispatchto warp the pointer and/usr/bin/wlrctl pointer move/click/scrollto nudge, click, and scroll. Nothing runs until you press a final key. - On entering scroll mode runs
/usr/bin/hyprctl evalonce to define themouseless-scrollsubmap, thenhyprctl dispatchto enter it. The submap binds only run/usr/bin/wlrctlandhl.dsp.submap("reset"). It lives in Hyprland's memory until the next Hyprland restart and is never written to your config files. - Reads and writes exactly one file it owns,
~/.config/omarchy/mouseless.json, and only when you change a setting or hand-edit it. - Makes no network requests and installs no packages.
- No sudo or pkexec is required.
- Starts no daemons or background processes.
How those calls are bounded:
- Executables are named by absolute path. Every argument is either a constant
string or an integer computed from the grid geometry; no setting, file
content, or key text is ever interpolated into a command. The submap Lua is
a constant built from a fixed bind table and is passed to
shas a positional argument, not spliced into the shell string. - Each of the three child processes (cursor position, click, scroll mode) is a
single instance with a 5-second watchdog that kills it. Their output is not
collected, except
cursorpos, where only the first line is read and only its first 64 bytes are parsed. Unloading the plugin stops every process and timer. - The settings file is capped at 64 KiB and every value is checked against an allowlist; anything else falls back to the defaults. All on-screen text is rendered as plain text.
Two Hyprland behaviours shape the design and are worth knowing:
- A layer surface with exclusive keyboard focus also receives every pointer event, regardless of its input region. That is why the overlay closes before it clicks, and why scrolling happens from a submap rather than from inside the overlay.
wlrctlscroll and click events only reach an app after the virtual pointer has sent a motion event, so every warp is followed by a one-pixel nudge out and back.
Install the plugin as above, then edit the files under
~/.config/omarchy/plugins/wkuehler.mouseless directly, or point that
checkout's origin at your fork. Apply changes with omarchy restart shell:
this overlay is keepLoaded, and the shell's file watcher does not replace a
running keepLoaded component. Run scripts/check before committing: it
validates the manifest, runs the HintModel.js tests under node, and runs
qmllint when it is installed.
Layout:
| File | Role |
|---|---|
manifest.json |
Plugin manifest: overlay kind, entry point. |
Mouseless.qml |
The overlay, key handling, settings card, scroll pill, and the shell-outs. |
HintModel.js |
Pure functions: grid and fine-block geometry, labels, settings defaults and sanitising. |
tests/hint-model.test.js |
Unit tests for HintModel.js. |
scripts/check |
All offline checks in one command. |
docs/PLAN.md |
Design notes and findings. |
Shell QML errors show up in qs log -i <instance> -t 100; find the instance
with qs list --all.
omarchy plugin remove wkuehler.mouselessThis disables the plugin and deletes the installed copy. It does not touch the
keybind you added, the wlrctl package, or ~/.config/omarchy/mouseless.json;
remove those yourself for a fully clean uninstall.
MIT. See LICENSE.
