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>
| M | src-tauri/ | 21 | +++++++++++++++++---- |
@@ -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