cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix(portal): control v2 disconnected the session on its first input ack

Dami committed 4f7f481
Clicking to control always dropped the whole session and forced a reconnect.

`set_input_ack_negotiated` is called only inside the
`if control_protocol == ControlProtocol::V1` branch in connect.rs. The control
v2 path negotiates InputAck with the host but never records that in the session
diagnostics, so `observe_ack` still saw `negotiated == false` and rejected the
host's first acknowledgement with "Host sent an input acknowledgement that was
not negotiated". That marks the pointer feedback terminal/malformed, and the
frontend treats a terminal feedback envelope as fatal and disconnects.

The host emits that first ack the moment the input v2 stream opens, to prime
pointer feedback, so every control attempt hit it. Control v2 could not have
worked.

Both rejection paths in `run_input_feedback_v2` also broke silently. A fatal
disconnect left nothing in the log to explain itself, which is why this looked
like a transport fault: the host reported only "connection lost" once Portal
had already torn the endpoint down. They now name the mismatch.

Verified against a live Bazzite host running the published 0.1.0: input v2 is
accepted and the session survives instead of dying ~8ms later, and the
rejection no longer appears.
commit
4f7f481f717d125bb9fca4835f5c3649c1f930b2
parent
c9ba9802ae8027571a73f35c49b6763a6373f289
author
Dami <dami.etoile@gmail.com>
1 file changed, +17 -4
Msrc-tauri/src/media/input_delivery.rs21+++++++++++++++++----
M src-tauri/src/media/input_delivery.rs +17 -4
@@ -547,6 +547,12 @@ pub(crate) async fn start_focused_input_v2(state: &AppState) -> Result<InputAvai
547547 context.grants,548548 )549549 .await?;550+ // The v1 path records this at negotiation time. Without the same call here551+ // the ack diagnostics stay "not negotiated" while the input stream really552+ // did negotiate InputAck, so the host's first acknowledgement is rejected553+ // as unsolicited and control v2 tears the session down.554+ lock_network_diagnostics(&context.network_diagnostics)555+ .set_input_ack_negotiated(availability.input_ack);550556 let (tx, rx) = tokio::sync::mpsc::channel::<InputEvent>(CLIENT_INPUT_QUEUE_CAPACITY);551557 *state.input_send.lock().await = Some(tx);552558 *state.input_connection.lock().await = Some((context.native_generation, connection));
@@ -762,16 +768,23 @@ pub(crate) async fn run_input_feedback_v2(
762768 || response.slot != ControllerSlot::ZERO763769 || response.focus_generation != focus_generation764770 {771+ eprintln!(772+ "[client] input v2 feedback binding mismatch: got session={} slot={:?} focus={}, expected session={} slot=0 focus={}",773+ response.session_id,774+ response.slot,775+ response.focus_generation,776+ session_id,777+ focus_generation778+ );765779 break PointerFeedbackTerminalReason::Malformed;766780 }767- if observe_input_ack_if_negotiated(781+ if let Err(error) = observe_input_ack_if_negotiated(768782 &feedback_diagnostics,769783 input_ack_enabled,770784 response.ack.sequence,771785 Instant::now(),772- )773- .is_err()774- {786+ ) {787+ eprintln!("[client] input v2 acknowledgement rejected: {error}");775788 break PointerFeedbackTerminalReason::Malformed;776789 }777790 if pointer_feedback_enabled