rustdesk/rustdesk
github.com/rustdesk/rustdesk ↗ · 856 files · 12.2 MB
Build • Docker • Structure • Screenshots [ Українська ] | [ česky ] | [ 中文 ] | [ Magyar ] | [ Español ] | [ فارسی ] | [ Français ] | [ Deutsch ] | [ Polski ] | [ Indonesian ] | [ Suomi ] | [ മലയാളം ] | [ 日本語 ] | [ Nederlands ] | [ Italiano ] | [ Русский ] | [
Overview
RustDesk is an open-source remote desktop client (v1.5.0) written in Rust with a Flutter UI, covering Windows, macOS, Linux, Android, and iOS. A single binary multiplexes roles via CLI flags: `--server` runs the controlled-side host (screen capture, input injection, clipboard, audio, file transfer), `--cm` runs the connection-manager approval UI, and bare launch runs the Flutter main window; outgoing connections punch via UDP/TCP/WebRTC through a rendezvous server (hbbs) with relay (hbbr) fallback. The wire protocol is protobuf over `hbb_common`'s `Stream`, secured by NaCl sign/box_ key exchange, and the shared protocol crate `libs/hbb_common` is a git submodule also used by the server project.
System map
One Cargo package (`rustdesk`, lib name `librustdesk`, crate-type cdylib+staticlib+rlib) produces the main `rustdesk` binary plus helper bins `service` (macOS) and `naming`, while `libs/portable` builds a self-extracting portable packer. `src/` holds the cross-platform core; `src/platform/` and `src/server/` hold OS- and host-side code; `flutter/` is the current UI (legacy Sciter UI in `src/ui/` is deprecated); `libs/` is a Cargo workspace of vendored crates. `libs/hbb_common` is a git submodule shared with rustdesk-server — it is empty in this clone. CI is GitHub Actions driven by `flutter-build.yml` (reusable) and `flutter-ci.yml` (PR gate).
- The crate builds as `librustdesk` with crate-type [cdylib, staticlib, rlib] so the same code backs the desktop binary, the Flutter dynamic library, and mobile FFI; `default-run = "rustdesk"` and extra bins `naming` (src/naming.rs) and `service` (src/service.rs) are declared.
- The default `main()` in src/main.rs is a thin shell: it calls `core_main::core_main()` and, if it returns Some(args), starts the UI via `ui::start(args)`; under feature `flutter` or mobile targets, `main` only runs `common::global_init()` + rendezvous/NAT diagnostics because the real app entry is the Flutter engine loading the library.
- core_main() is the CLI dispatcher: flags like `--server`, `--tray`, `--cm`, `--service`, `--install`, `--password`, `--get-id`, `--option`, `--assign`, `--deploy`, `--connect`/`--file-transfer`/`--terminal`/`--rdp` are parsed positionally from std::env::args and each maps to a subsystem call.
- Workspace members are libs/scrap (screen capture), libs/hbb_common (protocol/Config submodule), libs/base (client-only protos+fs+keys), libs/enigo (input injection), libs/clipboard, libs/virtual_display(+dylib), libs/portable, libs/remote_printer; `vdi/host` is excluded.
- crates-io is patched: `libxdo-sys` is replaced by libs/libxdo-sys-stub (so builds work without libxdo on Wayland-only systems), and `tungstenite`, `webrtc`, `webrtc-util`, `webrtc-sctp` are pinned by git rev to rustdesk-org forks carrying custom fixes (incremental ws read buffer, Windows IPv6 ICE, SCTP congestion-control off by default).
- Feature flags gate major subsystems: `flutter` (flutter_rust_bridge =1.80), `hwcodec`/`vram`/`mediacodec` (hardware codecs via scrap), `drm` and separately `drm-wake` (Linux DRM/KMS capture plus an opt-in synthetic pointer wake), `unix-file-copy-paste`, `inline` (legacy Sciter in-process UI), and resampler backends `use_dasp`(default)/`use_rubato`/`use_samplerate`.
- Server-side runtime is `Server` in src/server.rs: a `ServerPtr = Arc<RwLock<Server>>` holding `connections: HashMap<i32, ConnInner>` and `services: HashMap<String, Box<dyn Service>>`; `new()` registers audio, display, clipboard, input (cursor/pos/window-focus), and (Windows+flutter) printer services.
- IPC between the privileged `--server` process, the user UI process, and the `--cm` connection manager uses parity_tokio_ipc (Unix sockets / Windows named pipes) framed by BytesCodec and serde-tagged JSON `enum Data` (Login, Authorize, Close, SyncConfig, FS, ClipboardFile, SwitchSides*, UrlLink, DataPortableService, ...).
- The Flutter UI lives in flutter/ (package flutter_hbb 1.5.0+68): lib/main.dart dispatches on boot args — `multi_window <id> <json>` for remote/file-transfer/camera/port-forward/terminal windows, `--cm` for the connection manager, `--install` for the install page, default for the main window; state uses provider/getX and FFI goes through flutter_rust_bridge 1.80.
- Rust↔Flutter bridge: src/flutter_ffi.rs exposes hundreds of `pub fn` (session_add_sync, session_start, peer_get_sessions_count, start_global_event_stream...) generated into `src/bridge_generated.rs` (mod declared in lib.rs), while Rust pushes events back over `StreamSink` global event streams keyed by app type (`main`, `cm`).
- Build tooling: `build.py` drives cargo build + flutter build + packaging per-OS; `build.rs` (cc, hbb_common, os-version) compiles platform C/C++; `vcpkg.json` + vcpkg install libvpx/libyuv/opus/aom for native codec deps; the Dockerfile builds a Debian trixie image with vcpkg, cmake 3.30.6, gtk/xcb/pulse dev packages for Linux builds.
- Release profile sets lto, codegen-units=1, panic='abort', strip — and panic=abort means any panic in the Rust core kills the whole process including the Flutter UI.
Feature catalog
User-facing features map to ConnType variants (default remote desktop, file transfer, view camera, port forward/RDP, terminal) chosen per session, plus host-side services (video, audio, clipboard, input, printer, terminal). Administrative surface is a large CLI (install/service/config/password/deploy) plus TCP/IPC control channels. Monetized/enterprise features hang off the optional api-server (hbbs_http: account, strategy sync, address book, audit upload) and the `--deploy`/`--assign` device-registration commands.
- Outgoing session types are selected in `session_add` by booleans → ConnType: FILE_TRANSFER, VIEW_CAMERA, TERMINAL, PORT_FORWARD, RDP, or DEFAULT_CONN; Flutter passes them from `flutter/lib/main.dart`'s WindowType dispatch (RemoteDesktop, FileTransfer, ViewCamera, PortForward, Terminal).
- Remote desktop video: `src/server/video_service.rs` captures via `scrap::Capturer` per display (VideoSource::Monitor) or camera, encodes VP8/VP9/AV1 (`VpxEncoderConfig`, `AomEncoderConfig`) or hardware encoders behind `hwcodec`/`vram` features, with `VideoQoS` adaptation and DXGI recovery logic on Windows.
- File transfer runs through `base::fs` TransferJob machinery and `ipc::FS` messages (ReadDir/RemoveDir/NewWrite/WriteBlock/CheckDigest/Rename...); `Remote` in io_loop owns `read_jobs`/`write_jobs`/`remove_jobs` lists and `FileManager` is exposed via `file_trait`.
- Clipboard sync uses arboard + clipboard-master with a custom `dyn.com.rustdesk.owner` marker format, `CLIPBOARD_INTERVAL` 333ms polling, multi-clipboard support, and optional `unix-file-copy-paste` feature for file paste; a second `file-clipboard` service is registered when the feature is on.
- Remote input: `libs/enigo` Enigo (keyboard/mouse injection) on the controlled side plus `input_service` services (cursor, pos, window_focus); relative-mouse MOUSE_TYPE_MOVE_RELATIVE=5 is supported by the Flutter client only; Linux Wayland input goes through uinput (`input_service::setup_uinput`, `server/uinput.rs`).
- Remote terminal: `terminal_service` (desktop) with ConPTY via portable-pty on Windows and a separate `--terminal-helper` process (`server/terminal_helper::run_terminal_helper`) because ConPTY is incompatible with CreateProcessAsUserW; Flutter has a full terminal model layer (terminal_model, mouse handler, alt-screen scroll).
- TCP port forwarding and RDP tunneling: `src/port_forward.rs` + `port_forward_mux.rs` (Tunnel/Claim/CHANNEL_WINDOW) forward local ports through the encrypted session; `run_rdp` shells out to cmdkey/mstsc on Windows reading creds from rdp_username/rdp_password env vars.
- Wake-on-LAN and LAN discovery: `wol-rs` dependency plus `lan::start_listening()` answering `PeerDiscovery` ping/pong UDP broadcasts gated by `enable-lan-discovery` option, reporting id/hostname/username/mac.
- Auth features: permanent/temporary passwords, approve modes, ID whitelist (`OPTION_ID_WHITELIST`), IP whitelist (`whitelist`), 2FA TOTP via totp-rs stored encrypted in config (`auth_2fa::generate2fa`/`verify2fa`), trusted devices, and optional server-side strategies synced from the API.
- Privacy mode on Windows/macOS blanks/locks the physical display during a session via pluggable impls under src/privacy_mode/ (win_mag, win_exclude_from_capture, win_virtual_display, win_topmost_window, macos); a separate whiteboard overlay feature lives in src/whiteboard/ (client/server/per-OS backends) launched via the `--whiteboard` CLI flag.
- Virtual display (Windows): `virtual_display_manager` drives either the RustDesk IDD driver (`rustdesk_idd`) or amyuni (`amyuni_idd`), selected by IDD_IMPL const; CLI `--install-idd`/`--uninstall-amyuni-idd` manage the driver.
- Remote printing (Windows, flutter builds): `remote_printer` crate installs/updates a printer (`--install-remote-printer` CLI) and `printer_service` streams jobs; gated on Win10+.
Data flow
All remote traffic is protobuf `Message`/`RendezvousMessage` frames over a `Stream` abstraction (TCP, KCP/UDP, WebSocket, WebRTC datachannel) provided by hbb_common. Controlled side: rendezvous registration over UDP to hbbs, then per-connection streams multiplexed to named services via `ConnInner` send channels. Controlling side: Session/LoginConfigHandler state owned per session, decoded frames pushed to Flutter via StreamSink events or texture handles. Config is TOML files (Config/Config2/LocalConfig/PeerConfig) synchronized between the root service process and user process over IPC.
- Identity/registration flow: `RendezvousMediator::start_udp` opens UDP to the rendezvous server (RENDEZVOUS_PORT) and loops sending `register_peer` on a timer; `handle_resp` processes RegisterPeerResponse→register_pk, RegisterPkResponse (OK→key_confirmed, UUID_MISMATCH→handle_uuid_mismatch, NOT_DEPLOYED→NEEDS_DEPLOY+DEPLOY_RETRY_INTERVAL backoff), plus latency EMA updates into Config.
- Inbound path: hbbs delivers PunchHole/relay requests → `accept_connection`/`create_relay_connection` → `create_tcp_connection` → `admit_unauthorized` (bounds unauthenticated conns) → `identity_handshake` (NaCl sign of IdPk + box_ key exchange; on WebRTC the DTLS fingerprint is signed into the identity) → `Connection::start`.
- Outbound path: `Remote::io_loop` → `Client::start` → `_start` sets up parallel transports — direct IP/domain shortcut, UDP punch socket + NAT test, IPv6 socket, optional WebRTC offerer — then `_start_inner` runs the rendezvous dance and a second offer-less request races a pure TCP punch fallback (`select_ok`/race_transports_prefer_webrtc).
- Session crypto: `secure_connection` verifies the peer's signed IdPk against the rendezvous-server public key (custom `key` option or built-in RS_PUB_KEY), on WebRTC additionally binds the signed DTLS fingerprint to fail closed against SDP-swap MITM, then installs a symmetric key via PublicKey message + `conn.set_key`.
- After login the controlled `Connection` runs a `tokio::select!` loop over: peer messages (on_message), CM IPC channel (`rx_from_cm`: Authorize→send_logon_response_and_keep_alive, Close, CmWindowClosed...), authed IPC channel, video/main send queues (tx, tx_video), hbbs broadcast receiver, and timers (test_delay, file_timer, per-second).
- Video frames flow display→`scrap::Capturer`→encoder→`Message`→`ServiceTmpl::send_video_frame_shared` to each subscribed `ConnInner`, whose `tx_video` queue is drained by the connection's send task; incoming `ConnInner::send` pushes `(Instant, Arc<Message>)` onto unbounded mpsc channels.
- UI data path on the controller: decoded frames/options go to `Session<T: InvokeUiSession>` which calls handler methods (FlutterHandler) that push JSON `{"name": event, ...}` onto per-app-type `GLOBAL_EVENT_STREAM` StreamSinks, or RGBA/texture via `EventToUI::Rgba/Texture`.
- Config persistence: `Config`/`Config2`/`LocalConfig`/`PeerConfig` (hbb_common TOML stores) are read/written through `ui_interface`/`ipc`; on Linux/macOS `sync_and_watch_config_dir` keeps the root service and user `--server` process configs in sync over IPC `Data::SyncConfig` every 0.3s, and `import_config` merges TOML by mtime.
- HTTP control plane: `hbbs_http::sync` posts heartbeat + sysinfo to `api-server` URL every 3s tick (15s heartbeat), downloads `StrategyOptions` (config_options + extra maps) that are applied into Config — meaning the server can remotely set any option key listed in libs/base/src/config/keys.rs. (inferred)
- LAN discovery is a separate UDP broadcast channel on get_broadcast_port(): `PeerDiscovery` protobuf ping→pong carrying id/mac/hostname/username, independent of the rendezvous path.
- Custom-client provisioning is embedded, not configured at runtime: `custom_server::get_custom_server_from_string` parses `host=,key=,api=,relay=` from the exe filename or a reversed base64/signed blob; `load_custom_client` runs at startup in core_main/flutter initialize.
- File-transfer data moves as `ipc::FS`/`FileAction` protobuf messages + raw bytes; `TransferJob` objects in libs/base/src/fs.rs own per-job state (read_jobs/write_jobs in both Remote and Connection), with digest checks (`CheckDigest`) for resume/overwrite detection.
Behavior traces
Three flows verified end-to-end: (1) cold start → host registration, (2) outgoing remote-desktop session, (3) incoming controlled connection. A fourth — config sync between the privileged service and user process — is the persistence backbone. Function names below are the real call chain.
- STARTUP (desktop, no args): main() → core_main() → common::global_init() → load_custom_client() → windows bootstrap → arg scan → hbb_common::init_log → std::thread::spawn(crate::start_server) → returns Some(flutter_args) → ui::start (sciter) or Flutter engine main().
- STARTUP (host): start_server(is_server=true) → ipc::start("") listener thread → drm_capturer::warm_availability (linux+drm) → input_service::fix_key_down_timeout_loop / setup_uinput → wait_initial_config_sync → RendezvousMediator::start_all().
- STARTUP (registration): RendezvousMediator::start_all → test_nat_type → hbbs_http::sync::start → check_zombie → new_server() → spawn direct_server listener → lan::start_listening thread → loop{ per rendezvous host spawn Self::start → start_udp → register_peer on timer → handle_resp }. SHOULD_EXIT restarts all.
- OUTGOING CONNECT (UI): Flutter connect → bind.sessionAdd/sessionStart (flutter_ffi) → flutter::session_add creates Session<FlutterHandler> + LoginConfigHandler.initialize → session_start_ → Remote::new + io_loop on a thread.
- OUTGOING CONNECT (network): Remote::io_loop → Client::start → _start → (ip/domain direct shortcut | rendezvous resolution) → UDP punch socket + test_udp_uat + ipv6 socket + WebRTCStream offerer → _start_inner + TCP-punch fallback raced → secure_connection (signed id + DTLS fingerprint bind + symmetric key) → LoginRequest sent → io_loop remote_loop on frames.
- INCOMING CONNECT: hbbs PunchHole → accept_connection → new_listener on the punched socket → create_tcp_connection → admit_unauthorized (64 global / 16 per-IP, oldest evicted) → stream.set_max_packet_length(128KiB) → identity_handshake → Connection::start → on_open → permission send_permission notices → select! loop → on_message → LoginRequest → check_login_scope + password/2FA or CM authorize → authorized=true → service subscription.
- INCOMING CONNECT (auth gates): unauthorized conns die on `unauthorized_evicted` or `login_deadline` (LOGIN_GRACE=180s); login failures are bucketed [password, 2FA, id-whitelist] in LOGIN_FAILURES with 10-minute decay for whitelist rejects; on success `self.authorized=true; self.unauthorized_id=None` releases the slot.
- PERSISTENCE: Config::get/set writes TOML via hbb_common; user process start_server(is_server=false) first ipc::connect→Data::SyncConfig to pull config from the running server; Linux/macOS server waits up to 3s (CONFIG_SYNC_INITIAL_WAIT_SECS) for root→local sync, then `sync_and_watch_config_dir` pushes local changes back to root every 0.3s.
- UNI-LINK dispatch: `rustdesk://`-style URIs or `--connect <id>` args are normalized in core_main_invoke_new_connection into `{prefix}{authority}/{id}?params` and forwarded to the running instance via D-Bus (linux), WM_USER+2 window message (windows), or ipc::send_url_scheme (macos); falls back to handling locally.
- CM approval flow: pending conn sends ipc::Data::Login{...} to the CM ipc server (ui_cm_interface::start_ipc); Flutter `--cm` window shows the prompt; operator accept sends Data::Authorize back → conn.send_logon_response_and_keep_alive; Data::Close→send_close_reason_no_retry+on_close.
- Service template loop: each registered Service runs ServiceTmpl::run/repeat thread that hibernates at HIBERNATE_TIMEOUT=30ms until a subscriber exists, calls state.init() on first subscriber and state.reset() when the last leaves — so capture/encode resources are only held during active sessions.
Testing signals
Rust tests are unit-style `#[cfg(test)]`/`#[test]` modules embedded across src/ and a dedicated directory for video_qos; Flutter tests use flutter_test in flutter/test. CI exercises builds heavily (flutter-build.yml matrix) and runs a small set of named cargo test filters on Linux plus one flutter test file; there is no end-to-end session/integration test harness — network paths are exercised only in production.
- Rust test framework is plain `cargo test` with tokio::test (tokio test-util in dev-dependencies) plus `hound` and `docopt` dev deps; tests live inline (mod tests) and in dedicated files like src/client/tests/, src/audio_resampler/*_tests.rs, src/server/video_qos/tests/.
- src/server/video_qos/tests/ is the most systematic suite — separate files per concern (baseline, startup, jitter, adaptation, recovery, robustness, invariants, sim, smoke) testing the QoS bitrate controller.
- Audio has dedicated test files on both sides: src/server/audio_service/audio_capture_encoder_tests.rs, audio_capture_queue_tests.rs, src/client/audio_playback_tests.rs, src/client/tests/audio_playback_recovery_tests.rs + audio_state_tests.rs, and src/audio_resampler/{tests,samplerate_tests,allocation_tests}.rs.
- CI runs targeted cargo tests only on the Linux build: `cargo test --locked --lib --features drm drm_capturer_tests`, `cargo test --locked --lib --features drm ipc::test`, and `cargo test --locked -p scrap --features drm hotspot_provenance_tests`/`hotspot_guess_tests`; comments note a test filter matching nothing exits 0, so names must be kept in sync.
- Flutter side has 12 test files in flutter/test covering terminal model/mouse/scroll/keyboard, file_model, cm, cursor_size, quality_monitor_transport, input_modifier_utils, autocomplete_peer_merge, server_settings_dialog; CI runs just `flutter test --timeout 60s test/cursor_size_test.dart`.
- Security-adjacent logic has unit tests: login_failure_check, port_forward_mux, ipc, updater, ui_cm_interface, platform/{windows,linux,mod}, drm_capturer, rdp_input, wayland, terminal_service, portable_service, display_service all contain #[test]/#[tokio::test] markers.
- core_main.rs has a test module verifying is_user_main_ipc_scope_cli_command matches exactly the management commands (--password/--set-unlock-pin/--get-id/--set-id/--config/--option/--assign/--deploy) and not runtime flags like --server/--cm/--connect.
- UNCOVERED: the ~8000-line src/server/connection.rs auth/login/session state machine, src/client.rs's 5800-line transport selection (punch/relay/WebRTC racing), and the Flutter<->Rust session lifecycle have no integration tests; examples/ipc.rs is a manual docopt test harness for IPC only. (inferred)
- Server::new_for_test() exists to construct a Server without real services for unit tests, indicating tests are expected to fake the service registry rather than hit real capture.
- No fuzzing, no property tests, no network-mock harness found; CI 'playground.yml' is a maintainer scratch workflow, not a test gate; docs/CONTRIBUTING.md exists but the scan shows no dedicated testing doc. (inferred)
Risk map
The riskiest surface is the unauthenticated pre-login path (deliberately bounded by slot pools and message caps), the IPC bridge between privileged service and user processes (heavily hardened with uid/executable checks), and updater/portable paths that write executables and delete files. Heavy use of forked/patched crypto-adjacent deps (webrtc, tungstenite, kcp) and `panic=abort` release profile amplify small defects. The empty hbb_common submodule means the actual wire crypto/proto code cannot be audited in this clone.
- Unauthenticated-connection resource guards: MAX_UNAUTHORIZED_CONNS=64 global, 16 per address, LOGIN_GRACE=180s, MAX_UNAUTHORIZED_MESSAGE=128KiB, plus PunchPool caps (32 UDP + 32 TCP punches) and MAX_WEBRTC_ANSWERERS=16 — all tuned against pre-auth DoS; weakening any of these constants is a security regression.
- MITM hardening on WebRTC: the controlled side signs its DTLS fingerprint into IdPk and the controller fails closed on mismatch ('possible MITM' bails); removing the dtls_fingerprint_bound check would silently downgrade to an unauthenticated channel.
- IPC authentication is platform-specific and intricate: ipc/auth.rs has Windows executable-identity checks (ensure_peer_executable_matches_current_by_pid_opt, portable-service token handshake), Linux uid allow-listing (is_allowed_service_peer_uid, peer_uid_from_fd) and SCM_RIGHTS fd passing for DRM capture (ipc/drm.rs) — a privileged-root attack surface.
- Path-traversal-hardened file deletion: portable packer's resolve_within rebuilds meta.toml-listed paths component-by-component rejecting prefixes/roots/parents/colons because meta.toml sits in a user-writable dir and drives deletion — prior-traversal-bug-shaped code.
- Updater runs with root/service privileges: macOS update requires a root-owned /var/run/rustdesk-update.lock with O_NOFOLLOW + flock; Windows update_me/--update path and `platform::install_me` copy executables — a compromised api-server/URL could push code (update URLs come from config/options). (inferred)
- Shell-out surface: core_main/platform code spawns pkill, caffeinate, cmdkey/mstsc (rdp), gtk_sudo::exec, terminal_helper ConPTY processes, and install/uninstall scripts; args are mostly fixed strings but `run_rdp` reads rdp_username/rdp_password env vars into cmdkey args.
- panic='abort' in release profile + pervasive `.unwrap()` on locks (e.g. `server.write().unwrap()`, `conns.lock().unwrap()`) means any poisoning or panic aborts the whole process including active sessions; AGENTS.md bans unwrap but legacy code is full of it. (inferred)
- The strategy-sync path lets the self-hosted server push arbitrary Config options (`StrategyOptions.config_options` applied to Config) — a malicious/compromised api-server could flip security options like enable-lan-discovery or disable-clipboard on all clients. (inferred)
- 2FA secret handling: TOTP secret is generated per generate2fa() and stored via encrypt_vec_or_original with a hardcoded "00" key context in config — obfuscation-grade encryption at rest, not real protection.
- Legacy Sciter UI (src/ui/) still compiled when `flutter` feature is off and reachable via ui::start — deprecated but alive code paths (CUR_SESSION, SciterHandler) enlarge the maintenance/audit surface.
- Forked network stack risk: webrtc/webrtc-sctp/webrtc-util and tungstenite are pinned to rustdesk-org forks with custom congestion-control and parsing changes; upstream CVE fixes will not flow through cargo update and must be cherry-picked by rev. (inferred)
Extension points
Extension happens at five seams: (1) the Service registry — implement `Service`/`Subscriber` and register in `server::new()`; (2) ConnType — add a variant through protos + session_add dispatch + Flutter WindowType; (3) option keys — append to libs/base/src/config/keys.rs and src/lang template; (4) CLI flags — extend the args[0] chain in core_main; (5) Flutter FFI — new pub fns in flutter_ffi.rs + flutter_rust_bridge codegen. Platform code goes in src/platform/*, not shared paths.
- Service plugin point: implement `trait Service` (or reuse ServiceTmpl<T: Subscriber>) and call `server.add_service(Box::new(...))` in `new()` — video services are even added dynamically at runtime via try_add_monitor_service/try_add_primary_camera_service keyed by `get_service_name(VideoSource, idx)`.
- New connection type: add a ConnType in message proto (hbb_common/base protos), branch it in `session_add` (is_* bools), map it in `peer_get_sessions_count`, add a WindowType + kAppType in Flutter main.dart, and handle the login_request::Union arm in connection.rs.
- Option keys: single import path `base::config::keys` re-exports hbb_common's keys plus ~100 client keys; new options are added as `pub const OPTION_*` and consumed via config::option2bool/Config::get_option — server-pushed strategies can set them remotely.
- CLI extension is a linear `else if args[0] == "--x"` chain in core_main; note the pattern of gating management commands behind `platform::is_installed() && is_root()` and routing root CLI to user IPC via UserMainIpcScope/is_user_main_ipc_scope_cli_command.
- IPC message extension: add variants to serde-tagged `enum Data` (and FS/DataKeyboard/etc. sub-enums); both ipc::start listener handlers and ui_cm_interface consumers must be updated; FileBlockFromCM documents the raw-bytes side-channel protocol (send message then send_raw).
- Flutter FFI extension: add `pub fn` in src/flutter_ffi.rs (naming convention: session_*, main_*, peer_*; SyncReturn for sync calls) and regenerate bridge_generated via flutter_rust_bridge 1.80; events to UI go through GLOBAL_EVENT_STREAM via push_global_event(channel, json).
- Localization: add keys to src/lang/template.rs + every locale file per AGENTS.md rules (en.rs only when display differs from key; it.rs always empty string); keys are the English text themselves, sentence-case.
- Platform seams: src/platform/{windows.rs,linux.rs,macos.rs} + per-OS subdirs implement the hooks called from shared code (install_me, is_installed, elevate_or_run_as_system, check_autostart_config...); AGENTS.md mandates putting new platform logic in these modules with thin call-site hooks.
- Codec/capture backends are cfg-gated extensions in libs/scrap (dxgi, quartz, x11, wayland, android, drm capturer) and codec EncoderCfg dispatch in video_service — adding a capture backend means a new scrap module + feature flag + service wiring.
- Privacy-mode impls are pluggable per-OS under src/privacy_mode/ (win_mag, win_virtual_display, win_exclude_from_capture, win_topmost_window, macos) selected via is_current_privacy_mode_impl/PRIVACY_MODE_IMPL_* constants.
- Custom-server branding is a deployment-time extension: embed signed CustomServer JSON (host/key/api/relay) via exe filename or config string — verified against a hardcoded sign::PublicKey — rather than a plugin API.
- AGENTS.md's own extension doctrine: prefer purely additive #[cfg]-gated blocks and new functions over restructuring; 'feature off must run the old code'; shared-file changes should be thin one-line hooks — this is the enforced contribution contract.
Contributor handoff
Read AGENTS.md first — it is the authoritative style/scope contract (no unwrap, no nested tokio runtimes, minimal diffs, additive-only changes, import/comment/localization rules). Build requires the hbb_common submodule and system deps (vcpkg/pkg-config for vpx/opus/aom); the pinned Rust 1.75 toolchain and Flutter 3.24.5 matter. Safe edit boundaries: new code in feature modules or src/platform/*; dangerous: shared protocol/session files and anything touching pre-auth limits.
- AGENTS.md is the governing doc (CLAUDE.md just @-imports it): bans unwrap/expect outside tests & lock acquisition, bans nested runtimes/block_on in async, bans locks across .await, mandates single-braced use blocks per crate, and requires a 'regression-surface' report for every change.
- First files to read for any session work: src/core_main.rs (entry/CLI), src/server.rs + src/server/connection.rs (host), src/client.rs + src/client/io_loop.rs (controller), src/ipc.rs (inter-process Data enum), libs/base/src/config/keys.rs (all option names).
- Toolchain pins: rust-version 1.75 (Cargo.toml), CI RUST_VERSION 1.75 / macOS 1.81, Flutter 3.24.5 (3.44.9 win-arm64), flutter_rust_bridge '=1.80'/1.80.1 — mismatched frb versions regenerate incompatible bindings.
- Build commands: `cargo build --locked --features flutter` (desktop lib) or `python3 build.py` for full app packaging; quick checks: `cargo test --locked --lib --features drm` (Linux), `flutter test` in flutter/; vcpkg must provide libvpx/libyuv/opus/aom per Dockerfile.
- Editing `libs/hbb_common` costs a round-trip: it is shared with rustdesk-server — put client-only code in libs/base instead; base::config::keys re-exports the hbb_common keys so callers never import from two places.
- Known traps: `is_quick_support_exe` must stay in sync with libs/portable/src/main.rs; the CLI↔IPC routing list is_user_main_ipc_scope_cli_command has a regression test; IPC FileBlockFromCM requires message-then-send_raw ordering; tray icon assets are include_bytes!()'d from res/ (compile-time).
- Process model trap: the same binary runs as service/root (--server), user GUI, tray, and --cm simultaneously; shared state does NOT cross processes — it goes through ipc::Data or Config file sync (sync_and_watch_config_dir); never assume a global static is visible to the other process.
- Security-sensitive zones to touch carefully: pre-auth limits in connection.rs/rendezvous_mediator.rs (64/16/128KiB/180s/32-punch/16-answerer constants), ipc/auth.rs peer verification, updater.rs update paths, custom_server.rs signature verification — changes here need the 'regression surface' analysis AGENTS.md demands.
- Flutter-side session ownership: Session<T:InvokeUiSession> + LoginConfigHandler (ui_session_interface.rs) is the shared abstraction — Flutter implements it via FlutterHandler/event streams, legacy Sciter via remote::SciterHandler; new UI behavior should be added to the InvokeUiSession trait rather than bypassing it.
- cfg discipline: almost every module is gated per-OS/feature; `#[cfg(not(any(target_os="android",target_os="ios")))]` is the desktop idiom, `feature="flutter"` selects the UI backend, and imports must be folded into per-crate braced blocks with separate use only for differently-gated items.
- Testing expectation: add #[cfg(test)] mods or files next to the code (video_qos/tests pattern); CI only runs named filters on Linux and one flutter test file — a renamed test silently stops running because empty filters exit 0 (noted in workflow comments).
Languages
| Language | Files | Share of source |
|---|---|---|
| Rust | 287 | 74% |
| Markdown | 81 | docs/config |
| C | 22 | 6% |
| C++ | 20 | 5% |
| YAML | 20 | docs/config |
| Shell | 18 | 5% |
| Python | 15 | 4% |
| Kotlin | 13 | 3% |
Start reading here
Probable source entrypoint: Cargo.toml — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (202 files)
Repository structure
| Top-level path | Files |
|---|---|
flutter/ | 262 |
src/ | 202 |
libs/ | 154 |
res/ | 123 |
docs/ | 63 |
.github/ | 18 |
fastlane/ | 14 |
flatpak/ | 2 |
Docs found: AGENTS.mdCLAUDE.mdREADME.mddocs/CODE_OF_CONDUCT-DE.mddocs/CODE_OF_CONDUCT-FR.mddocs/CODE_OF_CONDUCT-JP.mddocs/CODE_OF_CONDUCT-KR.mddocs/CODE_OF_CONDUCT-NL.mddocs/CODE_OF_CONDUCT-NO.mddocs/CODE_OF_CONDUCT-PL.md
Package
Cargo.toml — RustDesk v1.5.0, 80 declared dependencies.
android-wakelockandroid_loggerarboardasync-processasync-traitbasebytemuckbytescccfg-ifchronocidr-utils
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructionsCLAUDE.md— Claude instructionssrc/main.rs— Rust binary entrypointsrc/lib.rs— Rust library entrypointlibs/portable/src/main.rs— Rust binary entrypoint.github/workflows/clear-cache.yml— GitHub Actions workflow.github/workflows/flutter-build.yml— GitHub Actions workflow.github/workflows/update-webpki-roots.yml— GitHub Actions workflow.github/workflows/flutter-tag.yml— GitHub Actions workflow
Structural health signals
Average score 6/10 across 96 analyzed source files; 51 hotspots surfaced.
src/server/connection.rs— 1865 lines, score 4 (Large file, Deep nesting)src/platform/windows.rs— 2433 lines, score 4 (Large file, Deep nesting)src/client/io_loop.rs— 1062 lines, score 4 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
c6a358afbe372026-09-22 — Update lang.rs - Splits ambiguous pt into pt-pt and pt-br (#16155)458ffe44c6102026-09-22 — Update Ukrainian README (#16309)2e7255f083332026-09-22 — fix: suppress heartbeat HTTP retry and TCP proxy logs (#16273)12f61b1fda042026-09-22 — Fix/drm rotated cursor (#16242)6a0eb9a1fda52026-09-22 — fix(terminal): preserve trackpad scrolling in alternate screen