From fecf0d7f6f7b3ae9c6860d76451c3a34c938427f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Gr=C3=A9goire?= Date: Sat, 1 Aug 2026 17:36:57 -0400 Subject: [PATCH] document automatic modes, context_modes, and hidden_modes Add a configuration page covering the ~/.config/padio/context extension point, the four resolution rules, and hidden modes, with the herdr plugin as one producer rather than the only one. Extend the profile field table, the sample config, and the annotated example with an agent mode reached only via context. Closes #13 Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 1 + config.json | 14 +++ docs/configuration/automatic-modes.md | 130 ++++++++++++++++++++++++++ docs/configuration/profiles.md | 17 ++-- docs/example-config.md | 16 ++++ mkdocs.yml | 1 + 6 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 docs/configuration/automatic-modes.md diff --git a/README.md b/README.md index 986a977..d182c99 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ If you're looking for a friendlier GUI-based controller remapper, search "Game C - **Button combos** — hold a modifier button to change what other buttons do (e.g., X+dpad_up) - **Per-app profiles** — automatic profile switching based on the frontmost application - **Modes** — multiple binding sets per profile, switchable via picker, cycling, or direct jump +- **Automatic modes** — let an external program pick the mode, e.g. the app in your terminal's focused pane ([herdr](https://herdr.dev/) plugin [available](https://github.com/vgreg/herdr-padio)) - **Custom menus** — define popup menus with labeled items that trigger any action - **Haptic feedback** — rumble on system beep, notifications, or on-demand from any binding - **Media keys** — play/pause, track skip, volume, brightness (no Accessibility permission needed) diff --git a/config.json b/config.json index 8108e7e..53c8170 100644 --- a/config.json +++ b/config.json @@ -44,7 +44,21 @@ "LB": { "type": "prev_mode" }, "RB": { "type": "next_mode" } }, + "context_modes": { + "claude": "agent", + "nvim": "nvim", + "zsh": "shell", + "ssh": "shell" + }, + "hidden_modes": ["agent"], "modes": { + "agent": { + "A": { "type": "keystroke", "key": "return" }, + "B": { "type": "keystroke", "key": "escape" }, + "X": { "type": "keystroke", "key": "`continue`" }, + "dpad_up": { "type": "keystroke", "key": "up" }, + "dpad_down": { "type": "keystroke", "key": "down" } + }, "shell": { "A": { "type": "keystroke", "key": "return" }, "B": { "type": "keystroke", "key": "c", "modifiers": ["ctrl"] }, diff --git a/docs/configuration/automatic-modes.md b/docs/configuration/automatic-modes.md new file mode 100644 index 0000000..8fddbdc --- /dev/null +++ b/docs/configuration/automatic-modes.md @@ -0,0 +1,130 @@ +# Automatic Modes + +PadIO can switch mode by itself based on what you are actually doing, instead of you picking a mode by hand every time. An external program reports a short **context token** (usually the name of the app you are working in), and each profile maps tokens to modes. + +The motivating case is a terminal multiplexer: focus a Claude Code pane and the controller is in agent mode, move to a shell pane and it is in shell mode, launch `nvim` and it becomes vim mode. But nothing about the mechanism is terminal-specific. Any program that can write a file can drive it. + +## How it works + +PadIO watches `~/.config/padio/context`, a plain text file holding one token: + +``` +claude +``` + +When the token **changes**, PadIO looks it up in the active profile's `context_modes` and switches mode if there is a match. + +``` +producer PadIO +──────── ───── +write ~/.config/padio/context ──> file watch → context_modes → mode +``` + +## Configuration + +Add `context_modes` to a profile. Keep bindings that should always be available in the profile's `global`, so they survive every mode change: + +```json +"profiles": { + "terminal": { + "apps": ["com.mitchellh.ghostty"], + "default_mode": "shell", + "global": { + "options": { "type": "mode_select" } + }, + "context_modes": { + "claude": "agent", + "nvim": "nvim", + "zsh": "shell", + "ssh": "shell" + }, + "hidden_modes": ["agent", "nvim"], + "modes": { + "shell": { }, + "agent": { }, + "nvim": { } + } + } +} +``` + +| Field | Type | Description | +|-----------------|--------|-------------| +| `context_modes` | object | Maps a context token to a mode name. Matching is exact. | + +## Resolution rules + +Four rules govern automatic switching. They exist to keep it from fighting you. + +**1. It fires only when the token changes.** A producer rewriting the same token repeatedly does nothing. + +**2. No match leaves the mode alone.** A token with no `context_modes` entry keeps the current mode. It does *not* fall back to `default_mode`. This is what makes it practical to add entries only for the apps you care about, and to ignore everything else. + +**3. A mode you picked by hand sticks.** Because switching happens only on token *change*, a mode chosen from the picker survives until you move to a different app. Pick `media` by hand inside a Claude pane and it stays `media`; move to an `nvim` pane and it becomes `nvim`. + +**4. An unknown mode name is ignored.** If `context_modes` names a mode that exists in neither the profile's `modes` nor `shared_modes`, PadIO logs it and does nothing. + +Only the active profile is consulted, so a token means whatever that profile says it means, and nothing at all in a profile with no `context_modes`. + +Returning to a profile re-applies the current token rather than snapping back to `default_mode`, so switching away from your terminal and back keeps the app-driven mode. + +## Hidden modes + +Automatic switching tends to produce one mode per app, which makes the mode picker unwieldy when only a couple of modes are ever chosen by hand. `hidden_modes` keeps them out of the way: + +```json +"hidden_modes": ["agent", "nvim"] +``` + +Hidden modes are excluded from the [`mode_select`](actions.md#mode_select) picker and from [`prev_mode` / `next_mode`](actions.md#prev_mode-next_mode) cycling. They remain fully reachable via `context_modes`, [`mode:`](actions.md#modename), and `default_mode`. + +The key can go on a profile, or at the top level for modes shared across profiles. The two lists are combined. + +```json +{ + "hidden_modes": ["media"], + "profiles": { + "terminal": { + "hidden_modes": ["agent", "nvim"] + } + } +} +``` + +If hiding would leave nothing to show, PadIO shows every mode instead, so the picker can never become an empty panel you cannot escape. + +!!! note + Cycling with `prev_mode` / `next_mode` while a hidden mode is active moves you into the visible set, since the current mode is not part of the cycle. + +## The context file + +The file is a general-purpose extension point. PadIO does not care what writes it. + +| Property | Behaviour | +|----------|-----------| +| Path | `~/.config/padio/context` | +| Content | A single token. Surrounding whitespace and newlines are stripped. | +| Empty or missing | Treated as no context. Not an error, and never a mode change. | +| Updates | Picked up immediately, whether written in place or replaced atomically. | + +Producers should write atomically, to a temp file in the same directory followed by a rename, so PadIO never reads a half-written token. PadIO ignores a rewrite of an unchanged token, so a producer that cannot easily detect changes may simply write every time. + +A minimal producer is a one-liner: + +```bash +printf 'nvim' > ~/.config/padio/context.tmp && mv ~/.config/padio/context.tmp ~/.config/padio/context +``` + +Which means anything can drive it: a shell hook on directory change, a window manager, a build script that switches you into a "watching CI" mode, or your editor. + +## herdr integration + +[herdr](https://herdr.dev/) is a terminal multiplexer for running coding agents, with workspaces, tabs and panes. The [herdr-padio](https://github.com/vgreg/herdr-padio) plugin is a ready-made producer for it: + +``` +herdr plugin install vgreg/herdr-padio +``` + +It watches the focused pane and writes the app running in it. Tokens are herdr's own agent name when it has detected one (`claude`), and otherwise the program driving the pane (`zsh`, `nvim`, `ssh`). Wrapped programs that report an unhelpful name can be renamed with substring rules; see that project's README. + +With herdr, put the workspace, tab and pane navigation bindings in the profile's `global` so they work in every mode, and let `context_modes` handle the app in the pane. diff --git a/docs/configuration/profiles.md b/docs/configuration/profiles.md index 4a74b13..76a0c97 100644 --- a/docs/configuration/profiles.md +++ b/docs/configuration/profiles.md @@ -28,12 +28,14 @@ A profile is a named set of bindings that applies when specific apps are in the } ``` -| Field | Type | Description | -|----------------|----------|-------------| -| `apps` | string[] | Bundle IDs this profile applies to. Empty list = default catch-all profile. | -| `default_mode` | string | Mode to activate when the profile is first entered. | -| `global` | object | Bindings that apply in all modes of this profile. Overridden by top-level `global`. | -| `modes` | object | Named modes, each containing button bindings. | +| Field | Type | Description | +|-----------------|----------|-------------| +| `apps` | string[] | Bundle IDs this profile applies to. Empty list = default catch-all profile. | +| `default_mode` | string | Mode to activate when the profile is first entered. | +| `global` | object | Bindings that apply in all modes of this profile. Overridden by top-level `global`. | +| `modes` | object | Named modes, each containing button bindings. | +| `context_modes` | object | Optional. Maps an external context token to a mode, for [automatic mode switching](automatic-modes.md). | +| `hidden_modes` | string[] | Optional. Modes to [hide from the picker](automatic-modes.md#hidden-modes) and from mode cycling. | ### Profile resolution order @@ -78,6 +80,9 @@ Each profile can have multiple modes. Only one mode is active at a time. Switch - [`mode_select`](actions.md#mode_select) — opens a picker overlay - [`prev_mode` / `next_mode`](actions.md#prev_mode-next_mode) — cycle through modes - [`mode:`](actions.md#modename) — jump directly to a named mode +- [automatic switching](automatic-modes.md) — let an external program pick the mode from what you are working in + +Modes that are only ever selected automatically can be kept out of the picker and the cycle with [`hidden_modes`](automatic-modes.md#hidden-modes). ## Button combos diff --git a/docs/example-config.md b/docs/example-config.md index d8b878c..751932d 100644 --- a/docs/example-config.md +++ b/docs/example-config.md @@ -49,7 +49,21 @@ A complete annotated config demonstrating profiles, modes, sequences, custom men "LB": { "type": "prev_mode" }, "RB": { "type": "next_mode" } }, + "context_modes": { + "claude": "agent", + "nvim": "nvim", + "zsh": "shell", + "ssh": "shell" + }, + "hidden_modes": ["agent"], "modes": { + "agent": { + "A": { "type": "keystroke", "key": "return" }, + "B": { "type": "keystroke", "key": "escape" }, + "X": { "type": "keystroke", "key": "`continue`" }, + "dpad_up": { "type": "keystroke", "key": "up" }, + "dpad_down": { "type": "keystroke", "key": "down" } + }, "shell": { "A": { "type": "keystroke", "key": "return" }, "B": { "type": "keystroke", "key": "c", "modifiers": ["ctrl"] }, @@ -140,4 +154,6 @@ This config: - **shell mode**: return, ctrl-c, ctrl-l, git menu on Y (using `"type": "menu", "name": "git"` syntax) - **nvim mode**: vim-style hjkl navigation, with X+dpad combos for ctrl-j/ctrl-k (half-page scroll) - **tmux mode**: prefix sequences (ctrl-a + key) for pane navigation + - **agent mode**: return, escape, and `continue` as injected text — reached only via `context_modes` +- **Automatic modes**: `context_modes` switches mode from an external token (see [Automatic Modes](configuration/automatic-modes.md)), and `hidden_modes` keeps `agent` out of the picker since it is never chosen by hand - **Git menu**: quick terminal commands accessible via Y button in shell mode diff --git a/mkdocs.yml b/mkdocs.yml index 707900c..9b5fce1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ nav: - Configuration: - Overview: configuration/index.md - Profiles & Modes: configuration/profiles.md + - Automatic Modes: configuration/automatic-modes.md - Actions: configuration/actions.md - Custom Menus: configuration/menus.md - Haptics: configuration/haptics.md