Files
rustdesk/AGENTS.md
RustDesk 85a5fefab8 fix(windows): prevent ghost and duplicate tray icons (#15689) (#15690)
* docs(agents): require minimally invasive, additive-first patches

Codify the review feedback from the tray ghost-icon fix: fixes should
add self-contained code around existing lines instead of restructuring
them, keep platform-specific logic in src/platform/ with fn-local
imports, and leave only thin one-line hooks in shared files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(windows): stop duplicate tray icons from piling up (#15689)

`check_process("--tray", ..)` is used to decide whether a tray process
needs to be spawned, but it can miss one that is already running: it
cannot read the command line of an elevated process from a non-elevated
one (the installer spawns the tray elevated), and wmic, used by 32-bit
builds since #11638, is gone from newer Windows 11. `connection.rs` runs
that check once per incoming connection, so every miss added another tray
icon and they kept piling up, which is the same blind spot behind #6692.

Hold a named mutex in the session namespace as the authoritative single
instance guard, so a redundant tray process exits before creating an
icon. `ERROR_ACCESS_DENIED` also counts as "already running", since it
means the mutex belongs to a tray we may not touch.

Also remove the icon before the tray menu's "Stop service" calls
uninstall_service(): on success it ends the process with
std::process::exit, which skips the destructor that would call
Shell_NotifyIcon(NIM_DELETE), so every click left a ghost icon behind.
The icon is shown again if stopping the service failed or was cancelled.

Ghost icons from the taskkill in the install/update/service flows are
left alone here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(windows): note that update_me's pid lookup can silently find nothing

The pids are matched by command line, which comes back empty for a 32-bit
build reading 64-bit processes (hence the `wmic` fallback of #11638, and
`wmic` is no longer installed by default since Windows 11 24H2) and for a
non-elevated process reading an elevated one. `taskkill` matches by image
name and still works, but the session lists are then empty, so the restore
guard silently restores nothing and the update leaves the user without a
tray icon and main window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(windows): record the confirmed cause of the duplicate tray icons

Process Explorer output in #15689 pinned it down: run_after_run_cmds()
spawns the tray in the caller's own context, so installing or toggling
the service from a RustDesk that was itself started elevated leaves a
high integrity tray behind, which a medium integrity main window cannot
inspect afterwards. Record where the detection fails exactly, so the next
reader doesn't have to rediscover that the executable path, not the
command line, is what comes back empty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 12:18:57 +08:00

4.7 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.

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.