mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-05-07 06:38: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.
87 lines
4.8 KiB
Markdown
87 lines
4.8 KiB
Markdown
# RustDesk Guide
|
|
|
|
## Project Layout
|
|
|
|
### Directory Structure
|
|
* `src/` Rust app
|
|
* `src/server/` audio / clipboard / input / video / network
|
|
* `src/platform/` platform-specific code
|
|
* `src/ui/` legacy Sciter UI (deprecated)
|
|
* `flutter/` current UI
|
|
* `libs/hbb_common/` config / proto / shared utils
|
|
* `libs/scrap/` screen capture
|
|
* `libs/enigo/` input control
|
|
* `libs/clipboard/` clipboard
|
|
* `libs/hbb_common/src/config.rs` all options
|
|
|
|
### Key Components
|
|
- **Remote Desktop Protocol**: Custom protocol implemented in `src/rendezvous_mediator.rs` for communicating with rustdesk-server
|
|
- **Screen Capture**: Platform-specific screen capture in `libs/scrap/`
|
|
- **Input Handling**: Cross-platform input simulation in `libs/enigo/`
|
|
- **Audio/Video Services**: Real-time audio/video streaming in `src/server/`
|
|
- **File Transfer**: Secure file transfer implementation in `libs/hbb_common/`
|
|
|
|
### UI Architecture
|
|
- **Legacy UI**: Sciter-based (deprecated) - files in `src/ui/`
|
|
- **Modern UI**: Flutter-based - files in `flutter/`
|
|
- Desktop: `flutter/lib/desktop/`
|
|
- Mobile: `flutter/lib/mobile/`
|
|
- Shared: `flutter/lib/common/` and `flutter/lib/models/`
|
|
|
|
## Rust Rules
|
|
|
|
* Avoid `unwrap()` / `expect()` in production code.
|
|
* Exceptions:
|
|
|
|
* tests;
|
|
* lock acquisition where failure means poisoning, not normal control flow.
|
|
* Otherwise prefer `Result` + `?` or explicit handling.
|
|
* Do not ignore errors silently.
|
|
* Avoid unnecessary `.clone()`.
|
|
* Prefer borrowing when practical.
|
|
* Do not add dependencies unless needed.
|
|
* Keep code simple and idiomatic.
|
|
|
|
## Tokio Rules
|
|
|
|
* Assume a Tokio runtime already exists.
|
|
* Never create nested runtimes.
|
|
* Never call `Runtime::block_on()` inside Tokio / async code.
|
|
* Do not hide runtime creation inside helpers or libraries.
|
|
* Do not hold locks across `.await`.
|
|
* Prefer `.await`, `tokio::spawn`, channels.
|
|
* Use `spawn_blocking` or dedicated threads for blocking work.
|
|
* Do not use `std::thread::sleep()` in async code.
|
|
|
|
## Flutter Rust Bridge
|
|
|
|
* Do **not** run `flutter_rust_bridge_codegen` — it requires a specific pinned version that is not easy to set up locally.
|
|
* When adding new FFI functions in `src/flutter_ffi.rs`, hand-write the corresponding Dart wrappers instead of regenerating.
|
|
* Web bridge (committed): edit `flutter/lib/web/bridge.dart` directly. Follow the existing patterns there for `SyncReturn<T>` / `Future<T>` and the `dart:js` glue.
|
|
* Native bridge (`flutter/lib/generated_bridge.dart`, `src/bridge_generated.rs`, `src/bridge_generated.io.rs`): these are gitignored and regenerated by the project's CI codegen. Manually editing them locally is fine for development testing, but those edits do not persist into commits.
|
|
|
|
## Web (Flutter Web) Architecture
|
|
|
|
Flutter Web in this repo is **not** "Dart compiled to JS via Flutter alone". The runtime is split:
|
|
|
|
* **Native targets (Win/Mac/Linux/Android/iOS)**: Rust drives sessions via `flutter_rust_bridge`; Dart only renders UI.
|
|
* **Web target**: Rust does **not** run. There is a separate hand-written TypeScript / JavaScript client at `flutter/web/js/` (gitignored — not present in this repo, lives in the maintainer's local tree). It owns connection, codec, keyboard, clipboard, etc. — basically a JS port of the Rust client. The Dart UI talks to it through `flutter/lib/web/bridge.dart`, which uses `dart:js` to call JS-side functions and to register Dart-side callbacks on `window.*`.
|
|
|
|
Implications when adding any session-runtime feature (keyboard, clipboard, audio, …):
|
|
|
|
* The Rust implementation in `src/` is for **native only**. Don't try to compile it to wasm.
|
|
* The matching Web-side logic must be written in TS/JS under `flutter/web/js/src/`. It's a translation of the Rust logic, usually simpler — Web is single-window, so any per-session-id plumbing in Rust collapses to a single global on Web.
|
|
* `flutter/lib/web/bridge.dart` is the only place where Dart sees JS. Other Dart code stays platform-agnostic and goes through `bind`. Don't sprinkle `if (isWeb)` runtime branches in shared Dart files to call Web-specific logic — put the platform divergence in the bridge.
|
|
* For JS → Dart events (e.g., a Web matcher firing), the convention is: Dart sets `js.context['onFooBar'] = (...) {...}` once at startup (typically in `mainInit`); the JS side calls `window.onFooBar(...)`. See `onLoadAbFinished`, `onLoadGroupFinished` for reference.
|
|
* The maintainer cannot easily run `flutter_rust_bridge_codegen`, so when a new FFI function lands in `src/flutter_ffi.rs`:
|
|
1. add the Web counterpart to `flutter/lib/web/bridge.dart` by hand;
|
|
2. note that on the Web target it may need to be a no-op or a JS bridge call rather than a real Rust invocation.
|
|
|
|
## Editing Hygiene
|
|
|
|
* Change only what is required.
|
|
* Prefer the smallest valid diff.
|
|
* Do not refactor unrelated code.
|
|
* Do not make formatting-only changes.
|
|
* Keep naming/style consistent with nearby code.
|