Commit Graph

118 Commits

Author SHA1 Message Date
fufesou
eb097012b3 feat(keyboard): shortcuts, release keys before shortcut callback
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-05-07 13:29:34 +08:00
rustdesk
cd7686baa2 feat(shortcuts): user-configurable keyboard shortcuts for session actions
Adds a keyboard shortcut feature (Rust matcher + Dart UI + cross-language
  parity tests) that lets users bind combinations like Ctrl+Alt+Shift+P to
  session actions. Bindings are stored in LocalConfig under
  `keyboard-shortcuts`; the matcher gates dispatch on `enabled` and
  `pass_through` flags so flipping the master switch off is a hard stop.

  Wire-up summary:
  - src/keyboard/shortcuts.rs: matcher, default bindings, parity test against
    flutter/test/fixtures/default_keyboard_shortcuts.json
  - src/keyboard.rs: shortcut intercept in process_event{,_with_session},
    feature-gated to `flutter`; runs before key swapping so users bind to
    physical keys
  - src/flutter_ffi.rs: main_reload_keyboard_shortcuts +
    main_get_default_keyboard_shortcuts; reload_from_config seeded in main_init
  - flutter/lib/common/widgets/keyboard_shortcuts/: shared config page body,
    recording dialog, shortcut display formatter, action group registry
  - flutter/lib/desktop/pages/desktop_keyboard_shortcuts_page.dart and
    flutter/lib/mobile/pages/mobile_keyboard_shortcuts_page.dart: platform
    shells around the shared body
  - flutter/lib/models/shortcut_model.dart: per-session ShortcutModel +
    registerSessionShortcutActions for actions with no toolbar TToggleMenu /
    TRadioMenu (fullscreen, switch display/tab, close tab, voice call, etc.)
  - flutter/lib/common/widgets/toolbar.dart: optional `actionId` field on
    TToggleMenu / TRadioMenu, plus per-helper auto-register pass that wires
    tagged entries' existing onChanged into the ShortcutModel
  - flutter/test/keyboard_shortcuts_test.dart + fixtures: cross-language
    parity (default bindings, supported key vocabulary)

  Design principles applied during review:

  1. Additions are fine; modifications to original logic must be deliberate.
     Tagging an existing TToggleMenu entry with `actionId:` is an addition.
     Rewriting its onChanged to satisfy a new contract is a modification —
     and was reverted for every case where the original click behavior was
     working. Four closures were touched and then reverted (mobile View
     Mode, Privacy mode multi-impl, Relative mouse mode, Reverse mouse
     wheel); their shortcuts are wired via standalone closures in
     shortcut_model.dart instead.

  2. Toolbar auto-register is reserved for entries whose onChanged is
     inherently self-flipping — typically `sessionToggleOption(name)` where
     the named option is flipped in place and the input bool is unused. The
     register pass passes `!menu.value` from registration time, which is
     harmless under self-flipping but wrong for closures that consume the
     input bool directly. Tagging a non-self-flipping entry forces a closure
     rewrite; choose non-toolbar registration in that case.

  3. When shortcuts are disabled, toolbar behavior must be bit-for-bit
     unchanged. The matcher's `enabled`-gate already guarantees no
     dispatch; the auto-register pass is left unconditional (its only effect
     is HashMap operations on a separate ShortcutModel) so mid-session
     enable works without a reconnect. The trade-off is intentional and
     documented at the top of toolbarControls.

  4. Comments stay terse. Rationale lives in one place — the doc comment of
     the helper or registration site, not duplicated at every call site.

  5. Where an existing helper needs a new optional behavior (e.g.
     `_OptionCheckBox` gaining a tooltip slot), the new branch must reduce
     to byte-identical output for existing callers (`trailing == null`
     case → original `Expanded(Text)` layout). Verified.

  6. Action IDs and labels stay consistent. Renamed `reset_cursor` →
     `reset_canvas` so the action ID matches its user-facing label
     ("Reset canvas") and capability flag.

  Out-of-scope but included:
  - AGENTS.md: documents flutter_rust_bridge no-codegen workflow and the
    Web target's hand-written TS client, since both are load-bearing for
    any new FFI work.
  - remote_toolbar.dart: i18n fix for the per-monitor tooltip ("All
    monitors" / "Monitor #N"), unrelated to shortcuts but kept here.
2026-04-30 16:40:42 +08:00
Sergiusz Michalik
7308c448f1 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>
2026-04-26 22:46:41 +08:00
fufesou
998b75856d feat: Add relative mouse mode (#13928)
* feat: Add relative mouse mode

- Add "Relative Mouse Mode" toggle in desktop toolbar and bind to InputModel
- Implement relative mouse movement path: Flutter pointer deltas -> `type: move_relative` -> new `MOUSE_TYPE_MOVE_RELATIVE` in Rust
- In server input service, simulate relative movement via Enigo and keep latest cursor position in sync
- Track pointer-lock center in Flutter (local widget + screen coordinates) and re-center OS cursor after each relative move
- Update pointer-lock center on window move/resize/restore/maximize and when remote display geometry changes
- Hide local cursor when relative mouse mode is active (both Flutter cursor and OS cursor), restore on leave/disable
- On Windows, clip OS cursor to the window rect while in relative mode and release clip when leaving/turning off
- Implement platform helpers: `get_cursor_pos`, `set_cursor_pos`, `show_cursor`, `clip_cursor` (no-op clip/hide on Linux for now)
- Add keyboard shortcut Ctrl+Alt+Shift+M to toggle relative mode (enabled by default, works on all platforms)
- Remove `enable-relative-mouse-shortcut` config option - shortcut is now always available when keyboard permission is granted
- Handle window blur/focus/minimize events to properly release/restore cursor constraints
- Add MOUSE_TYPE_MASK constant and unit tests for mouse event constants

Note: Relative mouse mode state is NOT persisted to config (session-only).
Note: On Linux, show_cursor and clip_cursor are no-ops; cursor hiding is handled by Flutter side.

Signed-off-by: fufesou <linlong1266@gmail.com>

* feat(mouse): relative mouse mode, exit hint

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact(relative mouse): shortcut

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-01-09 10:03:14 +08:00
luzpaz
e14e850e10 fix: typos in src/ and subdirectories (#11727)
Found via codespell
2025-09-17 13:37:44 +08:00
fufesou
a22f2108c6 refact: suppress warns on macos (#12449)
Signed-off-by: fufesou <linlong1266@gmail.com>
2025-08-18 15:09:11 +08:00
fufesou
12e15b5a37 fix: linux, weak network, repeated keys (#10211)
Use `press` as the `click` flag on Linux to avoid repeated keys, like
the Legacy mode.

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-12-06 20:01:11 +08:00
fufesou
cc6f919080 feat: mobile map mode (#9717)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-10-23 09:28:39 +08:00
fufesou
bf390611ab fix: keyboard, sciter (#9216)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-08-31 19:02:50 +08:00
dignow
d3454f07d3 fix: move some crates to rustdesk-org (#8772)
Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: fufesou <linlong1266@gmail.com>
2024-07-20 23:49:40 +08:00
fufesou
cf8ef2533a fix: keyboard, linux, repeated keys, #6793 (#8757)
* fix: keyboard, linux, repeat keys, #6793

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix: keyboard, linux->linux, may also repeat keys with bad network

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-20 00:42:49 +08:00
fufesou
dfc224ec01 fix: #8599 (#8603)
Signed-off-by: fufesou <linlong1266@gmail.com>
2024-07-04 20:20:25 +08:00
fufesou
c441d2f03f fix build
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-11-29 20:58:40 +08:00
fufesou
6fc4253d46 debug, linux
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-11-29 20:58:40 +08:00
fufesou
7a2590d1f9 fix, compilation
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-11-29 20:58:40 +08:00
fufesou
4cbbb5b64f feat, input source, win->win
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-11-29 20:58:40 +08:00
fufesou
f11104fcb5 tmp commit
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-11-29 20:58:39 +08:00
21pages
f9ee0189f0 format log, add space after colon
Signed-off-by: 21pages <pages21@163.com>
2023-11-18 09:52:46 +08:00
fufesou
90ac8b7b0b feat/virtual_display_privacy_mode
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-11-14 12:11:38 +08:00
mcfans
26e77ba2c3 feat: modifier key 2023-10-19 14:19:19 +08:00
mcfans
28c11801f3 feat: keyboard map mode control side 2023-10-19 13:40:12 +08:00
mcfans
b3e2ab0f3b feat: map mode and translate mode receive side 2023-10-19 13:33:26 +08:00
mcfans
22165ec1a5 feat: legacy mode android keyboard support 2023-10-19 00:16:22 +08:00
dignow
c10fc26cce fix build, android
Signed-off-by: dignow <linlong1265@gmail.com>
2023-10-14 12:26:27 +08:00
fufesou
06987c4ca9 refact, flutter sessions lock
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-10-03 09:51:21 +08:00
fufesou
9771c652c5 fix, alt + tab, switch window, release alt state
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-09-25 00:15:36 +08:00
fufesou
ae3efa1151 fix, RwLock, remove nested read calls
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-09-17 13:01:27 +08:00
fufesou
7b37e5183c update rdev, fix grab system utf8, fallback on linux
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-09-15 15:35:44 +08:00
dignow
7dcb28ce33 fix, separate window, event stream leak
Signed-off-by: dignow <linlong1265@gmail.com>
2023-08-07 09:01:31 +08:00
fufesou
e37745d0b4 fix build
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-07-18 09:36:06 +08:00
fufesou
a76fd86f19 remove warns
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-20 13:36:44 +08:00
fufesou
1d5f65b005 fix, translate mode, numpad keys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-10 17:51:07 +08:00
fufesou
0d5d073a43 trivial changes
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-08 20:28:34 +08:00
fufesou
f72593c281 tmp commit
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-08 19:49:11 +08:00
fufesou
d6c8fb2b28 refact is_peer_version_ge, and fix version comparation > to >=
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-07 14:47:01 +08:00
fufesou
83249f0f95 debug done
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-01 21:35:59 +08:00
fufesou
ed4016a77a debug done
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-01 20:56:03 +08:00
fufesou
021939a6a6 tmp commit, for debug
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-01 19:30:22 +08:00
fufesou
ae53ec877b translate mode, win2win, Send both unicode and virtual keycode to remote side
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-01 18:09:53 +08:00
fufesou
820b01ab41 add translate mode ref link
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-04-01 11:42:16 +08:00
RustDesk
582e025145 Merge pull request #3854 from chiehw/release
Release modifier when multi conn
2023-04-01 10:00:49 +08:00
asur4s
0f72af6529 Release modifier when multi conn 2023-03-31 13:01:20 +08:00
fufesou
8cbb367b79 fix build
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-03-31 12:13:50 +08:00
fufesou
e6e36d38a3 translate mode, win, fix hotkeys
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-03-31 11:48:37 +08:00
fufesou
8cb361c51e remove android build warns
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-03-27 21:11:07 +08:00
fufesou
3fdffa1371 format
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-03-27 12:08:01 +08:00
fufesou
17fe62aec1 Do not send lock modifiers if no related keys are pressed
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-03-27 12:06:38 +08:00
fufesou
e242df76b8 simplify keyboard lock modifiers
Signed-off-by: fufesou <shuanglongchen@yeah.net>
2023-03-27 11:59:07 +08:00
asur4s
77125b7a98 fix conflict 2023-03-26 22:24:47 +08:00
chiehw
82a7554be1 Send key instead of char in numpad 2023-03-26 06:06:32 -07:00