mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-07 22:58:10 +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.
96 lines
3.4 KiB
Dart
96 lines
3.4 KiB
Dart
// flutter/lib/mobile/pages/mobile_keyboard_shortcuts_page.dart
|
|
//
|
|
// Mobile shell for the Keyboard Shortcuts configuration page. Mirrors
|
|
// `desktop/pages/desktop_keyboard_shortcuts_page.dart` but with a touch-
|
|
// friendly layout (ListTile rows instead of dense rows) and a hint banner
|
|
// that explains the recording flow only works with a physical keyboard.
|
|
//
|
|
// All actual logic — group definitions, JSON I/O, conflict-replace flow,
|
|
// recording-dialog round-trip, "Reset to defaults" — lives in the shared
|
|
// `common/widgets/keyboard_shortcuts/page_body.dart`. This file only
|
|
// supplies the AppBar, the AppBar action, and the platform hint banner.
|
|
//
|
|
// Mobile keyboard detection limitation: Flutter has no reliable
|
|
// "is a physical keyboard attached?" API on iOS or Android. Soft keyboards
|
|
// don't generate the `KeyDownEvent`s the recording dialog listens for, so
|
|
// in practice the dialog only does anything useful when the user actually
|
|
// has a hardware keyboard plugged in (USB / Bluetooth / Smart Connector).
|
|
// For V1 we don't try to detect attachment — we just surface the
|
|
// requirement as an in-page hint instead of disabling the Edit button.
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../common.dart';
|
|
import '../../common/widgets/keyboard_shortcuts/page_body.dart';
|
|
|
|
class MobileKeyboardShortcutsPage extends StatefulWidget {
|
|
const MobileKeyboardShortcutsPage({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
State<MobileKeyboardShortcutsPage> createState() =>
|
|
_MobileKeyboardShortcutsPageState();
|
|
}
|
|
|
|
class _MobileKeyboardShortcutsPageState
|
|
extends State<MobileKeyboardShortcutsPage> {
|
|
final GlobalKey<KeyboardShortcutsPageBodyState> _bodyKey = GlobalKey();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(translate('Keyboard Shortcuts')),
|
|
actions: [
|
|
IconButton(
|
|
tooltip: translate('Reset to defaults'),
|
|
onPressed: () =>
|
|
_bodyKey.currentState?.resetToDefaultsWithConfirm(),
|
|
icon: const Icon(Icons.restore),
|
|
),
|
|
],
|
|
),
|
|
body: KeyboardShortcutsPageBody(
|
|
key: _bodyKey,
|
|
compact: false,
|
|
editButtonHint: translate('shortcut-mobile-physical-keyboard-tip'),
|
|
headerBanner: _PhysicalKeyboardHintBanner(theme: theme),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A muted info banner shown above the master toggle on mobile. We can't
|
|
/// reliably detect whether a physical keyboard is attached, so instead of
|
|
/// disabling the Edit button we surface the requirement up front.
|
|
class _PhysicalKeyboardHintBanner extends StatelessWidget {
|
|
final ThemeData theme;
|
|
const _PhysicalKeyboardHintBanner({required this.theme});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = theme.colorScheme.primary.withOpacity(0.08);
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: color,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(Icons.info_outline,
|
|
size: 18, color: theme.colorScheme.primary),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
translate('shortcut-mobile-physical-keyboard-tip'),
|
|
style: TextStyle(color: theme.colorScheme.onSurface),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|