feat(clients): signal explicit exit (QUIT_CLOSE_CODE) on deliberate disconnect

The core's deliberate-quit close (NativeClient::disconnect_quit → QUIT_CLOSE_CODE,
host skips the keep-alive linger) was implemented but never called by any client.
Wire it to each client's explicit user-disconnect action — NOT to a network drop /
host-ended / app-background (those keep the linger for a reconnect):

- core: new C-ABI punktfunk_connection_disconnect_quit(c) for the ABI clients
- Linux (direct-core): Ctrl+Alt+Shift+D + the controller escape chord
- Windows (direct-core): Ctrl+Alt+Shift+D
- Apple (C-ABI): PunktfunkConnection.disconnectQuit() + a `deliberate` flag on
  SessionModel.disconnect() (sessionEnded passes false → keeps the linger)
- Android (JNI): new nativeDisconnectQuit export, called from the back gesture +
  the Select+Start+L1+R1 chord (not the host-gone watchdog)
- probe already did this via --quit (77871d6)

Verified: core + Linux client + Android (cargo-ndk + gradle) build clean;
Windows/Apple compile-checked by CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 13:37:45 +00:00
parent eaaf176adf
commit 91fadce900
9 changed files with 91 additions and 4 deletions
+16
View File
@@ -2432,6 +2432,22 @@ pub unsafe extern "C" fn punktfunk_connection_probe_result(
})
}
/// Signal a **deliberate quit** (a user "stop", not a network drop) before closing: the connection
/// closes with [`QUIT_CLOSE_CODE`] instead of code 0, so the host tears the session down immediately
/// (skips the keep-alive linger) rather than holding it for a reconnect. Call this right before
/// [`punktfunk_connection_close`] on a user-initiated disconnect; a plain close (network drop,
/// backgrounding) leaves the linger intact. NULL is a no-op.
///
/// # Safety
/// `c` was returned by [`punktfunk_connect`] and remains valid (closed via `punktfunk_connection_close`).
#[cfg(feature = "quic")]
#[no_mangle]
pub unsafe extern "C" fn punktfunk_connection_disconnect_quit(c: *mut PunktfunkConnection) {
if let Some(c) = unsafe { c.as_ref() } {
c.inner.disconnect_quit();
}
}
/// Close the connection and free the handle (joins the internal threads). NULL is a no-op.
///
/// # Safety