Five field requests from one returning user ("just about ready as a daily driver"), traced through the code and implemented together — plus a compile break found on the way that this branch fixes first.
⚠ First commit: main's Windows host does not compile since #408
bbc01cdd (ABR Phase 4, merged this morning) threaded EncDerive through prepare_display, but the call site in native/handshake.rs passes enc_ofbeforebit_depth while the signature takes bit_depth first — a type error inside a cfg(target_os = "windows") block. Nothing is red because the ci.yml clippy job runs on Linux and never compiles that block, and windows-host.yml only runs on dispatch/release. The fix is a deliberately minimal standalone commit at the base of this branch, cherry-pickable as a hotfix if this PR sits.
10-bit SDR, independent of HDR
VIDEO_CAP_10BIT and VIDEO_CAP_HDR were always separate wire bits; everything above the wire welded them — the client advertised both from the one HDR switch, the handshake demanded an HDR capture source for any 10-bit depth, SessionPlan derived hdr from bit_depth >= 10, and the encoder stamped BT.2020 PQ on every 10-bit format.
Now a "10-bit SDR" client setting (tier-P, default off) advertises the depth bit alone. The handshake resolves bit_depth = 10 without the HDR gates where the SDR-10 chain exists — Windows IDD-push + direct NVENC + HEVC; everywhere else the session stays 8-bit and the Welcome says so before the client builds its decoder. The session's HDR verdict (session_hdr) is decoupled from the depth everywhere it was derived: the Welcome's colour label, SessionPlan::resolve (new hdr parameter), and both vd.set_hdr sites. The capture side grows PixelFormat::Rgb10a2Sdr — the BGRA slot expanded 8→10 by a trivial full-res pass (HdrRgb10Converter::new_sdr_expand) — which NVENC ingests as ABGR10 and encodes Main10 under the ordinary BT.709 SDR VUI, 4:2:0 and 4:4:4 alike. The display's colour state is never touched at either end; advanced colour stays pinned off.
Honest scope: the desktop stays 8-bit — the win is the encoder's precision (compression stops adding banding to gradients), which is what the report describes. The client already renders by the bitstream VUI (csc_rows is depth×matrix generic), so no decode-side change was needed. AV1 is excluded until NVENC's packed-RGB→10-bit-AV1 ingest is measured on glass.
"Capture system shortcuts" works in Desktop mouse mode
The setting was wired end to end and then discarded by one !desktop term in the presenter's grab condition — documented as "a remote desktop is something you Alt-Tab away from", which made the toggle inert for exactly the remote-desktop use it matters most for. The grab now follows the setting in both mouse models; desktop mode keeps an escape capture mode never had (the unlocked pointer clicks any other window, focus drops, the chords return). Docs updated on both pages; the Mac's ⌘-chord path is unchanged (its desktop-mouse behaviour was a separate, deliberate decision).
Keep host audio playing (per session)
Moonlight's "Mute host PC speakers" box, unchecked, as a per-session client choice — for streaming to a laptop while wearing headphones that are plugged into the host. New setting + CLIENT_CAP_KEEP_HOST_AUDIO (0x20, request-only, no HOST_CAP echo: an older host just re-routes as always, which degrades to "host went quiet", never a broken session). A session holding the bit gets the host's audio devices left alone for exactly its lifetime (RAII guard): Windows skips the IPolicyConfig default parking and loopbacks the operator's own default device; Linux taps the default sink's monitor instead of claiming the default. Host-global best-effort across concurrent sessions, as the cap documents. Rows in the GTK, WinUI and console-UI settings; desktop-only until the Android session advertises the bit.
Prep commands carry the negotiated mode
A per-app do/undo step's entire environment was PF_APP_ID; the width/height/refresh/HDR a per-mode frame cap needs were resolved 80 lines above and written only to the unix-only marker file. Both serving planes now pass PF_STREAM_WIDTH / _HEIGHT / _REFRESH / _HDR — the marker file's vocabulary, spelled once in hooks::prep_mode_env — so an RTSS cap is one step instead of one hard-coded entry per device, on Windows hosts too.
The forwarded cursor matches the client's DPI
The desktop-mode pointer was resampled by the video fit factor alone, and SDL shows a custom cursor surface at ~1:1 physical pixels on every backend — so on a 200 % client our pointer was half the size of every native one (X11 identically; Wayland escapes via buffer scale). The resample factor now folds in SDL_GetWindowDisplayScale — the same model the Apple client gets for free from point-sized NSCursors.
Verification
Rebased onto 45d3ff6a and re-gated. Green: cargo fmt --all --check; workspace clippy --all-targets --locked -- -D warnings (Linux, pf-gtkflow image); punktfunk-core 279, pf-client-core 227, pf-console-ui 205, host hooks:: 13 + audio:: 127 — including the four new tests (prep env vocabulary, the keep-host-audio guard count, the caps-bit distinctness, depth-without-HDR advertising); pf-capture + pf-frame for x86_64-pc-windows-msvc via scripts/xcheck.sh.
Not verified — owed: the Windows-only halves of pf-encode and punktfunk-host (no Windows toolchain was reachable; note windows-host.yml is dispatch-only, which is also how #408's break slipped through), and everything on-glass: the SDR-10 stream end to end on a real NVIDIA host (the one that matters most), the desktop-mode grab on Windows, keep-host-audio against real headphones, prep env on a live launch, and the cursor at 200 %.
Five field requests from one returning user ("just about ready as a daily driver"), traced through the code and implemented together — plus a compile break found on the way that this branch fixes first.
## ⚠ First commit: main's Windows host does not compile since #408
`bbc01cdd` (ABR Phase 4, merged this morning) threaded `EncDerive` through `prepare_display`, but the call site in `native/handshake.rs` passes `enc_of` **before** `bit_depth` while the signature takes `bit_depth` first — a type error inside a `cfg(target_os = "windows")` block. Nothing is red because the ci.yml clippy job runs on Linux and never compiles that block, and `windows-host.yml` only runs on dispatch/release. The fix is a deliberately minimal standalone commit at the base of this branch, cherry-pickable as a hotfix if this PR sits.
## 10-bit SDR, independent of HDR
`VIDEO_CAP_10BIT` and `VIDEO_CAP_HDR` were always separate wire bits; everything above the wire welded them — the client advertised both from the one HDR switch, the handshake demanded an HDR capture source for any 10-bit depth, `SessionPlan` derived `hdr` from `bit_depth >= 10`, and the encoder stamped BT.2020 PQ on every 10-bit format.
Now a **"10-bit SDR"** client setting (tier-P, default off) advertises the depth bit alone. The handshake resolves `bit_depth = 10` without the HDR gates where the SDR-10 chain exists — Windows IDD-push + direct NVENC + HEVC; everywhere else the session stays 8-bit and the Welcome says so before the client builds its decoder. The session's HDR verdict (`session_hdr`) is decoupled from the depth everywhere it was derived: the Welcome's colour label, `SessionPlan::resolve` (new `hdr` parameter), and both `vd.set_hdr` sites. The capture side grows `PixelFormat::Rgb10a2Sdr` — the BGRA slot expanded 8→10 by a trivial full-res pass (`HdrRgb10Converter::new_sdr_expand`) — which NVENC ingests as ABGR10 and encodes Main10 under the ordinary BT.709 SDR VUI, 4:2:0 and 4:4:4 alike. The display's colour state is never touched at either end; advanced colour stays pinned off.
Honest scope: the desktop stays 8-bit — the win is the *encoder's* precision (compression stops adding banding to gradients), which is what the report describes. The client already renders by the bitstream VUI (`csc_rows` is depth×matrix generic), so no decode-side change was needed. AV1 is excluded until NVENC's packed-RGB→10-bit-AV1 ingest is measured on glass.
## "Capture system shortcuts" works in Desktop mouse mode
The setting was wired end to end and then discarded by one `!desktop` term in the presenter's grab condition — documented as "a remote desktop is something you Alt-Tab away from", which made the toggle inert for exactly the remote-desktop use it matters most for. The grab now follows the setting in both mouse models; desktop mode keeps an escape capture mode never had (the unlocked pointer clicks any other window, focus drops, the chords return). Docs updated on both pages; the Mac's ⌘-chord path is unchanged (its desktop-mouse behaviour was a separate, deliberate decision).
## Keep host audio playing (per session)
Moonlight's "Mute host PC speakers" box, unchecked, as a per-session client choice — for streaming to a laptop while wearing headphones that are plugged into the host. New setting + `CLIENT_CAP_KEEP_HOST_AUDIO` (0x20, request-only, no HOST_CAP echo: an older host just re-routes as always, which degrades to "host went quiet", never a broken session). A session holding the bit gets the host's audio devices left alone for exactly its lifetime (RAII guard): Windows skips the IPolicyConfig default parking and loopbacks the operator's own default device; Linux taps the default sink's monitor instead of claiming the default. Host-global best-effort across concurrent sessions, as the cap documents. Rows in the GTK, WinUI and console-UI settings; desktop-only until the Android session advertises the bit.
## Prep commands carry the negotiated mode
A per-app `do`/`undo` step's entire environment was `PF_APP_ID`; the width/height/refresh/HDR a per-mode frame cap needs were resolved 80 lines above and written only to the unix-only marker file. Both serving planes now pass `PF_STREAM_WIDTH` / `_HEIGHT` / `_REFRESH` / `_HDR` — the marker file's vocabulary, spelled once in `hooks::prep_mode_env` — so an RTSS cap is one step instead of one hard-coded entry per device, on Windows hosts too.
## The forwarded cursor matches the client's DPI
The desktop-mode pointer was resampled by the video fit factor alone, and SDL shows a custom cursor surface at ~1:1 physical pixels on every backend — so on a 200 % client our pointer was half the size of every native one (X11 identically; Wayland escapes via buffer scale). The resample factor now folds in `SDL_GetWindowDisplayScale` — the same model the Apple client gets for free from point-sized `NSCursor`s.
## Verification
Rebased onto `45d3ff6a` and re-gated. Green: `cargo fmt --all --check`; workspace `clippy --all-targets --locked -- -D warnings` (Linux, pf-gtkflow image); punktfunk-core 279, pf-client-core 227, pf-console-ui 205, host `hooks::` 13 + `audio::` 127 — including the four new tests (prep env vocabulary, the keep-host-audio guard count, the caps-bit distinctness, depth-without-HDR advertising); pf-capture + pf-frame for `x86_64-pc-windows-msvc` via `scripts/xcheck.sh`.
**Not verified — owed:** the Windows-only halves of pf-encode and punktfunk-host (no Windows toolchain was reachable; note `windows-host.yml` is dispatch-only, which is also how #408's break slipped through), and everything on-glass: the SDR-10 stream end to end on a real NVIDIA host (the one that matters most), the desktop-mode grab on Windows, keep-host-audio against real headphones, prep env on a live launch, and the cursor at 200 %.
bbc01cdd (ABR overhaul Phase 4, merged today as #408) threaded the new EncDerive
through prepare_display, but the call site in native/handshake.rs passes enc_of
BEFORE bit_depth while the signature takes bit_depth first — a type error in a
cfg(target_os = "windows") block. Nothing caught it: the ci.yml clippy job runs
on Linux and never compiles this block, and windows-host.yml only runs on
dispatch/release, so main's Windows host is currently unbuildable and no check
is red.
Reorder the arguments to match the signature. (--no-verify: this commit is cut
as a minimal cherry-pickable hotfix from a branch whose full fmt-clean state
lands in the following commits.)
Five field requests from one returning user, traced and fixed together
(report: the "Daily-Driver Five" triage).
**10-bit SDR, independent of HDR.** VIDEO_CAP_10BIT and VIDEO_CAP_HDR were
always separate wire bits, but everything above the wire welded them: the
client advertised both from one "HDR" switch, the handshake required an HDR
capture source for any 10-bit depth, SessionPlan derived hdr from bit_depth,
and the encoder stamped BT.2020 PQ on every 10-bit format. Now a client
setting ("10-bit SDR", default off, tier-P) advertises the depth bit alone;
the handshake resolves bit_depth=10 without the HDR gates where the SDR-10
chain exists (Windows IDD-push + direct NVENC + HEVC — everywhere else the
session stays 8-bit, said honestly in the Welcome); the Welcome's colour
label, the virtual display's HDR bring-up and the capturer's want-HDR flag
all follow the new session-hdr verdict instead of the depth. The capture
side grows PixelFormat::Rgb10a2Sdr — the BGRA slot expanded 8→10 by a
trivial full-res pass (HdrRgb10Converter::new_sdr_expand) — which NVENC
ingests as ABGR10 and encodes Main10 under the ordinary BT.709 SDR VUI,
both 4:2:0 and 4:4:4. The win is the ENCODER's precision (compression
stops adding banding to gradients); the desktop stays 8-bit and neither
display's colour state is touched. The client already decodes by the VUI,
so no decode-side change was needed.
**"Capture system shortcuts" now works in Desktop mouse mode.** The setting
was wired end to end and then discarded by one `!desktop` term in the
presenter's grab condition — documented as "a remote desktop is something
you Alt-Tab away from", which made the toggle inert for exactly the
remote-desktop use it matters most for. The keyboard grab now follows the
setting in BOTH mouse models; desktop mode keeps an escape capture mode
never had (the unlocked pointer clicks any other window, focus drops, the
chords come back). Docs updated; the Mac's ⌘-chord behaviour is unchanged.
**Keep host audio playing (Moonlight's "Mute host PC speakers", unchecked,
per session).** New client setting + CLIENT_CAP_KEEP_HOST_AUDIO (0x20,
request-only, no HOST_CAP echo — an older host just re-routes as always).
A session holding the bit gets the host's audio devices left alone for its
lifetime (RAII guard): Windows skips the IPolicyConfig default parking and
loopbacks the operator's own default device; Linux taps the default sink's
monitor instead of claiming the default. Host-global best-effort across
concurrent sessions, as the cap documents. Rows in the GTK, Windows and
console-UI settings (desktop-only until Android advertises the bit).
**Prep commands carry the negotiated mode.** A per-app do/undo step's whole
environment was PF_APP_ID; the width/height/refresh/HDR it needs for a
per-mode frame cap were resolved 80 lines above and written only to the
unix-only marker file. Both serving planes now pass PF_STREAM_WIDTH /
_HEIGHT / _REFRESH / _HDR (the marker file's vocabulary, via one shared
hooks::prep_mode_env) — so an RTSS cap is one step instead of one
hard-coded entry per device, on Windows hosts too.
**The forwarded cursor matches the client's DPI.** The desktop-mode pointer
was resampled by the video fit factor alone, and SDL shows a custom cursor
surface at ~1:1 physical pixels on every backend — so on a 200% client our
pointer was half the size of every native one (and X11 the same; Wayland
escapes via buffer scale). The resample factor now folds in
SDL_GetWindowDisplayScale, the same model the Apple client gets for free
from point-sized NSCursors.
Verified: workspace clippy --locked -D warnings + targeted tests (hooks,
profiles, caps, console-ui rows, capture-policy) green on Linux
(pf-gtkflow); pf-capture/pf-frame windows-msvc green via xcheck; rustfmt
clean. NOT verified: the Windows-only halves of pf-encode/punktfunk-host
(no reachable Windows toolchain — .133 down; windows-host.yml is
dispatch-only, see the preceding commit), and everything on-glass: the
SDR-10 stream end to end, the desktop-mode grab, keep-host-audio wiring,
prep env on a live launch, and the cursor size at 200%.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Five field requests from one returning user ("just about ready as a daily driver"), traced through the code and implemented together — plus a compile break found on the way that this branch fixes first.
⚠ First commit: main's Windows host does not compile since #408
bbc01cdd(ABR Phase 4, merged this morning) threadedEncDerivethroughprepare_display, but the call site innative/handshake.rspassesenc_ofbeforebit_depthwhile the signature takesbit_depthfirst — a type error inside acfg(target_os = "windows")block. Nothing is red because the ci.yml clippy job runs on Linux and never compiles that block, andwindows-host.ymlonly runs on dispatch/release. The fix is a deliberately minimal standalone commit at the base of this branch, cherry-pickable as a hotfix if this PR sits.10-bit SDR, independent of HDR
VIDEO_CAP_10BITandVIDEO_CAP_HDRwere always separate wire bits; everything above the wire welded them — the client advertised both from the one HDR switch, the handshake demanded an HDR capture source for any 10-bit depth,SessionPlanderivedhdrfrombit_depth >= 10, and the encoder stamped BT.2020 PQ on every 10-bit format.Now a "10-bit SDR" client setting (tier-P, default off) advertises the depth bit alone. The handshake resolves
bit_depth = 10without the HDR gates where the SDR-10 chain exists — Windows IDD-push + direct NVENC + HEVC; everywhere else the session stays 8-bit and the Welcome says so before the client builds its decoder. The session's HDR verdict (session_hdr) is decoupled from the depth everywhere it was derived: the Welcome's colour label,SessionPlan::resolve(newhdrparameter), and bothvd.set_hdrsites. The capture side growsPixelFormat::Rgb10a2Sdr— the BGRA slot expanded 8→10 by a trivial full-res pass (HdrRgb10Converter::new_sdr_expand) — which NVENC ingests as ABGR10 and encodes Main10 under the ordinary BT.709 SDR VUI, 4:2:0 and 4:4:4 alike. The display's colour state is never touched at either end; advanced colour stays pinned off.Honest scope: the desktop stays 8-bit — the win is the encoder's precision (compression stops adding banding to gradients), which is what the report describes. The client already renders by the bitstream VUI (
csc_rowsis depth×matrix generic), so no decode-side change was needed. AV1 is excluded until NVENC's packed-RGB→10-bit-AV1 ingest is measured on glass."Capture system shortcuts" works in Desktop mouse mode
The setting was wired end to end and then discarded by one
!desktopterm in the presenter's grab condition — documented as "a remote desktop is something you Alt-Tab away from", which made the toggle inert for exactly the remote-desktop use it matters most for. The grab now follows the setting in both mouse models; desktop mode keeps an escape capture mode never had (the unlocked pointer clicks any other window, focus drops, the chords return). Docs updated on both pages; the Mac's ⌘-chord path is unchanged (its desktop-mouse behaviour was a separate, deliberate decision).Keep host audio playing (per session)
Moonlight's "Mute host PC speakers" box, unchecked, as a per-session client choice — for streaming to a laptop while wearing headphones that are plugged into the host. New setting +
CLIENT_CAP_KEEP_HOST_AUDIO(0x20, request-only, no HOST_CAP echo: an older host just re-routes as always, which degrades to "host went quiet", never a broken session). A session holding the bit gets the host's audio devices left alone for exactly its lifetime (RAII guard): Windows skips the IPolicyConfig default parking and loopbacks the operator's own default device; Linux taps the default sink's monitor instead of claiming the default. Host-global best-effort across concurrent sessions, as the cap documents. Rows in the GTK, WinUI and console-UI settings; desktop-only until the Android session advertises the bit.Prep commands carry the negotiated mode
A per-app
do/undostep's entire environment wasPF_APP_ID; the width/height/refresh/HDR a per-mode frame cap needs were resolved 80 lines above and written only to the unix-only marker file. Both serving planes now passPF_STREAM_WIDTH/_HEIGHT/_REFRESH/_HDR— the marker file's vocabulary, spelled once inhooks::prep_mode_env— so an RTSS cap is one step instead of one hard-coded entry per device, on Windows hosts too.The forwarded cursor matches the client's DPI
The desktop-mode pointer was resampled by the video fit factor alone, and SDL shows a custom cursor surface at ~1:1 physical pixels on every backend — so on a 200 % client our pointer was half the size of every native one (X11 identically; Wayland escapes via buffer scale). The resample factor now folds in
SDL_GetWindowDisplayScale— the same model the Apple client gets for free from point-sizedNSCursors.Verification
Rebased onto
45d3ff6aand re-gated. Green:cargo fmt --all --check; workspaceclippy --all-targets --locked -- -D warnings(Linux, pf-gtkflow image); punktfunk-core 279, pf-client-core 227, pf-console-ui 205, hosthooks::13 +audio::127 — including the four new tests (prep env vocabulary, the keep-host-audio guard count, the caps-bit distinctness, depth-without-HDR advertising); pf-capture + pf-frame forx86_64-pc-windows-msvcviascripts/xcheck.sh.Not verified — owed: the Windows-only halves of pf-encode and punktfunk-host (no Windows toolchain was reachable; note
windows-host.ymlis dispatch-only, which is also how #408's break slipped through), and everything on-glass: the SDR-10 stream end to end on a real NVIDIA host (the one that matters most), the desktop-mode grab on Windows, keep-host-audio against real headphones, prep env on a live launch, and the cursor at 200 %.Five field requests from one returning user, traced and fixed together (report: the "Daily-Driver Five" triage). **10-bit SDR, independent of HDR.** VIDEO_CAP_10BIT and VIDEO_CAP_HDR were always separate wire bits, but everything above the wire welded them: the client advertised both from one "HDR" switch, the handshake required an HDR capture source for any 10-bit depth, SessionPlan derived hdr from bit_depth, and the encoder stamped BT.2020 PQ on every 10-bit format. Now a client setting ("10-bit SDR", default off, tier-P) advertises the depth bit alone; the handshake resolves bit_depth=10 without the HDR gates where the SDR-10 chain exists (Windows IDD-push + direct NVENC + HEVC — everywhere else the session stays 8-bit, said honestly in the Welcome); the Welcome's colour label, the virtual display's HDR bring-up and the capturer's want-HDR flag all follow the new session-hdr verdict instead of the depth. The capture side grows PixelFormat::Rgb10a2Sdr — the BGRA slot expanded 8→10 by a trivial full-res pass (HdrRgb10Converter::new_sdr_expand) — which NVENC ingests as ABGR10 and encodes Main10 under the ordinary BT.709 SDR VUI, both 4:2:0 and 4:4:4. The win is the ENCODER's precision (compression stops adding banding to gradients); the desktop stays 8-bit and neither display's colour state is touched. The client already decodes by the VUI, so no decode-side change was needed. **"Capture system shortcuts" now works in Desktop mouse mode.** The setting was wired end to end and then discarded by one `!desktop` term in the presenter's grab condition — documented as "a remote desktop is something you Alt-Tab away from", which made the toggle inert for exactly the remote-desktop use it matters most for. The keyboard grab now follows the setting in BOTH mouse models; desktop mode keeps an escape capture mode never had (the unlocked pointer clicks any other window, focus drops, the chords come back). Docs updated; the Mac's ⌘-chord behaviour is unchanged. **Keep host audio playing (Moonlight's "Mute host PC speakers", unchecked, per session).** New client setting + CLIENT_CAP_KEEP_HOST_AUDIO (0x20, request-only, no HOST_CAP echo — an older host just re-routes as always). A session holding the bit gets the host's audio devices left alone for its lifetime (RAII guard): Windows skips the IPolicyConfig default parking and loopbacks the operator's own default device; Linux taps the default sink's monitor instead of claiming the default. Host-global best-effort across concurrent sessions, as the cap documents. Rows in the GTK, Windows and console-UI settings (desktop-only until Android advertises the bit). **Prep commands carry the negotiated mode.** A per-app do/undo step's whole environment was PF_APP_ID; the width/height/refresh/HDR it needs for a per-mode frame cap were resolved 80 lines above and written only to the unix-only marker file. Both serving planes now pass PF_STREAM_WIDTH / _HEIGHT / _REFRESH / _HDR (the marker file's vocabulary, via one shared hooks::prep_mode_env) — so an RTSS cap is one step instead of one hard-coded entry per device, on Windows hosts too. **The forwarded cursor matches the client's DPI.** The desktop-mode pointer was resampled by the video fit factor alone, and SDL shows a custom cursor surface at ~1:1 physical pixels on every backend — so on a 200% client our pointer was half the size of every native one (and X11 the same; Wayland escapes via buffer scale). The resample factor now folds in SDL_GetWindowDisplayScale, the same model the Apple client gets for free from point-sized NSCursors. Verified: workspace clippy --locked -D warnings + targeted tests (hooks, profiles, caps, console-ui rows, capture-policy) green on Linux (pf-gtkflow); pf-capture/pf-frame windows-msvc green via xcheck; rustfmt clean. NOT verified: the Windows-only halves of pf-encode/punktfunk-host (no reachable Windows toolchain — .133 down; windows-host.yml is dispatch-only, see the preceding commit), and everything on-glass: the SDR-10 stream end to end, the desktop-mode grab, keep-host-audio wiring, prep env on a live launch, and the cursor size at 200%.