# 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` / `Future` 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.