From 983ff8e78d93e0c83fbfa33a0cded3aae858d517 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 28 Jul 2026 20:46:29 +0200 Subject: [PATCH] fix(session/console): a profile must not advertise a cursor channel the presenter isn't drawing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found reviewing the P0 wiring. The console builds its window and input models ONCE, from the global defaults, and they live across every launch — but each launch now re-resolves the host's profile. So a profile that sets `mouse_mode: desktop` on a host, while the global stays `capture`, made the session advertise `CLIENT_CAP_CURSOR` ("this client draws the host cursor itself") to a presenter latched in capture mode, which draws no cursor: the host stops compositing the pointer and the stream has no visible cursor at all — the exact failure the capability's own doc comment warns about. The advertisement now follows the LATCHED mouse model, which is the one that is actually true of the presenter. Everything the host is told about the stream itself — mode, bitrate, codec, audio, pad — keeps honoring the binding, and the comment says which fields are latched and why, so the console's presentation-tier gap is documented rather than rediscovered. Co-Authored-By: Claude Opus 5 (1M context) --- clients/session/src/console.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clients/session/src/console.rs b/clients/session/src/console.rs index 3fb04089..07e896a7 100644 --- a/clients/session/src/console.rs +++ b/clients/session/src/console.rs @@ -137,6 +137,13 @@ pub fn run(target: Option<&str>) -> u8 { // `{"ready":true}` and restores on exit) — plain CLI/gamescope runs stay silent. let json_status = arg_flag("--json-status"); let settings_at_start = trust::Settings::load(); + // The console's window and its input models are built ONCE, from the global defaults, and + // live across every launch — so the presentation-tier fields below (stats tier, touch and + // mouse model, match-window, render scale) are latched here and a per-host profile cannot + // move them in this mode. Everything the HOST is told (mode, bitrate, codec, audio, pad) is + // re-resolved per launch and does honor the binding. Closing that gap means rebuilding the + // presenter's models per launch — profiles P4 territory, not P0. + let latched_mouse = settings_at_start.mouse_mode(); // Request-access hand-off: the launch handler stamps this when it starts a delegated-approval // connect; `on_connected` reads it once the host lets us in and persists the host as PAIRED, @@ -221,6 +228,13 @@ pub fn run(target: Option<&str>) -> u8 { force_software, vulkan, ); + // …with ONE field that must follow the latched model rather than this + // launch's: the cursor-channel advertisement says "this client draws the + // host cursor itself", which is only true while the presenter is in desktop + // mouse mode. A profile that flips `mouse_mode` here would make the host + // stop compositing the pointer into a presenter that isn't drawing one — + // a stream with no visible cursor at all. + params.cursor_forward = latched_mouse == trust::MouseMode::Desktop; if request_access { // The host PARKS the connect until the operator approves — outlast its // approval window (host `PENDING_APPROVAL_WAIT`), matching the desktop