projects/shelled/rustdesk-web-client/index.html
Z User 7b758ebe01 Add standalone RustDesk web client (reverse-engineered)
Complete browser-based remote desktop client built from protocol analysis:
- Protobuf message definitions (75+ message types, 10 enums)
- NaCl encryption (Ed25519 + Curve25519 ECDH + XSalsa20-Poly1305)
- WebSocket signaling (hbbs) + relay (hbbr) connection lifecycle
- VP8/VP9/AV1 video decoding via ogvjs WASM + yuv-canvas WebGL
- Opus audio decoding via libopus WASM + Web Audio API
- Full mouse/keyboard input forwarding to protobuf events
- Connection dialog, status bar, toolbar, log panel UI

Also tracked web_deps (codec libraries) in rustdesk-as-ref/
2026-04-06 21:11:21 +00:00

153 lines
6.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>RustDesk - Remote Desktop</title>
<link rel="stylesheet" href="css/style.css">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖥️</text></svg>">
</head>
<body>
<!-- Connection Overlay -->
<div id="connection-overlay">
<div class="connection-card">
<h1>RustDesk</h1>
<p class="subtitle">Standalone Web Client — Remote Desktop</p>
<form id="connection-form" onsubmit="return false;">
<div class="form-group">
<label for="server-addr">Server Address</label>
<input type="text" id="server-addr" placeholder="hbbs.example.com or 192.168.1.100:21115"
value="" autocomplete="off" spellcheck="false">
</div>
<div class="form-group">
<label for="peer-id">Remote Peer ID</label>
<input type="text" id="peer-id" placeholder="e.g. 9-digit peer ID"
autocomplete="off" spellcheck="false" style="font-family: monospace; font-size: 18px; letter-spacing: 2px;">
</div>
<div class="form-row">
<div class="form-group" style="flex: 2;">
<label for="server-key">Server Public Key (optional)</label>
<input type="text" id="server-key" placeholder="For encrypted connections"
autocomplete="off" spellcheck="false" style="font-family: monospace; font-size: 11px;">
</div>
<div class="form-group" style="flex: 0; display: flex; align-items: flex-end;">
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; margin-bottom: 2px;">
<input type="checkbox" id="tls-check" checked>
<span style="font-size: 12px; color: var(--text-secondary);">TLS</span>
</label>
</div>
</div>
<button type="button" id="btn-connect" class="btn btn-primary">Connect</button>
</form>
</div>
</div>
<!-- Password Dialog -->
<div id="password-overlay">
<div class="password-card">
<h2>🔐 Authentication Required</h2>
<p style="font-size: 13px; color: var(--text-secondary); margin-bottom: 16px;">
The remote peer requires a password to connect.
</p>
<div class="form-group">
<input type="password" id="password-input" placeholder="Enter password" autocomplete="off">
</div>
<div style="display: flex; gap: 8px;">
<button type="button" class="btn btn-primary" id="btn-password-submit">Connect</button>
<button type="button" class="btn btn-danger" id="btn-password-cancel">Cancel</button>
</div>
</div>
</div>
<!-- Status Bar -->
<div id="status-bar">
<span class="status-indicator" id="status-indicator"></span>
<span class="status-text" id="status-text">Disconnected</span>
<span class="status-text" style="margin-left: 16px;">
Peer: <strong id="peer-id-display"></strong>
</span>
<span class="status-text" style="margin-left: 16px;">
Client: <strong id="client-id-display"></strong>
</span>
<div class="status-right">
<span class="fps-display" id="fps-display">— FPS</span>
</div>
</div>
<!-- Remote Desktop Canvas -->
<div id="remote-canvas-wrapper">
<canvas id="remote-canvas" tabindex="0"></canvas>
</div>
<!-- Toolbar -->
<div id="toolbar">
<button class="btn-icon" id="btn-disconnect" title="Disconnect">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18.36 6.64a9 9 0 11-12.73 0M12 2v10"/>
</svg>
</button>
<div class="toolbar-divider"></div>
<button class="btn-icon" id="btn-fullscreen" title="Fullscreen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/>
</svg>
</button>
<div class="toolbar-divider"></div>
<button class="btn-icon" id="btn-mute" title="Toggle Audio">
🔊
</button>
<button class="btn-icon" id="btn-ctrl-alt-del" title="Ctrl+Alt+Del">
⌨️
</button>
<button class="btn-icon" id="btn-keyboard" title="Toggle Keyboard Mode">
🔣
</button>
<div class="toolbar-divider"></div>
<button class="btn-icon" id="btn-log" title="Toggle Log">
📋
</button>
</div>
<!-- Log Panel -->
<div id="log-panel">
<div class="log-header">
<span>Connection Log</span>
<button class="btn-icon btn-sm" id="btn-log-close"></button>
</div>
<div class="log-content" id="log-content"></div>
</div>
<!-- Toast Container -->
<div id="toast-container"></div>
<!-- External Libraries -->
<script src="https://cdn.jsdelivr.net/npm/protobufjs@7/dist/protobuf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tweetnacl@1/nacl.min.js"></script>
<!-- Codec Libraries (from web_deps) -->
<script src="lib/ogvjs-1.8.6/ogv-support.js"></script>
<script src="lib/ogvjs-1.8.6/ogv-decoder-video-vp8-wasm.js"></script>
<script src="lib/ogvjs-1.8.6/ogv-decoder-video-vp9-wasm.js"></script>
<script src="lib/ogvjs-1.8.6/ogv-decoder-video-av1-wasm.js"></script>
<script src="lib/ogvjs-1.8.6/ogv-worker-video.js"></script>
<script src="lib/ogvjs-1.8.6/ogv.js"></script>
<script src="lib/yuv-canvas-1.2.6.js"></script>
<script src="lib/libopus.js"></script>
<!-- Application -->
<script src="js/proto.js"></script>
<script src="js/crypto.js"></script>
<script src="js/connection.js"></script>
<script src="js/video.js"></script>
<script src="js/audio.js"></script>
<script src="js/input.js"></script>
<script src="js/app.js"></script>
</body>
</html>