mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-07 14:48:11 +03:00
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.
66 lines
2.4 KiB
Dart
66 lines
2.4 KiB
Dart
// flutter/lib/common/widgets/keyboard_shortcuts/display.dart
|
|
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart';
|
|
import '../../../consts.dart';
|
|
import '../../../models/platform_model.dart';
|
|
|
|
/// Read the bindings JSON and produce a human-readable shortcut string for
|
|
/// `actionId`, formatted for the current OS. Returns null if unbound.
|
|
class ShortcutDisplay {
|
|
static String? formatFor(String actionId) {
|
|
final raw = bind.mainGetLocalOption(key: kShortcutLocalConfigKey);
|
|
if (raw.isEmpty) return null;
|
|
final Map<String, dynamic> parsed;
|
|
try {
|
|
parsed = jsonDecode(raw) as Map<String, dynamic>;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
if (parsed['enabled'] != true) return null;
|
|
final list = (parsed['bindings'] as List? ?? []).cast<Map<String, dynamic>>();
|
|
final found = list.firstWhere(
|
|
(b) => b['action'] == actionId,
|
|
orElse: () => {},
|
|
);
|
|
if (found.isEmpty) return null;
|
|
|
|
// Guard against a hand-edited / corrupt config where `key` is missing or
|
|
// not a string — silently treat the binding as unbound rather than
|
|
// crashing the toolbar render.
|
|
final keyValue = found['key'];
|
|
if (keyValue is! String) return null;
|
|
|
|
final isMac = defaultTargetPlatform == TargetPlatform.macOS ||
|
|
defaultTargetPlatform == TargetPlatform.iOS;
|
|
// `mods` similarly may be malformed; treat a non-list as no modifiers.
|
|
final modsRaw = found['mods'];
|
|
final mods = modsRaw is List
|
|
? modsRaw.whereType<String>().toList()
|
|
: const <String>[];
|
|
final parts = <String>[];
|
|
for (final m in ['primary', 'alt', 'shift']) {
|
|
if (!mods.contains(m)) continue;
|
|
switch (m) {
|
|
case 'primary': parts.add(isMac ? '⌘' : 'Ctrl'); break;
|
|
case 'alt': parts.add(isMac ? '⌥' : 'Alt'); break;
|
|
case 'shift': parts.add(isMac ? '⇧' : 'Shift'); break;
|
|
}
|
|
}
|
|
parts.add(_keyDisplay(keyValue, isMac));
|
|
return isMac ? parts.join('') : parts.join('+');
|
|
}
|
|
|
|
static String _keyDisplay(String key, bool isMac) {
|
|
switch (key) {
|
|
case 'delete': return isMac ? '⌫' : 'Del';
|
|
case 'enter': return isMac ? '⏎' : 'Enter';
|
|
case 'arrow_left': return '←';
|
|
case 'arrow_right':return '→';
|
|
case 'arrow_up': return '↑';
|
|
case 'arrow_down': return '↓';
|
|
}
|
|
if (key.startsWith('digit')) return key.substring(5);
|
|
return key.toUpperCase();
|
|
}
|
|
}
|