Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Three SwiftPM targets (`Package.swift`):
## Things that are easy to get wrong

- **Firmware lies**: it answers `{"ok":1}` to any payload, malformed or not. The LEDs are the only ground truth. `PadEmulator` reproduces this on purpose (and the stock keymap, and silent unbound keys) — keep it faithful when changing it.
- **Only one HID client at a time**: Work Louder's Input app and the Codex desktop app fight over the same lighting. `contendingClient` has two signals, combined only in `updateContention()`: a reply with a response id we never issued sets `trafficSeen` (`noteResponse(id:)` and `noteIssued(id:)`, also the test seams), and an IORegistry scan (`WLDevice.otherClients()`, behind the `scanClients` seam) lists the other processes holding the pad. An `.authoritative` scan (USB vendor interface) with others raises the flag by itself, unless Recheck acknowledged them (`acknowledgedPIDs`: the pids a Recheck's scan listed, so an app that opens every HID device and never sends cannot pin the warning; pruned by every scan that no longer lists them, so a relaunch raises again; kept by `.unavailable`; reset by `start()`/`stop()`; their replies still raise through `trafficSeen`); an `.advisory` one (Bluetooth) only names them; an empty list from either clears `trafficSeen`; `.unavailable` (emulator, failed walk) leaves the replies to decide. Scans run on panel open (`scanContention()`), on Recheck (`recheckContention()`: clear traffic, scan, acknowledge, repaint if running), after `openDevice()` connects, and after a foreign reply (at most once per `replyScanInterval`); never while off. `contenders` names them in the warning. A recheck must never clear `issuedIDs`: a reply to our own call still in flight would look foreign and re-raise the warning at once. The Inspector is a separate process and counts as another client.
- **Only one HID client at a time**: Work Louder's Input app and the Codex desktop app fight over the same lighting. Only a reply with a response id we never issued raises `contendingClient` (`noteResponse(id:result:error:)` and `noteIssued(id:)`, also the test seams), and not a null one: Input sends `host.focused_app` on every app switch, which the firmware answers with a bare null (lighting calls answer `{"ok":1}`); the filter runs after the own-id check, so our own ids are still consumed. **Holding the pad never raises it**: Input keeps the vendor interface open in the background without sending, and a presence rule warned on every launch. An IORegistry scan (`WLDevice.otherClients()`, behind the `scanClients` seam; `.authoritative` and `.advisory` are treated alike) says who may have sent and when they have gone: `traffic` is `.from(pids)` — laid at the newcomers, i.e. others not in `quietPIDs` (seen holding the pad while no reply was outstanding), else at every other — and clears once a scan lists none of them; `.unattributed` (no scan could say) clears only on an empty list. Newcomers blamed go into `suspectedPIDs` and are never counted quiet while present, so a Recheck with the Inspector still open does not spread its next reply to Input. `reconcile()` applies a scan; `.unavailable` (emulator, failed walk) changes nothing; `start()`/`stop()` forget it all. Scans run on panel open (`scanContention()`), on Recheck (`recheckContention()`: clear traffic, scan, repaint if running), after `openDevice()` connects, and after a foreign reply (at most once per `replyScanInterval`); never while off. `contenders` names the suspects still present, else every other. A recheck must never clear `issuedIDs`: a reply to our own call still in flight would look foreign and re-raise the warning at once. The Inspector is a separate process and counts as another client.
- **Signing matters**: Input Monitoring is granted per code signature, so ad-hoc builds need re-granting every rebuild. `bundle.sh` prefers a real Apple Development / Developer ID identity (`WL_SIGN_IDENTITY` overrides). `swift run` works only because it inherits the terminal's grant.
- **A remote Herdr is an owned ssh**: `SSHTunnel` runs its own `/usr/bin/ssh` (`ControlMaster=no`, so the process lifetime *is* the tunnel's) with a remote `cat >/dev/null` on a stdin pipe we hold, so even a crash ends it by EOF. `HERDR_SOCKET_PATH` beats any tunnel, so the panel disables the target picker when it is set — and Herdr itself sets it for processes it spawns, so a `swift run` from inside a Herdr pane gets the disabled picker (and the `LiveBridgeTargetTests` skip).
- **The socket path is global, so a switch must not leak.** `setSocketPath` bumps a generation; a reply that lands after a switch becomes `HerdrError.targetChanged`, and the bridge guards every async callback (tunnel state, lifecycle restart, status-stream `onClosed`, poll, in-flight `agent.list`) with its own generation counter plus a `herdrActive` flag. Event streams resolve the path in `start()`, not `init`. Pane ids are only unique per server — never carry agent state across targets.
Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,25 +290,25 @@ never issued is a reliable tell. The **Inspector** counts too: it is a separate
process with its own connection to the pad.

The manager also asks macOS which other processes hold the pad open, each time
the panel opens. Over USB that answer is exact: the warning names the other app
("Also driving this pad: Input — colours may fight."), and it clears by itself
the next time you open the panel after that app has quit. **Recheck** next to
the warning checks again on the spot and repaints the pad with the manager's
colours. Switching the manager off clears the warning too.

Some apps (input remappers, macro or streaming tools) open every HID device
without ever touching the lighting, and would keep the warning up for as long
as they run. **Recheck** accepts the apps holding the pad at that moment: they
no longer raise the warning on their own, though they are still named if it
comes up for another reason. It comes back when one of them sends to the pad,
when a different app opens it, or when an accepted app quits and is started
again. Switching the manager off and on forgets what was accepted.
the panel opens and whenever such a reply comes in. The warning names the app
that most likely sent it ("Also driving this pad: Micro Manager Inspector —
colours may fight."), and it clears by itself the next time you open the panel after that
app has quit. **Recheck** next to the warning clears it on the spot and
repaints the pad with the manager's colours. Switching the manager off clears
the warning too.

Holding the pad open is not driving it, so that alone never raises the
warning. Work Louder's Input app keeps the pad open while it runs in the
background, and tells the pad which app is in front on every app switch; the
manager ignores those messages, since they leave the lighting alone. Some apps (input remappers, macro or streaming tools) open
every HID device without ever touching the lighting. The manager remembers
the apps that were holding the pad quietly, and blames a reply on an app that
arrived since. If one of the quiet apps starts sending, the warning names all
of them and stays until they have quit or you press **Recheck**.

Over Bluetooth the pad is a single device that also carries the keyboard, so
apps that only listen for keys (chat apps, for one) may hold it open too. There the
check can name the apps but not raise the warning by itself: it still takes a
reply we never asked for, and it stays until the other app has quit and you
press **Recheck**, or the check finds nobody else at all. See
apps that only listen for keys (chat apps, for one) hold it open too. They
are quiet holders like any other. See
[design/pad-contention.md](design/pad-contention.md) for the details.

## Development
Expand Down
Loading
Loading