Commit Graph
11 Commits
Author SHA1 Message Date
enricobuehlerandClaude Fable 5 a011aebef5 feat(host,web): experimental PnP monitor-devnode disable for Exclusive sessions
Second experiment against the connected-but-dark-head stutter (field-proven
on the reporter's box: unplugging his standby HDMI TV removes a metronomic
~4 s double-jolt; DDC/CI is a dead end for TVs — measured on the lab LG, VCP
0xD6 gets no I2C ACK). An Exclusive isolate only removes physical monitors
from the CCD topology; their PnP devnodes stay live, so every standby wake
(auto input scan, Instant-On HPD cycling) still triggers the full Windows
reaction: PnP arrival/removal, CCD re-evaluation, DWM invalidation — the
suspected hiccup mechanism (Apollo #368's Device-Manager-refresh signature).

- New `pnp_disable_monitors` display-policy axis (default off): orthogonal
  to presets like game_session/ddc_power_off, surfaced in GET/PUT
  /display/settings + the enforced list, carried through the layout
  transform.
- windows/monitor_devnode.rs: after the isolate takes, disable exactly the
  deactivated monitors' devnodes — CCD target → monitor device path
  (DISPLAYCONFIG_TARGET_DEVICE_NAME) → PnP instance id → CM_Disable_DevNode
  with CM_DISABLE_PERSIST, so a hot-plug RE-ARRIVAL stays disabled (that
  persistence is the whole point). Teardown re-enables BEFORE the CCD
  restore (+300 ms re-arrival settle) so restored paths have their monitors
  back. Precise selection — co-installed third-party virtual displays are
  never touched.
- Crash safety: instance ids journal to <config>/pnp-disabled-monitors.json
  before disabling; serve startup re-enables leftovers from a crashed host.
  Worst case is documented in the console help (Device Manager re-enable).
- Web console: second Experimental-badged toggle (the DDC block refactored
  into a shared ExperimentalToggle), EN/DE strings, preset-switch carry.

Verified: Linux 263 tests + clippy + fmt clean; Windows (RTX box) 220/220 +
clippy clean; web tsc + production build clean; openapi.json regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:47:40 +02:00
enricobuehlerandClaude Fable 5 cca5008805 feat(host,web): experimental DDC/CI monitor power-off for Exclusive sessions
The sole-virtual-display stutter investigation's active experiment: when the
Exclusive isolate deactivates a physical monitor, the dark-but-connected head
keeps getting serviced (monitor standby auto-input-scan / DP link churn) at a
seconds-scale cadence — the leading suspect for the periodic double-jolt. A
panel commanded off over DDC/CI (the VESA monitor-control channel in the video
cable) believes it has an owner and, on cooperating firmware, stops probing.

- New `ddc_power_off` display-policy axis (default off): orthogonal to presets
  like game_session, stored in display-settings.json, surfaced in GET/PUT
  /display/settings + the enforced list, carried through the layout transform.
- windows/ddc.rs: VCP 0xD6 power-mode control via the dxva2 Physical Monitor
  API. Deliberately DPMS-off (0x04, DDC stays responsive, signal return wakes)
  and never power-button-off (0x05, bricks-until-button on many monitors).
  Probe-before-write; every failure is skip-and-log — monitors without DDC/CI,
  OSD-disabled, or behind docks/KVMs degrade to a logged no-op.
- Manager wiring: panels commanded off immediately BEFORE the Exclusive CCD
  isolate (an HMONITOR — and with it the DDC channel — only exists while the
  display is active); teardown wakes them right after the CCD restore, where
  returning signal alone already wakes most firmware.
- Web console: an Experimental-badged on/off control on the display card,
  applied immediately like the game-session axis and preserved across preset
  switches; EN/DE strings incl. the wake-failure escape hatch (press the
  monitor's power button once, turn the option off).

Diagnostic value on top of the fix: if this kills a reporter's stutter, the
churn is monitor-firmware-initiated; if only topology=primary/extend does, the
driver services dark heads regardless — the two remaining root-cause classes.

Verified: Linux 258 tests + clippy + fmt clean; Windows (RTX box) 220/220 +
clippy clean; web tsc + production build clean; openapi.json regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:32:36 +02:00
enricobuehlerandClaude Fable 5 8743f63406 polish(web): favicon, Punktfunk casing, display-save toast, idle stop button
- Favicon: the brand-mark lens SVG at /favicon.svg, linked from the root head.
- Branding: user-facing "punktfunk" → "Punktfunk" (tab title, app_name,
  pairing prose, the logo aria-labels/SVG titles) — protocol identifiers
  (punktfunk/1, _punktfunk._udp, binary/module names) stay lowercase.
- The display policy auto-saves on every preset pick / field edit with no
  signal; mount @unom/ui's sonner Toaster in the root and fire a success toast
  from the save funnel's onSuccess (new i18n key, en + de). First toast usage
  in the console — errors stay inline as before.
- The stop-session button rendered destructive-red even with nothing to stop:
  variant switches to the muted `secondary` while no session is active.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 22:18:37 +02:00
enricobuehlerandClaude Opus 4.8 79c4f4cbc1 feat(host): user-defined custom display presets
Save named bundles of the display-management policy (the six behavior axes
plus the game-session axis) as custom presets, alongside the built-ins. A
custom preset is data — stored in <config>/display-presets.json — not a Preset
enum variant, so DisplayPolicy::effective() stays pure and the built-in set is
untouched; applying one writes a Custom policy via the existing PUT
/display/settings.

- policy.rs: CustomPreset/CustomPresetInput + load/add/update/delete store
- mgmt.rs: GET/POST /display/presets + PUT/DELETE /display/presets/{id},
  surfaced on GET /display/settings
- web console: custom-preset cards with save-as / edit / delete + i18n
- regenerated api/openapi.json; docs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:33:43 +00:00
enricobuehlerandClaude Opus 4.8 1992eb1c52 feat(host): game-mode integration + dedicated game sessions
Implements design/gamemode-and-dedicated-sessions.md (Parts A1-A5 + B0-B2):
reconciles the merged display-management registry with session-mobile
Bazzite/SteamOS hosts and adds a per-launch dedicated gamescope mode.

- A1 DisplayOwnership {Owned,External,SessionManaged} + poolable_now(): the
  registry pools only what it owns, so gamescope managed/attach outputs are no
  longer double-owned by the registry AND the gamescope restore worker (fixes
  the game-mode-reconnect stale-node wedge).
- A2 validated reuse: (backend,mode,launch,epoch) reuse key + kept_display_alive
  liveness probe + reused_gen/mark_failed on a reused-display first-frame failure.
- A3 policy-driven managed restore (keep_alive replaces the hardcoded 5s debounce;
  forever = held = gaming-rig truthful) + crash-restore persist + SIGKILL teardown
  (kill_unit, applied to our transient unit AND the autologin stop -- validated
  live on .181 to avoid the F44 GPU-context leak).
- A4 session epoch: observe_session_instance bumps the epoch + invalidate_backend
  on a desktop-compositor instance change; gamescope spawns are exempt.
- A5 per-spawn log + PID-scoped gamescope node discovery.
- B0 game_session {auto,dedicated} policy (top-level, preset-orthogonal) +
  pick_gamescope_mode dedicated_launch + steam -silent command shaping.
- B1 free the autologin Steam before a dedicated Steam spawn (single-instance).
- B2 game-exit -> APP_EXITED_CLOSE_CODE (0x52) clean session end.

Adversarially reviewed (11 findings fixed). Validated on glass (.181 Bazzite F44,
RTX 4090): dedicated spawn streams a real game smoothly; keep-alive reuse; the
SIGKILL fix avoids the F44 vkCreateDevice leak. Workspace green
(build / test --workspace / clippy -D warnings / fmt), OpenAPI + C header
regenerated, web console tsc + vite build green. clients/probe: bump the
no-video timeout 8s->45s for gamescope cold starts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:33:43 +00:00
enricobuehlerandClaude Opus 4.8 b28ddfc2d4 fix(web): clearer topology/identity copy, capped description width, mobile More-nav, preset spacing
Console polish on the Virtual displays card + shell:
- Topology help now leads with the streamed display's role (Extend/Primary/Exclusive) instead of
  the confusing physical-monitor-only framing; notes the headless case. Identity help spells out the
  actual behavior (stable per-client identity → the desktop reapplies that client's scaling/resolution
  on reconnect) + what Shared / Per-client / Per-client+resolution each do.
- Cap description/help width at max-w-prose so long help text isn't a full-viewport line on large screens.
- Mobile bottom nav: 8 flat tabs were too cramped → 4 pinned tabs + a "More" tab whose sheet holds the
  rest (Performance/Logs/Pairing/Settings), "More" highlighted when the active route is in the overflow.
- More breathing room under the "Preset" heading.

web tsc + biome + vite build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 20:40:35 +00:00
enricobuehlerandClaude Opus 4.8 ac030bbda0 feat(vdisplay): ship keep_alive=forever (gaming-rig) — Windows MgrState::Pinned
Completes the last §6A-era preset. The Linux registry already resolved forever→Pinned (pure
lifecycle machine); the blockers were the Windows manager, the mgmt reject, and the console tag:

- Windows manager: new `MgrState::Pinned { mon }` — the last-released monitor under keep_alive=forever
  is kept indefinitely (like Lingering but the linger timer never fires). A reconnect preempts +
  recreates it (same as Lingering — a reused IddCx swap-chain is dead), snapshot reports "pinned",
  and `force_release` (POST /display/release, the §8 escape hatch) frees a pinned monitor. release()
  branches on the new `keep_alive_forever()`; all MgrState matches made exhaustive over Pinned.
- mgmt PUT /display/settings: stop rejecting keep_alive=forever (now honored on both platforms with a
  release path). OpenAPI regenerated.
- web: un-disable the gaming-rig preset (DISABLED_PRESETS now empty) — one-click applies.

Linux paths + web/tsc/openapi green; 47 vdisplay tests pass. The Windows manager.rs is #[cfg(windows)]
(not compilable on the Linux dev box) — build-verified + on-glass validation on .173 to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:26:43 +00:00
enricobuehlerandClaude Opus 4.8 956420d947 fix(web): preset cards use the design-system animated Card (motion + material)
The preset options were raw <button>s — flat, no motion/material — unlike the rest of
the console. They now render as the `interactive` AnimatedCard (motion hover + specular
material, consistent with every other card), keyboard-accessible (role=button + Enter/
Space), with a 2px primary ring for the active one and a proper disabled state for
gaming-rig.

web tsc + vite build + biome-lint green; deployed on .21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:18:24 +00:00
enricobuehlerandClaude Opus 4.8 42ea12ad8f fix(web): unify display-field spacing (shared Field) + clearer layout help
- Every option in the custom form now renders through one `Field` wrapper (label →
  control → help at a consistent `space-y-3`), so the label→input gap is roomier and
  identical across keep-alive, the button groups, and max-displays — the first field no
  longer spaces differently from the rest.
- Reworded the multi-monitor layout help: it now says Auto is side-by-side and Manual
  gives a per-display X/Y editor "in the Live displays section below once two or more are
  streaming" — instead of pointing at an "arrangement table" that isn't visible until
  clients connect.

web tsc + vite build + biome-lint green; deployed on .21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:07:45 +00:00
enricobuehlerandClaude Opus 4.8 5b71a74da5 fix(web): explicit keep-alive Off/Keep toggle + roomier custom display form
Two UX fixes on the Virtual displays Configuration card:

- Keep-alive is no longer implicitly "on" by typing in the seconds field. It's an
  explicit two-button toggle — **Off** (tear down at disconnect) vs. **Keep for** [N]
  seconds — and the seconds input only appears when "Keep for" is selected. The
  duration is remembered across toggles, and the help text explains both modes.
- Opened up the cramped custom form: the fields container is `space-y-6` with more
  padding (`p-5`, rounded-lg), each option group is `space-y-2.5`, and the Save button
  sits below a divider — so it reads as sections with room instead of a pressed stack.

web tsc + vite build + biome-lint green; deployed on .21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:00:57 +00:00
enricobuehlerandClaude Opus 4.8 167b3ae251 feat(web): move Virtual displays to its own nav section; roomier preset grid
The Host page was crowded (identity, codecs, ports, GPU, displays, compositors) and the
virtual-display config surface is large enough to warrant its own home.

- New **Virtual displays** nav section: `/displays` route + `sections/Displays` (moved
  DisplayCard out of `sections/Host`), a `MonitorPlay` sidebar entry after Host, and
  `nav_displays` i18n. Removed the displays card from the Host page/view.
- On its own page the card splits into two: **Configuration** (presets + custom axes) and
  **Live displays** (the live list + arrangement table) — room to breathe.
- Presets now render in a max-2-column grid (`sm:grid-cols-2`) with larger padding, a bigger
  section heading + preset titles (text-base semibold), roomier spacing, and bottom-aligned
  "what it sets" badges so the cards line up.

web tsc + vite build + biome-lint green; deployed + verified on the Mutter box (.21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 14:54:24 +00:00