mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-08 15:18:13 +03:00
fix(client): serialize X11 keyboard grab and debounce focus feedback (#14836)
* fix(client): serialize X11 keyboard grab and debounce focus feedback When two RustDesk sessions run fullscreen on separate monitors on Linux/X11, keyboard input gets stuck on the wrong session or stops working entirely. This happens because each Flutter isolate calls change_grab_status concurrently, racing on KEYBOARD_HOOKED and the rdev grab channel. Additionally, XGrabKeyboard causes a focus-change feedback loop: grab shifts focus away from the Flutter window, triggering PointerExit, which releases the grab, restoring focus, triggering PointerEnter, which re-grabs -- cycling at ~10 Hz and blocking keyboard input. Fix by: - Serializing grab transitions with a mutex and tracking the owning session (by lc.session_id), so a stale Wait from session A cannot clobber session B's freshly acquired grab. - Debouncing Wait events (300 ms) from the same session that just acquired the grab, breaking the X11 focus feedback loop. - Refreshing the debounce timer on idempotent Run calls (enterView while already owner), keeping the grab stable during normal use. Signed-off-by: Sergiusz Michalik <github@latens.me> * fix(client): add deferred release and dedup for debounced Wait When a Wait is debounced (within 300ms of grab acquisition), schedule a deferred release thread that re-checks after the debounce window. If no new Run refreshed the grab, the deferred thread releases it, ensuring a genuine leave within the debounce window is not lost. Add a deferred_pending flag to GrabOwnerState to prevent spawning redundant threads during the X11 focus feedback loop. Signed-off-by: Sergiusz Michalik <github@latens.me> * fix(client): use window-scoped ID and fix deferred-release re-arming Address PR review feedback: - Use per-window UUID instead of connection-scoped lc.session_id so two windows viewing the same peer get distinct grab owners - Reset deferred_pending on both idempotent Run refresh and owner handoff, so a subsequent Wait can always spawn a fresh timer - Replace manual Default impl with derive * fix(client): recover from poisoned mutex instead of panicking * docs: clarify cross-platform rationale for GrabOwnerState * fix(client): only clear deferred_pending when timer snapshot matches * fix(client): use full u128 window ID, downgrade grab logs to debug - Widen GrabOwnerState.owner to u128 to avoid theoretical collision from truncating a 128-bit UUID to 64 bits - Downgrade all grab transition log::info! to log::debug! to reduce log noise during routine window switches - Clear deferred_pending on post-debounce release path to maintain the "deferred_pending => timer in flight" invariant * fix(client): gate GRAB_DEBOUNCE_MS with cfg(target_os = "linux") * fix(grab): release grabbed keys without clobbering new owner state Signed-off-by: fufesou <linlong1266@gmail.com> * fix(keyboard): Simple refactor Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: Sergiusz Michalik <github@latens.me> Signed-off-by: fufesou <linlong1266@gmail.com> Co-authored-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
committed by
GitHub
parent
c8ba99d1a1
commit
7308c448f1
@@ -605,21 +605,30 @@ pub fn session_handle_flutter_raw_key_event(
|
||||
}
|
||||
}
|
||||
|
||||
// SyncReturn<()> is used to make sure enter() and leave() are executed in the sequence this function is called.
|
||||
//
|
||||
// If the cursor jumps between remote page of two connections, leave view and enter view will be called.
|
||||
// session_enter_or_leave() will be called then.
|
||||
// As rust is multi-thread, it is possible that enter() is called before leave().
|
||||
// This will cause the keyboard input to take no effect.
|
||||
// As Rust is multi-threaded, enter() can be called before leave().
|
||||
// The Rust-side grab ownership state filters stale transitions.
|
||||
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
|
||||
let keyboard_mode = session.get_keyboard_mode();
|
||||
// Use the full per-window UUID (not lc.session_id which is per-connection)
|
||||
// so that two windows viewing the same peer get distinct grab owners.
|
||||
let window_id = _session_id.as_u128();
|
||||
if _enter {
|
||||
set_cur_session_id_(_session_id, &keyboard_mode);
|
||||
session.enter(keyboard_mode);
|
||||
crate::keyboard::client::change_grab_status(
|
||||
crate::common::GrabState::Run,
|
||||
&keyboard_mode,
|
||||
window_id,
|
||||
);
|
||||
} else {
|
||||
session.leave(keyboard_mode);
|
||||
crate::keyboard::client::change_grab_status(
|
||||
crate::common::GrabState::Wait,
|
||||
&keyboard_mode,
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
SyncReturn(())
|
||||
|
||||
Reference in New Issue
Block a user