# stochos

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

Keyboard-driven mouse control overlay for Wayland, X11, and macOS. OSS alternative to [mouseless](https://mouseless.click).

![example](example.gif)

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:

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

With cargo:

```sh
cargo install stochos
```

From source:

```sh
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`:

```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 (macOS is not supported) |
| 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) |
| `v` | Switch to free mode |

All keys are configurable (see Configuration below).

### CLI flags

| Flag | Effect |
|------|--------|
| `--bisect` | Start the overlay directly in bisect 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

### 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.

```toml
font_size = 2  # Glyph scale multiplier for the 8x8 bitmap font: 1=8px, 2=16px, 3=24px
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

[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"
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

[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.

### 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.
