feat(host,web): experimental PnP monitor-devnode disable for Exclusive sessions
ci / web (push) Successful in 1m0s
ci / docs-site (push) Successful in 1m6s
apple / swift (push) Successful in 1m9s
decky / build-publish (push) Successful in 22s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 47s
windows-host / package (push) Failing after 3m40s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m54s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m21s
flatpak / build-publish (push) Failing after 2m26s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 1m2s
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
release / apple (push) Failing after 19m24s
ci / web (push) Successful in 1m0s
ci / docs-site (push) Successful in 1m6s
apple / swift (push) Successful in 1m9s
decky / build-publish (push) Successful in 22s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 47s
windows-host / package (push) Failing after 3m40s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m54s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m21s
flatpak / build-publish (push) Failing after 2m26s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 1m2s
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
release / apple (push) Failing after 19m24s
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>
This commit is contained in:
@@ -165,9 +165,11 @@ const DisplayForm: FC<{
|
||||
identity: effective.identity,
|
||||
layout: effective.layout,
|
||||
max_displays: effective.max_displays,
|
||||
// Game-session + DDC are orthogonal to the preset — carry them through the Custom switch.
|
||||
// Game-session + the experimental axes are orthogonal to the preset — carry them
|
||||
// through the Custom switch.
|
||||
game_session: draft.game_session ?? "auto",
|
||||
ddc_power_off: draft.ddc_power_off ?? false,
|
||||
pnp_disable_monitors: draft.pnp_disable_monitors ?? false,
|
||||
});
|
||||
} else {
|
||||
apply({ ...draft, preset: id as Preset });
|
||||
@@ -182,8 +184,9 @@ const DisplayForm: FC<{
|
||||
preset: "custom",
|
||||
...p.fields,
|
||||
game_session: p.game_session ?? "auto",
|
||||
// The experimental DDC axis isn't part of a preset — keep the current setting.
|
||||
// The experimental axes aren't part of a preset — keep the current settings.
|
||||
ddc_power_off: draft.ddc_power_off ?? false,
|
||||
pnp_disable_monitors: draft.pnp_disable_monitors ?? false,
|
||||
});
|
||||
|
||||
// A custom card is "current" when the in-force policy is a Custom one whose fields + game-session
|
||||
@@ -467,36 +470,34 @@ const DisplayForm: FC<{
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* EXPERIMENTAL: DDC/CI panel power-off — orthogonal like game-session (survives preset
|
||||
switches, applies immediately). Windows-only in effect, acted on at the Exclusive isolate. */}
|
||||
<div className="border-t pt-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="block">{m.display_ddc()}</Label>
|
||||
<Badge variant="outline" className="text-amber-600 dark:text-amber-500">
|
||||
{m.display_experimental()}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{([false, true] as const).map((on) => (
|
||||
<Button
|
||||
key={String(on)}
|
||||
size="sm"
|
||||
variant={(draft.ddc_power_off ?? false) === on ? "default" : "outline"}
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
const next = { ...draft, ddc_power_off: on };
|
||||
setDraft(next);
|
||||
apply(next);
|
||||
}}
|
||||
>
|
||||
{on ? m.display_ddc_enabled() : m.display_ddc_disabled()}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<p className="max-w-prose text-xs text-muted-foreground">{m.display_ddc_help()}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* EXPERIMENTAL toggles — orthogonal like game-session (survive preset switches, apply
|
||||
immediately). Windows-only in effect, acted on at the Exclusive isolate. */}
|
||||
<ExperimentalToggle
|
||||
label={m.display_ddc()}
|
||||
help={m.display_ddc_help()}
|
||||
value={draft.ddc_power_off ?? false}
|
||||
offLabel={m.display_ddc_disabled()}
|
||||
onLabel={m.display_ddc_enabled()}
|
||||
busy={busy}
|
||||
onSet={(on) => {
|
||||
const next = { ...draft, ddc_power_off: on };
|
||||
setDraft(next);
|
||||
apply(next);
|
||||
}}
|
||||
/>
|
||||
<ExperimentalToggle
|
||||
label={m.display_pnp()}
|
||||
help={m.display_pnp_help()}
|
||||
value={draft.pnp_disable_monitors ?? false}
|
||||
offLabel={m.display_pnp_disabled()}
|
||||
onLabel={m.display_pnp_enabled()}
|
||||
busy={busy}
|
||||
onSet={(on) => {
|
||||
const next = { ...draft, pnp_disable_monitors: on };
|
||||
setDraft(next);
|
||||
apply(next);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* What's in force right now */}
|
||||
<div className="flex flex-wrap items-center gap-2 border-t pt-3">
|
||||
@@ -513,6 +514,9 @@ const DisplayForm: FC<{
|
||||
{(draft.ddc_power_off ?? false) && (
|
||||
<Badge variant="outline">{m.display_ddc_badge()}</Badge>
|
||||
)}
|
||||
{(draft.pnp_disable_monitors ?? false) && (
|
||||
<Badge variant="outline">{m.display_pnp_badge()}</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="max-w-prose text-xs text-muted-foreground">{m.display_pending_note()}</p>
|
||||
@@ -535,6 +539,45 @@ const Field: FC<{ label: string; help?: string; children: ReactNode }> = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* An Experimental-badged on/off policy toggle (the DDC/CI and PnP monitor axes) — rendered outside
|
||||
* the Custom block like the game-session axis: survives preset switches and applies immediately.
|
||||
*/
|
||||
const ExperimentalToggle: FC<{
|
||||
label: string;
|
||||
help: string;
|
||||
value: boolean;
|
||||
offLabel: string;
|
||||
onLabel: string;
|
||||
busy: boolean;
|
||||
onSet: (v: boolean) => void;
|
||||
}> = ({ label, help, value, offLabel, onLabel, busy, onSet }) => (
|
||||
<div className="border-t pt-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="block">{label}</Label>
|
||||
<Badge variant="outline" className="text-amber-600 dark:text-amber-500">
|
||||
{m.display_experimental()}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{([false, true] as const).map((on) => (
|
||||
<Button
|
||||
key={String(on)}
|
||||
size="sm"
|
||||
variant={value === on ? "default" : "outline"}
|
||||
disabled={busy}
|
||||
onClick={() => onSet(on)}
|
||||
>
|
||||
{on ? onLabel : offLabel}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<p className="max-w-prose text-xs text-muted-foreground">{help}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/** A [`Field`] whose control is a row of mutually-exclusive option buttons (topology / conflict / …). */
|
||||
const Choice: FC<{
|
||||
label: string;
|
||||
|
||||
Reference in New Issue
Block a user