Files
rustdesk/AGENTS.md
RustDesk 9a81c8a138 Drm deb in release workflow (#15776)
* 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>
2026-08-07 08:31:09 +08:00

5.1 KiB

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.