Files
rustdesk-server/web_client
mike 4308a2f112 feat: M6 web client QoL — clipboard, multi-monitor, quality, reconnect, H.264, docs
Builds on the M6a-g MVP (d07e98e) with five user-facing features that
take the in-browser remote-control client from "demo toy" to "workable
for daily use", plus user-facing documentation.

M6h — Text clipboard sync (both directions)
- Host → browser via Clipboard{format: Text} → navigator.clipboard.writeText.
- Browser → host via Cmd-V/Ctrl-V keydown intercept → navigator.clipboard
  .readText → Message{clipboard}, sent BEFORE the V keystroke so the
  host's paste hotkey lands on the freshly-synced text.
- Handles both single-format Clipboard (older peers) and MultiClipboards
  wrapper (peers ≥ 1.3.0; gated by clipboard.rs:is_support_multi_clipboard).
- Switched printable-with-modifier hotkeys (Ctrl-C etc.) from Unicode to
  Chr payload — host's process_unicode does key_sequence(char) which
  ignores modifiers, breaking copy/cut; process_chr respects them.
- Firefox refuses navigator.clipboard.readText() by default — accepted as
  a known browser limitation, host → browser direction works regardless.

M6i — Multi-monitor switching
- HUD picker shown when peer_info.displays > 1.
- On change: SwitchDisplay + CaptureDisplays{set:[idx]} two-message dance
  — required for clients ≥ 1.2.4 (we send "1.4.0"). Without the follow-
  up, switch_display_to leaves both video services subscribed and
  switching display 0 → 1 → 0 doesn't restore display 0.
- Mouse coords offset by the active display's virtual-desktop origin
  (DisplayInfo.x, .y). Without this, clicks on display 2 landed on
  display 1 because both share canvas (0,0) but only display 1 has
  origin (0,0) in virtual-desktop space.

M6j — Quality / FPS / mute controls
- Image quality preset (Low/Balanced/Best) → Misc{option: {image_quality}}.
- Custom FPS (15/30/60) → Misc{option: {custom_fps}}; host caps at 30
  unless allow_more_fps is advertised.
- Mute toggle additionally sends Misc{option: {disable_audio: Yes/No}}
  so the peer stops encoding audio while muted (saves CPU + bandwidth).

M6k — Auto-reconnect on transient drops
- session.recv() throw → reconnect with exponential backoff: 1s, 2s, 4s,
  8s, 16s, 30s, 30s, capped at 30s, max 10 attempts.
- Dim overlay sits on top of the canvas during retry; canvas keeps
  last-known frame for visual continuity.
- Auth errors (password/signature) bail immediately — no point retrying.
- User options (mute, image_quality, custom_fps, current display)
  re-applied to host on each successful reconnect, since host treats
  every session as fresh and resets to defaults.
- Architecture: `session` is a let-binding mutated on reconnect; HUD
  button closures read it at click-time so they automatically retarget.
  Input modules (mouse/keyboard/clipboard) get a Proxy that forwards
  method calls to whatever session is current — avoids re-binding
  window/canvas listeners on each reconnect.

M6l — H.264 video decode (Annex-B + SPS-derived codec string)
- decode/bitstream.ts: iterate Annex-B NAL units, derive avc1.PPCCLL
  from the keyframe's inline SPS (host's hwcodec defaults to high
  profile; a hardcoded baseline string would make WebCodecs refuse the
  stream).
- Defer H.264 decoder configure until first keyframe arrives.
- VP9 codec string corrected from level 1.0 (vp09.00.10.08) to level
  5.0 (vp09.00.50.08) — wrong level was probably forcing software
  decode in some browsers.
- Default prefer flipped to VP8 (cheapest software encoder; H.264 path
  stays implemented for hosts with hwcodec/nvenc).

M6m — docs/CONFIGURATION.md "Web client" section: routes, browser
matrix, network requirements (relay reachability + reverse-proxy WS
upgrade), feature status table, codec selection rationale, the
recv/dec/draw HUD diagnostic, build commands.

Bundle: 535 KB / ~75 KB gzipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:43:23 +02:00
..

RustDesk web client

Browser-based RustDesk client embedded in rustdesk-server. Surfaced from the admin dashboard as a "Connect" button on the Devices page.

Architecture (one-liner)

Plain TypeScript SPA → talks WebSocket directly to hbbs:21118 (rendezvous) and hbbr:21119 (relay) → protobufjs for wire format → libsodium-wrappers for crypto → WebCodecs for video/audio decode → <canvas> for display.

No frameworks. ~1 MB minified.

Building

./build.sh        # bundles to dist/bundle.{js,css}
git add dist/

dist/ is committed so cargo build -p hbbs doesn't need Node. Anyone touching code under src/ should re-run ./build.sh and commit the new dist/ files in the same commit.

Regenerating proto bindings

Rare — only when libs/hbb_common bumps and proto fields change:

npm run protogen
./build.sh
git add src/proto/generated.* dist/

Layout

src/
  main.ts                 boot: read #custom-config, init transport
  crypto.ts               libsodium wrapper
  proto/                  generated protobufjs static modules (committed)
  transport/              rendezvous WS, relay WS, secure handshake state machine
  decode/                 video (WebCodecs VideoDecoder), audio (AudioDecoder)
  input/                  mouse/keyboard capture → protobuf MouseEvent/KeyEvent
  ui/                     canvas + toolbar + style.css
  audit.ts                POST /api/audit/conn with admin cookie
dist/
  bundle.js + .css        committed esbuild output

Wire-protocol references

  • /Users/sn0/Desktop/rustdesk-server/libs/hbb_common/protos/{rendezvous,message}.proto
  • /Users/sn0/Desktop/rustdesk/src/client.rs — desktop-client connect/secure/login state machine
  • /Users/sn0/Desktop/rustdesk-server/libs/hbb_common/src/tcp.rs:296-344 — secretbox nonce derivation (8-byte LE counter, separate per direction)