Skip to content

Repository files navigation

stochos

stochos (/'sto.xos/) — from Greek στόχος: aim, target, goal.

Keyboard-driven mouse control overlay for Wayland, X11, and macOS. OSS alternative to mouseless.

example

Displays a letter grid over your screen. Type a two-key combo to jump to a cell, refine with a sub-grid key, then act. Runs once per invocation (no daemon).

Wayland: Tested on Hyprland. Should work on any wlroots-based compositor with zwlr_layer_shell_v1 and zwlr_virtual_pointer_v1.

X11: Tested on i3. Should work on any X11 window manager with the XTest extension.

macOS: Single-display only. Requires Accessibility permission (see Setup → macOS below). Cold-start launches dismiss Control Center / WiFi-brightness widgets; other system menus and notification center are unaffected.

Install

With curl:

curl -fsSL https://raw.githubusercontent.com/museslabs/stochos/main/install.sh | sh

With cargo:

cargo install stochos

From source:

git clone https://github.com/museslabs/stochos
cd stochos
cargo build --release                                          # Linux: both backends
cargo build --release --no-default-features --features wayland # Wayland only
cargo build --release --no-default-features --features x11     # X11 only
cargo build --release --no-default-features                    # macOS

Setup

Hyprland

Bind it to a key in hyprland.conf:

bind = , SUPER_L, exec, stochos

i3

Bind it to a key in ~/.config/i3/config:

bindsym Super_L exec stochos

macOS

Bind it to a key with your launcher of choice (Raycast, skhd, Aerospace, etc.). For example, in ~/.aerospace.toml:

alt-enter = 'exec-and-forget /usr/local/bin/stochos'

On first launch, macOS will prompt for Accessibility permission. Open System Settings → Privacy & Security → Accessibility and enable the binary (or the terminal you launched from). stochos uses the permission to capture global keystrokes via CGEventTap and to synthesize mouse events via CGEventPost.

If CGEventTapCreate failed is printed instead, also enable the binary under Input Monitoring.

Released binaries are not codesigned with a Developer ID, so after every upgrade macOS treats the new binary as a different program and re-prompts for Accessibility (and Input Monitoring, if applicable). Re-enable it in System Settings each time. Install via the curl | sh command above — curl does not set the Gatekeeper quarantine attribute, so the binary runs without a "cannot be opened" prompt.

Usage

  1. Trigger the overlay
  2. Type two letters to select a grid cell (e.g. a then s)
  3. Type one more letter to refine within the sub-grid
  4. Perform an action (see below)

Default keys

Key Action
Space Click
Enter Double click
m Triple click
Delete Right click
Insert Middle click
Escape Close overlay
Backspace Undo last step
Arrow keys Scroll (up/down/left/right)
/ Start drag (select end point, then Space)
` Toggle macro recording
@ Replay macro by bind key
Tab Search macros / quick-save position
b Switch to bisect mode
n Switch back to normal mode (from bisect)
c Switch to hint mode
v Switch to free mode

All keys are configurable (see Configuration below).

CLI flags

Flag Effect
--bisect Start the overlay directly in bisect mode
--hint Start the overlay directly in hint mode
--free Start the overlay directly in free mode (cursor starts at current mouse position)
--free-center Start the overlay directly in free mode with the cursor at the center of the screen
--allow-multiple Skip the single-instance lock
--print-default-config Print the default config (TOML) to stdout and exit. Diff against your config.toml to discover options added by newer versions

Bisect mode

An alternative grid mode that recursively subdivides. Instead of two-key combos and a sub-grid, each hint press zooms the grid into the cell you chose, re-rendering a fresh grid inside that region. Keep pressing hints to home in, then click.

  • Enter bisect mode: press b from normal mode, or launch with --bisect
  • Each hint key splits the current region into a smaller grid (2x2 by default)
  • Space / Enter / m / Delete / Insert act (click / double-click / triple-click / right-click / middle-click) at the center of the current region and exit
  • Backspace pops back up one level
  • Subdivision stops automatically once a cell would fall below min_cell_size pixels — the region is highlighted and you can act or back out
  • Press n to switch back to normal mode

Hint mode

An alternative mode that labels likely clickable UI targets directly, instead of asking you to navigate a grid. Type the label shown on a target, then perform an action.

  • Enter hint mode: press c from normal, bisect, or free mode, or launch with --hint
  • Space / Enter / m / Delete / Insert act (click / double-click / triple-click / right-click / middle-click) on the selected target and exit
  • / starts a drag from the selected target; select another hint and press Space to drag
  • Backspace edits the typed hint label, or returns to the previous mode when no label is typed
  • Escape closes the overlay

Detection is pluggable behind a HintDetector trait, selected by hint.detector:

  • atspi (Linux, built in): reads the desktop AT-SPI accessibility tree over D-Bus and labels interactive elements using their roles and bounds. It is usually the most accurate detector. GTK, Qt, and Gecko apps tend to work well; Chromium/Electron may need accessibility enabled; games and canvas-heavy apps may expose little or nothing. On Wayland, exact placement requires compositor support; Hyprland and Sway are supported. On X11, coordinates are already absolute. Only the primary monitor is handled.
  • cv (always available): a pure-Rust computer-vision pass over a screenshot — downscale → Canny edges → morphological closing → connected components → resolution-relative size/shape filtering with a per-box quality score. No model, no network, no services to talk to. CV sees visual structure, not semantic clickability, so it will still occasionally label a border, separator, or decorative shape, and miss a low-contrast control. Tune edge_low_threshold/edge_high_threshold (raise to cut clutter, lower to catch faint controls).
  • auto (default): tries AT-SPI first and falls back to CV when needed. If an app exposes window chrome but not content, CV labels the unhandled content area.
[hint]
detector = "auto"   # or "atspi" / "cv"

Free mode

An alternative mode for direct keyboard-driven cursor movement. Instead of selecting a grid cell, you steer the cursor continuously with movement keys, then act.

  • Enter free mode: press v from normal or bisect mode, or launch with --free (starts at mouse position) or --free-center (starts at screen center)
  • Move the cursor with h / j / k / l (left / down / up / right)
  • Press = to increase speed and - to decrease it; the current speed is shown in the top-left corner
  • Space / Enter / m / Delete / Insert act (click / double-click / triple-click / right-click / middle-click) and exit
  • Backspace returns to the previous mode; Escape closes the overlay
  • Key repeat is supported: hold a movement key to move continuously

Limitations: drag and text selection are not available in free mode. Use normal mode for those actions.

Key repeat cadence: On Wayland the overlay drives its own repeat (300 ms delay, 25 Hz). On macOS and X11 the OS provides repeats, so the initial delay and rate follow your system keyboard settings (System Settings → Keyboard on macOS, xset r rate on X11).

Macros

Record multi-step mouse sequences for replay.

Record: Press ` to start recording. Navigate and act normally (Space to click, Enter to double-click, m to triple-click, Tab to hover-only, / to drag). Press ` again to stop. You'll be prompted for an optional bind key and a name.

Replay: Press @ then the bind key. Or press Tab to search by name, then Enter to select.

Timing. Stochos captures the delay between each recorded action and replays at the same rhythm, so animations and page loads have time to settle. The first action has no wait (you weren't waiting for anything to start the macro). On replay, delays are scaled by macros.playback_speed in config.toml (default 1.0, 2.0 plays twice as fast, 0.0 or negative plays instantly with no waits). You can also hand-edit the per-action wait_ms values in macros.json for fine-grained tuning.

Macros are resolution-independent and stored at ~/.config/stochos/macros.json. Macros recorded before this version still load and replay (without inter-action delays).

Configuration

Config file location: ~/.config/stochos/config.toml (respects XDG_CONFIG_HOME).

All fields are optional. Missing fields use defaults.

font_size = 2  # Glyph scale multiplier for the 8x8 bitmap font: 1=8px, 2=16px, 3=24px
hint_font_size = 1  # Optional override for element hint labels; defaults to 1 for dense target screens
sub_hint_font_size = 2  # Optional override for sub-grid hint glyphs; defaults to font_size when omitted
panel_font_size = 2  # Optional override for macro/search popup panels; defaults to sub_hint_font_size, then font_size

[grid]
hints = ["a", "s", "d", "f", "j", "k", "l", ";", "g", "h", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]
sub_hints = ["a", "s", "d", "f", "j", "k", "l", ";", "g", "h", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "z", "x", "c", "v", "b"]
sub_cols = 5
target_cell_size = 90  # Enable dynamic grid by setting this value

[bisect]
hints = ["a", "s", "d", "f"]  # One per cell, row-major (length must be >= rows * cols)
rows = 2
cols = 2
min_cell_size = 16  # Stop subdividing once a cell would be smaller than this, in pixels

[hint]
detector = "auto"  # "auto" (default) | "atspi" (Linux) | "cv"
alphabet = ["a", "s", "d", "f", "j", "k", "l", ";", "g", "h", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]  # Keys used to build hint labels
max_label_len = 3
auto_click = false  # If true, click immediately when a typed label uniquely identifies a target
downscale_longest = 1280  # CV: longest screen-capture side fed to the detector; 0 = no downscale
edge_low_threshold = 50  # CV: Canny low threshold (raise to cut clutter, lower to catch faint controls)
edge_high_threshold = 150  # CV: Canny high threshold

[macros]
playback_speed = 1.0  # 1.0 = recorded speed, 2.0 = twice as fast, 0.0 or negative = instant (no waits)

[keys]
normal = "n"
bisect = "b"
hint = "c"
free_mode = "v"
click = "space"
double_click = "enter"
triple_click = "m"
close = "escape"
undo = "backspace"
right_click = "delete"
middle_click = "insert"
scroll_up = "up"
scroll_down = "down"
scroll_left = "left"
scroll_right = "right"
macro_menu = "tab"
macro_record = "`"

[colors]
# Grid colors - Maximum visibility in all conditions
cell_normal = "#00000050"        # Semi-black overlay (works on light backgrounds)
text_dim = "#ffffff30"           # Semi-white dim (slightly visible on dark)
sub_cell_normal = "#20202080"    # Dark grey with good opacity
sub_bg = "#20202080"             # Matches sub cell
text_first = "#00ff88ff"         # Teal-green (good in sunlight)
text_second = "#ff8800ff"        # Orange (high visibility)
cell_highlight = "#00000028"     # Subtle dark tint (no color, just darkens slightly)
text_highlight = "#ffff00ff"     # Pure yellow (maximum contrast)
cell_drag = "#ff00ddaa"          # Hot pink (extremely visible)
panel_bg = "#121212f5"           # Near-black (96% opaque) - creates strong contrast barrier
text_white = "#f5f5f5ff"         # Off-white (comfortable for long reading)
text_grey = "#b8b8b8ff"          # Light grey (maintains 4.5:1 contrast ratio)
selected_bg = "#2196f3ff"        # Material Design Blue (works in light/dark)
rec_bg = "#f44336ff"             # Material Design Red (urgent, visible everywhere)
border = "#00e676ff"             # Material Green (fresh, visible)
border_dragging = "#e91e63ff"    # Material Pink (strong attention grabber)
crosshair = "#ffaa00ff"          # Amber crosshair in free mode
hint_chip_bg = "#2e1e1eee"       # Hint label background
hint_text = "#00ccffff"          # Untyped hint label text
hint_text_typed = "#50ff50ff"    # Typed hint label text
hint_dim = "#66666688"           # Dimmed non-matching hints

[free]
left = "h"
down = "j"
up = "k"
right = "l"
fast = "="
slow = "-"
base_speed = 25
fast_multiplier = 3.0
slow_multiplier = 3.0
min_speed = 1
max_speed = 500

Font

  • font_size sets the glyph scale multiplier. Stochos uses an 8x8 bitmap font, so each step adds 8px: 1=8px, 2=16px, 3=24px, and so on. Default is 2.
  • Increase font_size for high-DPI displays such as 3 or 4 on 4K monitors. Valid range: 1-10.
  • sub_hint_font_size sets the glyph scale multiplier for sub-grid hints. If omitted, it inherits font_size. It uses the same 8px steps and 1-10 range, and still clamps down to fit inside each sub-cell.
  • panel_font_size sets the glyph scale multiplier for macro and search popup panels. If omitted, it inherits sub_hint_font_size, or font_size if that is also unset. It uses the same 8px steps and 1-10 range.

Grid

  • hints sets the characters for the main grid. Grid size is len(hints) x len(hints) (default 20x20 = 400 cells).
  • sub_hints sets the characters for the sub-grid. Sub-grid size is sub_cols x (len(sub_hints) / sub_cols) (default 5x5 = 25 cells).
  • sub_cols sets how many columns the sub-grid has.

Bisect

  • hints sets the characters for each bisect cell, in row-major order. Must contain at least rows * cols entries; extras are ignored.
  • rows and cols set the grid shape used at every subdivision level (default 2x2).
  • min_cell_size is the pixel floor at which subdivision stops. The glyph scale auto-shrinks to fit the cell, so lowering this lets you reach tiny regions.

Hint mode

  • alphabet sets the characters used for generated hint labels. Defaults to the same set as [grid].hints; override to change which keys label elements.
  • max_label_len caps generated label length. Higher values allow more targets, but require more keystrokes.
  • auto_click clicks as soon as the typed label uniquely matches one target. Leave it false if you want to choose the action after selecting.
  • downscale_longest controls CV detector speed/precision. Lower values are faster but less precise; 0 disables downscaling.
  • edge_low_threshold / edge_high_threshold tune Canny edge detection. Lower values find more targets and more false positives; higher values are stricter.

Free mode

  • left, down, up, right set the movement keys (default: h, j, k, l).
  • fast / slow increase or decrease the step size by fast_multiplier / slow_multiplier (default: = and -).
  • base_speed is the initial step size in pixels (default: 25).
  • fast_multiplier / slow_multiplier are the scale factors applied when pressing the speed keys (default: 3.0).
  • min_speed / max_speed clamp the step size in pixels (default: 1 and 500).

Macros

  • playback_speed scales the per-action delays recorded into each macro. 1.0 plays at the recorded rhythm, 2.0 plays twice as fast, 0.5 plays at half speed. Setting it to 0.0 (or any negative value) disables waits entirely for instant playback. Default is 1.0.

Keys

Any keyboard key can be bound to any action.

Character keys use the character itself: "a", ";", "/", "@", "!".

Special keys use snake_case names:

Name Key
space Space
enter Enter
escape Escape
backspace Backspace
tab Tab
delete Delete
insert Insert
home Home
end End
page_up Page Up
page_down Page Down
up down left right Arrow keys
f1 through f12 Function keys
caps_lock num_lock scroll_lock Lock keys
print_screen pause context_menu System keys
num_pad_0 through num_pad_9 Numpad digits
num_pad_add num_pad_subtract num_pad_multiply num_pad_divide num_pad_decimal num_pad_enter Numpad operators

Shifted characters work too: "@", "!", "~", "A" (uppercase), etc.

Releases

Contributors

Languages