headless-primary: make the watcher always-converge
Add a self-healing backstop so recovery no longer depends on an event firing: - periodic reconcile (every 8s): read-only drift check that runs apply ONLY when broken (no headless / DP-1 not mirroring / workspace trapped on a mirror / orphan / off-screen window) — no steady-state flicker. - delayed reconcile 1.5s after a monitor event to catch late workspace reassignment races. - socat auto-reconnect; exit cleanly when the session socket vanishes. New 'reconcile' subcommand. Verified: reconcile heals a deliberately broken mirror and no-ops on a healthy state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,7 @@ clients. Inherent to cloning an ultrawide.
|
||||
|
||||
| Path | Role |
|
||||
|---|---|
|
||||
| `bin/sunshine-headless-primary.sh` | The manager. `apply` establishes/repairs state (idempotent); `rescue` pulls off-screen floating windows back on-screen; `watch` re-applies on monitor hotplug AND auto-rescues windows that open off-screen. |
|
||||
| `bin/sunshine-headless-primary.sh` | The manager. `apply` establishes/repairs state (idempotent); `rescue` pulls off-screen floating windows back on-screen; `reconcile` heals drift only if broken; `watch` the self-healing daemon (events + periodic reconcile backstop + auto-reconnect). |
|
||||
| `bin/sunshine-prestart.sh` | Sunshine `ExecStartPre` — thin wrapper that runs `sunshine-headless-primary.sh apply` before the encoder probe. |
|
||||
| `~/.config/hypr/monitors.conf` | `DP-1` kept **normal** (safety fallback); `HEADLESS-1..4` default to `0x0`. The manager applies the `DP-1 → mirror HEADLESS-1` on top. |
|
||||
| `~/.config/hypr/autostart.conf` | `exec-once` runs `apply` at login and starts the `watch` daemon. |
|
||||
@@ -77,16 +77,28 @@ clients. Inherent to cloning an ultrawide.
|
||||
|
||||
### Self-healing (the watcher)
|
||||
|
||||
`watch` subscribes to Hyprland's event socket and reacts:
|
||||
`watch` is designed so the setup **always converges**, not just when an event
|
||||
fires. Three layers:
|
||||
|
||||
- `monitoradded` / `monitorremoved` → full `apply` (re-impose the mirror,
|
||||
re-consolidate, rescue) — debounced 2s to coalesce the v1+v2 event pair.
|
||||
- `openwindow` → targeted `rescue` of just that window if it opened off-screen
|
||||
(0.3s settle delay first).
|
||||
1. **Events** (fast path) — subscribes to Hyprland's socket:
|
||||
- `monitoradded` / `monitorremoved` → `apply` immediately (debounced 2s to
|
||||
coalesce the v1+v2 burst), then a `reconcile` 1.5s later to catch a
|
||||
workspace Hyprland reassigns to `DP-1` *after* apply already ran.
|
||||
- `openwindow` → targeted `rescue` if that window opened off-screen.
|
||||
2. **Periodic `reconcile` backstop** (every `RECONCILE_SECS`, default 8s) — a
|
||||
read-only drift check that runs `apply` **only when something is actually
|
||||
wrong** (no headless / DP-1 not mirroring / workspace trapped on a mirror /
|
||||
orphaned workspace / off-screen window). This is the guarantee: even if an
|
||||
event is missed entirely, the state self-heals within a few seconds. It does
|
||||
nothing when healthy, so there's no steady-state flicker.
|
||||
3. **Auto-reconnect** — if `socat` drops, the loop reconnects; if the session
|
||||
socket disappears (Hyprland gone) it exits cleanly so the next login's
|
||||
`exec-once` starts a fresh watcher (no cross-session duplicates).
|
||||
|
||||
This is what makes stray windows self-heal: a monitor swap or an app that opens
|
||||
off-screen (OBS, `bluetui`, kdenlive have all done this) gets pulled back into
|
||||
view automatically, no manual hunting.
|
||||
So a monitor swap, a trapped workspace ("can't show workspace 1"), or an app
|
||||
that opens off-screen (OBS, `bluetui`, kdenlive have all done this) gets fixed
|
||||
automatically — near-instant via the event, or within ~8s via the backstop.
|
||||
Force a check manually anytime with `sunshine-headless-primary.sh reconcile`.
|
||||
|
||||
### Critical implementation notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user