mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-07 21:11:05 +03:00
* docs(agents): add a comment-length rule Comments were growing to document rejected alternatives, past bugs and measurements. That belongs in the commit message, not the source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci(drm): build the unattended-wayland deb in the release workflow The deb was built by a separate drm-capture workflow on a plain runner, so it diverged from every other Linux deb: different base, different vcpkg/ffmpeg, different toolchain. Move it into flutter-build.yml as build-rustdesk-linux-drm, mirroring build-rustdesk-linux's x86_64 path -- same ubuntu18.04 container, same vcpkg install, same rust and flutter. libdrmtap is built on the runner first and handed to the container via DRMTAP_PREBUILT_DIR, because bionic's meson is too old to build it. The job is ungated, so the --drm packaging path is exercised on every PR; only publishing stays gated on upload-artifact. drm-capture.yml is deleted along with docs/DRM_CAPTURE_SECURITY.md -- the 29 drm unit tests that workflow ran are no longer executed by CI. Three bugs the move exposed: - build.py anchored the libdrmtap paths on abspath(__file__), which is only cwd-independent on Python >= 3.9 (bpo-20443). The packaging container runs 3.6 and chdir's into flutter/, so the ABI-gate cross-check resolved one directory off and every --drm packaging run would have died with FileNotFoundError. Captured as REPO_ROOT at import instead. - DRMTAP_PREBUILT_DIR no longer needs DRMTAP_ALLOW_UNPINNED. A prebuilt dir inside the repo's own third_party/libdrmtap at the pinned sha is the pinned object, not an override, and is now verified as such. - The variant's Depends carried a bare libdrm2. libdrmtap needs drmModeGetFB2, so it is libdrm2 (>= 2.4.95); below that the package installed and could never capture. The loader also logs the dlerror now instead of discarding it, so a soname or glibc mismatch is named rather than surfacing as a generic "libdrmtap not available". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(drm): declare the unattended-wayland deb's real libc6 and libdrm floors libdrmtap is built on the ubuntu-22.04 runner while the rest of the deb comes from the ubuntu18.04 container, so the package has a mixed glibc floor and declared neither half. It installed happily on Ubuntu 20.04 / Debian 11 (glibc 2.31), then dlopen failed on GLIBC_2.34 and capture degraded to the PipeWire portal -- the one thing this variant exists to avoid. Measure the floor off the staged objects and put it in Depends, so apt refuses with a reason instead of handing over a package that can never capture. Measured rather than written down: the number moves whenever either base does, and it lands exactly on RHEL/Rocky 9 (glibc 2.34), where one off-by-one decides whether that whole family can install. drmModeGetFB2 landed in libdrm 2.4.101, not 2.4.95 -- checked against the libdrm tags, xf86drmMode.h first declares it in 2.4.101. The old floor admitted Debian 10 (2.4.97), where the .so is linked -z now and dies on an undefined symbol at dlopen. libdrmtap's own meson.build carries the same wrong number. Upload the deb on always(): the run that fails the drm check is the one whose artifact is most worth downloading. Publish stays gated on success, so an unverified build still cannot reach a release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
107 lines
5.1 KiB
Markdown
107 lines
5.1 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.
|
|
|
|
## 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.
|
|
|
|
### Comments
|
|
|
|
* Keep them short: one line by default, three at most.
|
|
* Say **why**, never what. If the code already says it, delete the comment.
|
|
* Do not document rejected alternatives, past bugs, measurements, or how you arrived at the code. That belongs in the commit message or the PR.
|
|
* A comment must never be longer than the code it describes.
|
|
* Applies to YAML, shell and Python too, not just Rust.
|
|
|
|
### Be minimally invasive
|
|
|
|
* Prefer purely additive changes: layer new (`#[cfg]`-gated) blocks or new functions around existing code instead of restructuring it. The ideal diff for a fix adds lines and modifies/deletes none.
|
|
* Do not extract or reshape existing code just to enable your new code; look for a mechanism that leaves existing lines untouched (e.g. hide/show an existing object instead of refactoring its construction into a helper for rebuilding).
|
|
* Put new logic in self-contained functions in the module it belongs to (platform-specific logic in `src/platform/`, with `use` inside the function body to avoid churning shared import blocks). Call sites in shared files (`src/tray.rs`, `src/core_main.rs`, `src/server/connection.rs`, …) should be thin one-line hooks.
|
|
|
|
## Localization (`src/lang/*.rs`)
|
|
|
|
Each file is a `HashMap<key, translation>`. Layout:
|
|
|
|
* `template.rs` is the master list of every key. **Never edit it** as part of translation work.
|
|
* `en.rs` holds only the keys whose English display text differs from the key itself.
|
|
* Every other file (`de.rs`, `fr.rs`, …) carries the full key set; an untranslated entry has an empty value: `("key", "")`.
|
|
|
|
### Finding the English source for a key
|
|
|
|
When filling an empty entry, determine the source English text with this rule:
|
|
|
|
* If `key` exists in `en.rs` **with a non-empty value**, that value is the source text (look it up in `en.rs`).
|
|
* Otherwise the **key string itself is the source text** (the key is already plain English).
|
|
|
|
Then translate that source into the file's target language (infer the language from the file's existing non-empty entries / filename).
|
|
|
|
### Translation hygiene
|
|
|
|
* Only fill empty values. Never change keys, and never touch existing non-empty translations.
|
|
* Preserve placeholders (`{}`) and escape sequences (`\n`, `\"`) exactly as in the source.
|
|
* Do not translate brand or technical tokens: `RustDesk`, `Socks5`, `TLS`, `UAC`, `Wayland`, `X11`, `TCP`, `UDP`, `2FA`, `RDP`, `D3D`, etc.
|
|
* Copy URL values (e.g. `doc_*` keys) verbatim from `en.rs`.
|
|
|
|
### Adding new keys (feature work)
|
|
|
|
* New English-text keys use sentence case, not Title Case: `Use ID whitelisting`, **not** `Use ID Whitelisting`. Acronyms (ID, IP, 2FA…) stay uppercase. Legacy Title-Case keys (e.g. `Use IP Whitelisting`) stay as-is — do not rename them.
|
|
* Since the key itself is the English display text, a sentence-case key usually needs **no** `en.rs` entry; add one only when the display text must differ from the key (e.g. `*_tip` keys).
|
|
* Append each new key to `template.rs` (with `""`) and to every `src/lang/*.rs` file (translated, or `""` if unsure), at the end of the list.
|