mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-08 07:08:09 +03:00
feat: keyboard shortcuts in remote sessions
Add an opt-in keyboard-shortcut system that triggers session
actions (Send Ctrl+Alt+Del, Toggle Fullscreen, Switch Display,
Screenshot, Switch Tab, etc.) via three-modifier combinations
during a remote session.
Architecture
- Native: src/keyboard/shortcuts.rs intercepts at the encoder
layer (process_event and process_event_with_session), so the
feature is input-source-independent. Bindings persist as a
single JSON blob in LocalConfig.
- Web: matching + keydown intercept live in the separate hand-
written TS client at flutter/web/js/ (gitignored, not in this
repo). flutter/lib/web/bridge.dart::mainInit registers
window.onShortcutTriggered so the JS matcher can dispatch
back into the active session's ShortcutModel; the bridge's
mainReloadKeyboardShortcuts forwards to a JS reloadShortcuts
on settings writes.
- Three-modifier prefix (Ctrl+Alt+Shift; Cmd+Option+Shift on
macOS/iOS) sidesteps the need for a pass-through toggle.
- Flutter native path threads the explicit per-call SessionID
for tab-precise routing; rdev path uses globally-current
session.
UI
- Settings -> General -> Keyboard Shortcuts opens a dedicated
configuration page; desktop and mobile share a body widget.
- Recording dialog with live capture, prefix validation, and a
conflict-replace flow.
- Toolbar menu items display the bound shortcut inline.
- Default bindings (adapted from AnyDesk):
+Del Send Ctrl+Alt+Del
+Enter Toggle Fullscreen
+Left/Right Switch Display Prev/Next
+P Screenshot
+1..9 Switch Session Tab
Other
- AGENTS.md: documented (a) flutter_rust_bridge_codegen needs
a pinned version + Dart bridge wrappers should be hand-
written, and (b) the Web-target split where flutter/web/js/
is the runtime owner on Web rather than wasm-compiled Rust.
- 38 new i18n strings in src/lang/en.rs with Chinese
translations in src/lang/cn.rs.
Refs discussion #1933.
This commit is contained in:
@@ -7,6 +7,7 @@ import 'package:uuid/uuid.dart';
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:flutter_hbb/consts.dart';
|
||||
import 'package:flutter_hbb/common.dart' as common;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
@@ -930,6 +931,30 @@ class RustdeskImpl {
|
||||
]));
|
||||
}
|
||||
|
||||
// Tell the JS-side matcher (flutter/web/js/src/shortcut_matcher.ts) to
|
||||
// re-read its bindings from LocalStorage. Mirrors the native call which
|
||||
// refreshes the Rust matcher's in-memory cache.
|
||||
void mainReloadKeyboardShortcuts({dynamic hint}) {
|
||||
js.context.callMethod('reloadShortcuts', []);
|
||||
}
|
||||
|
||||
// Mirror of `default_bindings()` in `src/keyboard/shortcuts.rs`. Keep these
|
||||
// two lists in sync — if you add or change a default binding on the Rust
|
||||
// side, update the literal below to match.
|
||||
String mainGetDefaultKeyboardShortcuts({dynamic hint}) {
|
||||
const prefix = ['primary', 'alt', 'shift'];
|
||||
final list = <Map<String, dynamic>>[
|
||||
{'action': 'send_ctrl_alt_del', 'mods': prefix, 'key': 'delete'},
|
||||
{'action': 'toggle_fullscreen', 'mods': prefix, 'key': 'enter'},
|
||||
{'action': 'switch_display_next', 'mods': prefix, 'key': 'arrow_right'},
|
||||
{'action': 'switch_display_prev', 'mods': prefix, 'key': 'arrow_left'},
|
||||
{'action': 'screenshot', 'mods': prefix, 'key': 'p'},
|
||||
for (var n = 1; n <= 9; n++)
|
||||
{'action': 'switch_tab_$n', 'mods': prefix, 'key': 'digit$n'},
|
||||
];
|
||||
return jsonEncode(list);
|
||||
}
|
||||
|
||||
String mainGetInputSource({dynamic hint}) {
|
||||
final inputSource =
|
||||
js.context.callMethod('getByName', ['option:local', 'input-source']);
|
||||
@@ -1176,6 +1201,15 @@ class RustdeskImpl {
|
||||
}
|
||||
|
||||
Future<void> mainInit({required String appDir, dynamic hint}) {
|
||||
// JS -> Dart shortcut bridge. The matcher in flutter/web/js/src/
|
||||
// shortcut_matcher.ts calls `window.onShortcutTriggered(actionId)` when a
|
||||
// binding fires; route it to the active session's ShortcutModel.
|
||||
// Web is single-window so `gFFI` is always the active session.
|
||||
js.context['onShortcutTriggered'] = (dynamic action) {
|
||||
if (action is String) {
|
||||
common.gFFI.shortcutModel.onTriggered(action);
|
||||
}
|
||||
};
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user