4308a2f112
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>
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)