Skip to content

Automatic mode switching from the active herdr pane #7

Description

@vgreg

Goal

Make PadIO switch mode automatically based on which app is running in the active herdr pane, so controller bindings follow the app under the cursor without a manual mode pick.

Primary use case: navigation plus voice control of AI agents from an Xbox controller, with herdr (in Ghostty) replacing tmux. Herdr has three navigation levels (workspaces, tabs, panes) plus whatever app runs in each pane.

Design

Herdr's navigation levels live in profile.global for the Ghostty profile, so workspace/tab/pane bindings are always live. The app running in the focused pane selects the mode. No changes to the profile resolution cascade are needed.

herdr (socket API)                    bridge                    PadIO
─────────────────────                 ──────                    ─────
events.subscribe pane.focused  ──┐
                                 ├──> resolve leaf ──> write ──> DispatchSource watch
poll pane.process_info (~3Hz)  ──┘    process name    context     → context → mode

Herdr side (from its docs): newline-delimited JSON over a Unix socket at ~/.config/herdr/herdr.sock (or HERDR_SOCKET_PATH). events.subscribe supports pane.focused; pane.process_info returns the shell pid, foreground process group id, and foreground processes with pid, name and argv/cmdline.

PadIO side: today mode is set only by controller input (ControllerManager.switchMode, reachable from executeAction's .setMode case and the mode picker). Profile selection reads exactly one external signal, the frontmost bundle ID (ControllerManager.refreshActiveProfile). There is no URL scheme, socket, XPC or command listener. PadIO needs a new inbound channel.

Decisions already made

  • Channel: a watched file, ~/.config/padio/context, holding a single token. Reuses the DispatchSource pattern already proven in ConfigLoader (MappingConfig.swift:309). No entitlements, no networking, no Info.plist changes.
  • Policy lives in PadIO. The bridge reports a raw process name; PadIO's config maps it to a mode via context_modes. All user configuration stays in one file.
  • No-match leaves the mode alone. If the context token has no context_modes entry, the current mode is kept. It does not fall back to default_mode.
  • Manual override sticks. External context applies only when the token changes. A mode picked by hand stays until the context token changes.
  • Hidden modes. Because context-driven modes can be numerous (one per app), the mode picker needs to show only a chosen few (e.g. media, zsh). Hidden modes stay reachable via setMode and external context.
  • Bridge ships as a herdr plugin using [[startup]], so install is herdr plugin install <owner>/<repo> with no launchd agent to manage. It talks to the socket directly rather than relying on the plugin [[events]] hook, whose docs only demonstrate worktree.created.

Known gap

Launching an app from the shell inside an already-focused pane fires no event: pane.focused does not fire, and pane.updated is documented as firing on terminal-title changes (excluding spinner-only churn), not on foreground-process changes. Hence the ~3Hz poll of pane.process_info on the focused pane alongside the event subscription, so both pane switches and in-pane launches go through one code path.

Sequence

The two tracks are independent and can proceed in parallel. Everything except #11 and #12 is unblocked today; those two need a live herdr session.

Caveat

All herdr detail here comes from reading its published docs, not from a live session. The probe sub-issue validates the field names and subscription semantics before the bridge is built.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestherdrherdr terminal multiplexer integration

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions