diff --git a/shelled/shelled-os-ui/src-tauri/Cargo.toml b/shelled/shelled-os-ui/src-tauri/Cargo.toml index dd35a53..564b1db 100644 --- a/shelled/shelled-os-ui/src-tauri/Cargo.toml +++ b/shelled/shelled-os-ui/src-tauri/Cargo.toml @@ -12,4 +12,3 @@ tauri-build = { version = "2", features = [] } tauri = { version = "2", features = [] } serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1", features = ["full"] } diff --git a/shelled/shelled-os-ui/src-tauri/binaries/README.md b/shelled/shelled-os-ui/src-tauri/binaries/README.md index 190d2ba..9b2b18a 100644 --- a/shelled/shelled-os-ui/src-tauri/binaries/README.md +++ b/shelled/shelled-os-ui/src-tauri/binaries/README.md @@ -1,29 +1,95 @@ -# RustDesk Sidecar Binary +# RustDesk Integration — Build Instructions -Place the compiled RustDesk binary here: +## Architecture -- **Windows**: `rustdesk-x86_64-pc-windows-msvc.exe` → rename to `rustdesk.exe` -- **Linux**: `rustdesk-x86_64-unknown-linux-gnu` → rename to `rustdesk` -- **macOS**: `rustdesk-universal-apple-darwin` → rename to `rustdesk` +``` +Shelled OS (Tauri) +├── Main Window: OS desktop UI (taskbar, popups) +├── RustDesk Server: Native binary (--server mode, ports 21115-21119) +└── RustDesk Web Client: Flutter web build loaded in a Tauri webview + └── Connects to server via WebSocket (ws://localhost:21118, ws://localhost:21119) +``` -## How to build RustDesk +## Step 1: Build RustDesk Native Binary -See `shelled/rustdesk-as-ref/CLAUDE.md` for build instructions. +The RustDesk binary is built from `shelled/rustdesk-as-ref/`. -### Quick build (Windows with vcpkg) +### Prerequisites (Windows) +- Rust toolchain (`rustup`) +- vcpkg with: `libvpx`, `libyuv`, `opus`, `aom` +- Visual Studio Build Tools (C++ workload) ```powershell -cd shelled/rustdesk-as-ref -set VCPKG_ROOT= +set VCPKG_ROOT=C:\path\to\vcpkg +cd shelled\rustdesk-as-ref cargo build --release ``` -The output binary will be at: -`target/release/rustdesk.exe` - -Copy it to this directory: +### Copy binary ```powershell +mkdir shelled\shelled-os-ui\src-tauri\binaries copy target\release\rustdesk.exe shelled\shelled-os-ui\src-tauri\binaries\rustdesk.exe ``` -Tauri will automatically bundle this binary when you run `tauri build`. +## Step 2: Build RustDesk Web Client + +The web client is a Flutter web build that communicates with the server via WebSocket. + +### Prerequisites +- Flutter SDK 3.24+ +- The `flutter/web/js/` WASM bridge (from `rustdesk-web-client` project) + +### Build +```powershell +cd shelled\rustdesk-as-ref\flutter + +# Install web dependencies +flutter pub get +flutter create --platforms web . # Generate web/ directory if missing + +# Build Flutter web +flutter build web --release +``` + +### Copy to Shelled OS +```powershell +mkdir shelled\shelled-os-ui\rustdesk-web +xcopy build\web\* shelled\shelled-os-ui\rustdesk-web\ /E +``` + +### Note on the WASM Bridge +The RustDesk web client requires a JS/WASM bridge layer that provides: +- `setByName(name, value)` — Flutter → Rust/WASM commands +- `getByName(name, value)` — Rust/WASM → Flutter data +- `init()` — Initialize the connection + +This bridge was removed from the main RustDesk repo. You'll need to get it from: +- GitHub Releases: `web_deps.tar.gz` from `rustdesk/doc.rustdesk.com` +- Or the `MonsieurBiche/rustdesk-web-client` fork's `fix-build` branch + +## Step 3: Run Shelled OS + +```powershell +cd shelled\shelled-os-ui +npm run dev +``` + +### Using the Remote Desktop +1. Click the Remote Desktop icon in the taskbar +2. Click "Start Server" to launch the RustDesk server +3. Click "Launch" to open the web client in a new window +4. Connect to remote machines through the web client + +## Server Ports + +| Port | Protocol | Purpose | +|------|----------|---------| +| 21115 | TCP | NAT type test | +| 21116 | TCP+UDP | ID registration & signaling | +| 21117 | TCP | Relay server | +| 21118 | TCP/WS | WebSocket signaling (web client) | +| 21119 | TCP/WS | WebSocket relay (web client) | + +## WebSocket URLs (for web client) +- Signal: `ws://localhost:21118` (or `wss://` behind reverse proxy) +- Relay: `ws://localhost:21119` (or `wss://` behind reverse proxy) diff --git a/shelled/shelled-os-ui/src-tauri/src/main.rs b/shelled/shelled-os-ui/src-tauri/src/main.rs index ff4597f..be3fead 100644 --- a/shelled/shelled-os-ui/src-tauri/src/main.rs +++ b/shelled/shelled-os-ui/src-tauri/src/main.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; use std::process::{Child, Command, Stdio}; use std::sync::Mutex; -use tauri::Manager; +use tauri::{Manager, WebviewUrl, WebviewWindowBuilder}; #[cfg(target_os = "windows")] use std::os::windows::process::CommandExt; -/// State holding the RustDesk child process +/// State holding the RustDesk server process struct RustDeskState { process: Mutex>, } @@ -22,23 +22,20 @@ struct RustDeskStatus { pid: Option, } -/// Get the path to the RustDesk sidecar binary +/// Find the RustDesk binary fn get_rustdesk_path() -> Result { let exe_dir = std::env::current_exe() .map_err(|e| format!("Failed to get current exe path: {}", e))?; - // In development, look alongside the Tauri binary - // In production, look in the same directory (Tauri bundles sidecars there) let exe_dir = exe_dir .parent() .ok_or("Failed to get parent directory of exe")?; - // Try multiple possible locations let candidates = vec![ - exe_dir.join("rustdesk.exe"), // Bundled sidecar (Windows) - exe_dir.join("binaries").join("rustdesk.exe"), // Explicit binaries dir - exe_dir.join("rustdesk"), // Bundled sidecar (Linux/macOS) - exe_dir.join("binaries").join("rustdesk"), // Explicit binaries dir (Linux/macOS) + exe_dir.join("rustdesk.exe"), + exe_dir.join("binaries").join("rustdesk.exe"), + exe_dir.join("rustdesk"), + exe_dir.join("binaries").join("rustdesk"), ]; for candidate in &candidates { @@ -53,9 +50,10 @@ fn get_rustdesk_path() -> Result { )) } -/// Start the RustDesk sidecar process +/// Start the RustDesk server process in background (--server mode) +/// This enables the machine to receive incoming connections #[tauri::command] -async fn start_rustdesk( +async fn start_rustdesk_server( state: tauri::State<'_, RustDeskState>, app: tauri::AppHandle, ) -> Result { @@ -64,10 +62,8 @@ async fn start_rustdesk( // Already running? if let Some(ref child) = *proc { if let Ok(Some(_)) = child.try_wait() { - // Process exited, clean up *proc = None; } else { - // Still running return Ok(RustDeskStatus { running: true, pid: Some(child.id()), @@ -77,29 +73,29 @@ async fn start_rustdesk( let path = get_rustdesk_path()?; + // Start RustDesk in server mode (listens on 21115-21119) #[cfg(target_os = "windows")] const CREATE_NO_WINDOW: u32 = 0x08000000; #[cfg(target_os = "windows")] let child = Command::new(&path) - .arg("--connect") + .arg("--server") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .creation_flags(CREATE_NO_WINDOW) .spawn() - .map_err(|e| format!("Failed to start RustDesk: {}", e))?; + .map_err(|e| format!("Failed to start RustDesk server: {}", e))?; #[cfg(not(target_os = "windows"))] let child = Command::new(&path) - .arg("--connect") + .arg("--server") .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .map_err(|e| format!("Failed to start RustDesk: {}", e))?; + .map_err(|e| format!("Failed to start RustDesk server: {}", e))?; let pid = child.id(); *proc = Some(child); - // Emit event to frontend app.emit("rustdesk-status", RustDeskStatus { running: true, pid: Some(pid), @@ -112,9 +108,9 @@ async fn start_rustdesk( }) } -/// Stop the RustDesk sidecar process +/// Stop the RustDesk server process #[tauri::command] -async fn stop_rustdesk( +async fn stop_rustdesk_server( state: tauri::State<'_, RustDeskState>, app: tauri::AppHandle, ) -> Result { @@ -136,7 +132,7 @@ async fn stop_rustdesk( Ok(status) } -/// Get the current status of the RustDesk sidecar +/// Get the current server status #[tauri::command] async fn get_rustdesk_status( state: tauri::State<'_, RustDeskState>, @@ -145,22 +141,18 @@ async fn get_rustdesk_status( if let Some(ref mut child) = *proc { match child.try_wait() { - Ok(Some(_status)) => { - // Process has exited + Ok(Some(_)) => { *proc = None; Ok(RustDeskStatus { running: false, pid: None, }) } - Ok(None) => { - // Still running - Ok(RustDeskStatus { - running: true, - pid: Some(child.id()), - }) - } - Err(e) => Err(format!("Failed to check process status: {}", e)), + Ok(None) => Ok(RustDeskStatus { + running: true, + pid: Some(child.id()), + }), + Err(e) => Err(format!("Failed to check process: {}", e)), } } else { Ok(RustDeskStatus { @@ -170,63 +162,55 @@ async fn get_rustdesk_status( } } -/// Connect to a remote peer via RustDesk +/// Open the RustDesk web client in a new Tauri webview window. +/// The web client is the Flutter web build embedded in the app. +/// It connects to the local RustDesk server via WebSocket. #[tauri::command] -async fn connect_to_peer( - state: tauri::State<'_, RustDeskState>, +async fn open_rustdesk_web( app: tauri::AppHandle, - peer_id: String, -) -> Result { - // Stop any existing instance - { - let mut proc = state.process.lock().map_err(|e| e.to_string())?; - if let Some(mut child) = proc.take() { - let _ = child.kill(); - let _ = child.wait(); + server_url: Option, +) -> Result<(), String> { + // The RustDesk web client is served from the embedded rustdesk-web/ assets. + // In dev: looks at ../rustdesk-web/index.html + // In prod: bundled as Tauri resource + let web_path = std::env::current_dir() + .unwrap_or_default() + .parent() + .map(|p| p.join("rustdesk-web").join("index.html")) + .filter(|p| p.exists()); + + let url = if let Some(path) = web_path { + let path_str = path.to_string_lossy().to_string(); + // On Windows, convert to file:// URL + #[cfg(target_os = "windows")] + let url = format!("file:///{0}", path_str.replace('\\', "/")); + #[cfg(not(target_os = "windows"))] + let url = format!("file://{0}", path_str); + + // Append server URL as query param if provided + if let Some(ref srv) = server_url { + format!("{}?server={}", url, srv) + } else { + url } - } - - let path = get_rustdesk_path()?; - - #[cfg(target_os = "windows")] - const CREATE_NO_WINDOW: u32 = 0x08000000; - #[cfg(target_os = "windows")] - let child = Command::new(&path) - .arg("--connect") - .arg(&peer_id) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .creation_flags(CREATE_NO_WINDOW) - .spawn() - .map_err(|e| format!("Failed to start RustDesk connection: {}", e))?; - - #[cfg(not(target_os = "windows"))] - let child = Command::new(&path) - .arg("--connect") - .arg(&peer_id) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|e| format!("Failed to start RustDesk connection: {}", e))?; - - let pid = child.id(); - { - let mut proc = state.process.lock().map_err(|e| e.to_string())?; - *proc = Some(child); - } - - let status = RustDeskStatus { - running: true, - pid: Some(pid), + } else { + // Fallback: try loading from Tauri asset protocol + // This requires the web client to be bundled via tauri.conf.json resources + "https://web.rustdesk.com".to_string() }; - app.emit("rustdesk-connected", serde_json::json!({ - "peer_id": peer_id, - "pid": pid, - })) - .map_err(|e| e.to_string())?; + WebviewWindowBuilder::new( + &app, + "rustdesk-web", + tauri::WebviewUrl::External(url.parse().map_err(|e| format!("Invalid URL: {}", e))?), + ) + .title("RustDesk - Remote Desktop") + .fullscreen(true) + .inner_size(1280.0, 720.0) + .build() + .map_err(|e| format!("Failed to create webview: {}", e))?; - Ok(status) + Ok(()) } fn main() { @@ -235,10 +219,10 @@ fn main() { process: Mutex::new(None), }) .invoke_handler(tauri::generate_handler![ - start_rustdesk, - stop_rustdesk, + start_rustdesk_server, + stop_rustdesk_server, get_rustdesk_status, - connect_to_peer, + open_rustdesk_web, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/shelled/shelled-os-ui/src-tauri/tauri.conf.json b/shelled/shelled-os-ui/src-tauri/tauri.conf.json index a510126..d365dd3 100644 --- a/shelled/shelled-os-ui/src-tauri/tauri.conf.json +++ b/shelled/shelled-os-ui/src-tauri/tauri.conf.json @@ -18,13 +18,5 @@ "security": { "csp": null } - }, - "bundle": { - "externalBin": [ - "binaries/rustdesk" - ], - "resources": [ - "binaries/*" - ] } } diff --git a/shelled/shelled-os-ui/src/css/remote-desktop.css b/shelled/shelled-os-ui/src/css/remote-desktop.css index d6541ca..4477242 100644 --- a/shelled/shelled-os-ui/src/css/remote-desktop.css +++ b/shelled/shelled-os-ui/src/css/remote-desktop.css @@ -1,13 +1,85 @@ /* Remote Desktop Component Styles */ #remote-desktop { - left: 260px; /* Offset from remote desktop icon */ + left: 200px; width: 520px; - height: 560px; + height: 500px; z-index: 95; } -/* Connection Section */ +/* Server Control Section */ +.rd-server-control { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.rd-server-info { + display: flex; + align-items: center; + gap: 12px; + flex: 1; +} + +.rd-server-icon { + font-size: 28px; + color: var(--accent-cyan); +} + +.rd-server-details { + display: flex; + flex-direction: column; +} + +.rd-server-label { + font-size: 0.95rem; + font-weight: 500; + color: var(--bg-white); +} + +.rd-server-ports { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.5); + font-family: monospace; +} + +/* Buttons */ +.rd-btn-server { + background: linear-gradient(135deg, #11998e, #38ef7d); + white-space: nowrap; + flex-shrink: 0; +} + +.rd-btn-server.rd-toggle-active { + background: linear-gradient(135deg, #e52e00, #ff6b35); +} + +.rd-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 10px 18px; + border: none; + border-radius: 8px; + font-family: 'Outfit', sans-serif; + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + transition: transform 0.2s ease, background 0.2s ease, opacity 0.2s ease; + color: white; +} + +.rd-btn:hover { + transform: scale(1.03); +} + +.rd-btn:active { + transform: scale(0.98); +} + +/* Connection/Launch Section */ .rd-connection-section { padding: 16px 20px; border-bottom: 1px solid var(--glass-border); @@ -22,6 +94,18 @@ letter-spacing: 0.5px; } +.rd-launch-section { + display: flex; + flex-direction: column; + gap: 12px; +} + +.rd-launch-desc { + font-size: 0.82rem; + color: rgba(255, 255, 255, 0.5); + line-height: 1.4; +} + .rd-input-row { display: flex; gap: 8px; @@ -50,45 +134,6 @@ color: rgba(255, 255, 255, 0.35); } -.rd-btn { - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 10px 18px; - border: none; - border-radius: 8px; - font-family: 'Outfit', sans-serif; - font-size: 0.9rem; - font-weight: 500; - cursor: pointer; - transition: transform 0.2s ease, background 0.2s ease, opacity 0.2s ease; - color: white; -} - -.rd-btn:hover { - transform: scale(1.03); -} - -.rd-btn:active { - transform: scale(0.98); -} - -.rd-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -#rd-connect-btn { - background: linear-gradient(135deg, #0078D7, #00BCF2); -} - -#rd-disconnect-btn { - background: linear-gradient(135deg, #e52e00, #ff6b35); - display: none; -} - /* Status Bar */ .rd-status-bar { display: flex; @@ -137,102 +182,6 @@ color: rgba(255, 255, 255, 0.8); } -/* Recent Connections */ -.rd-recent-section { - flex-grow: 1; - overflow-y: auto; - padding: 12px 16px; -} - -.rd-recent-section h3 { - font-size: 0.85rem; - font-weight: 500; - color: rgba(255, 255, 255, 0.6); - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.rd-empty-recent { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - padding: 32px 20px; - color: rgba(255, 255, 255, 0.3); - font-size: 0.85rem; -} - -.rd-empty-recent .material-icons { - font-size: 32px; -} - -.rd-recent-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 12px; - border-radius: 8px; - cursor: pointer; - transition: background 0.2s ease, transform 0.15s ease; - margin-bottom: 4px; -} - -.rd-recent-item:hover { - background: rgba(255, 255, 255, 0.1); - transform: translateX(3px); -} - -.rd-recent-info { - display: flex; - align-items: center; - gap: 12px; -} - -.rd-recent-icon { - font-size: 22px; - color: var(--accent-cyan); -} - -.rd-recent-details { - display: flex; - flex-direction: column; -} - -.rd-recent-id { - font-size: 0.95rem; - font-weight: 500; - color: var(--bg-white); -} - -.rd-recent-label { - font-size: 0.75rem; - color: rgba(255, 255, 255, 0.5); -} - -.rd-recent-connect { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.1); - border: none; - color: var(--bg-white); - cursor: pointer; - transition: background 0.2s ease, transform 0.2s ease; -} - -.rd-recent-connect:hover { - background: var(--accent-cyan); - transform: scale(1.1); -} - -.rd-recent-connect .material-icons { - font-size: 18px; -} - /* Quick Actions */ .rd-actions { display: flex; @@ -266,13 +215,3 @@ .rd-action-btn .material-icons { font-size: 16px; } - -/* Scrollbar */ -.rd-recent-section::-webkit-scrollbar { - width: 6px; -} - -.rd-recent-section::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); - border-radius: 4px; -} diff --git a/shelled/shelled-os-ui/src/index.html b/shelled/shelled-os-ui/src/index.html index 7531db0..32196b1 100644 --- a/shelled/shelled-os-ui/src/index.html +++ b/shelled/shelled-os-ui/src/index.html @@ -78,18 +78,20 @@ - +
-

Connect to Remote Machine

-
- - -
@@ -97,14 +99,21 @@
- Not connected + Server not running
- -
-

Recent Connections

-
- + +
+

Remote Desktop Client

+
+

Open the RustDesk web client to connect to remote machines. The web client connects to the server via WebSocket.

+
+ + +
diff --git a/shelled/shelled-os-ui/src/js/components/RemoteDesktop.js b/shelled/shelled-os-ui/src/js/components/RemoteDesktop.js index 67bc40c..97b1268 100644 --- a/shelled/shelled-os-ui/src/js/components/RemoteDesktop.js +++ b/shelled/shelled-os-ui/src/js/components/RemoteDesktop.js @@ -3,16 +3,13 @@ export default class RemoteDesktop { this.os = os; this.element = document.getElementById('remote-desktop'); this.closeBtn = this.element.querySelector('.close-btn'); - this.connectBtn = this.element.querySelector('#rd-connect-btn'); - this.disconnectBtn = this.element.querySelector('#rd-disconnect-btn'); - this.peerIdInput = this.element.querySelector('#rd-peer-id'); + this.launchBtn = this.element.querySelector('#rd-launch-btn'); + this.serverToggle = this.element.querySelector('#rd-server-toggle'); this.statusIndicator = this.element.querySelector('#rd-status'); this.statusText = this.element.querySelector('#rd-status-text'); - this.connectionList = this.element.querySelector('#rd-connection-list'); + this.serverUrlInput = this.element.querySelector('#rd-server-url'); - this.isConnected = false; - this.currentPeerId = null; - this.recentConnections = []; + this.serverRunning = false; this.initialize(); } @@ -23,125 +20,96 @@ export default class RemoteDesktop { this.os.closeAllPopups(); }); - this.connectBtn.addEventListener('click', (e) => { + // Launch web client button + this.launchBtn.addEventListener('click', (e) => { e.stopPropagation(); - this.connectToPeer(); + this.openWebClient(); }); - this.disconnectBtn.addEventListener('click', (e) => { + // Server toggle + this.serverToggle.addEventListener('click', (e) => { e.stopPropagation(); - this.disconnect(); + this.toggleServer(); }); - this.peerIdInput.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - e.stopPropagation(); - this.connectToPeer(); - } - }); - - // Listen for status updates from Tauri backend + // Listen for server status events from Tauri this.listenForEvents(); } async listenForEvents() { if (window.__TAURI__) { - const { listen } = await import('@tauri-apps/api/event'); - - await listen('rustdesk-status', (event) => { - const { running, pid } = event.payload; - this.updateStatus(running, pid); - }); - - await listen('rustdesk-connected', (event) => { - const { peer_id, pid } = event.payload; - this.onConnected(peer_id, pid); - }); + try { + const { listen } = await import('@tauri-apps/api/event'); + await listen('rustdesk-status', (event) => { + const { running, pid } = event.payload; + this.updateServerStatus(running, pid); + }); + } catch (e) { + console.warn('Tauri events not available:', e); + } } } - async connectToPeer() { - const peerId = this.peerIdInput.value.trim(); - if (!peerId) { - this.setStatus('error', 'Please enter a peer ID'); + async toggleServer() { + if (!window.__TAURI__) { + this.setStatus('error', 'Requires Tauri runtime'); return; } - this.setStatus('connecting', `Connecting to ${peerId}...`); - this.connectBtn.disabled = true; - try { - if (window.__TAURI__) { - const { invoke } = await import('@tauri-apps/api/core'); - const result = await invoke('connect_to_peer', { peerId }); + const { invoke } = await import('@tauri-apps/api/core'); - if (result.running) { - this.currentPeerId = peerId; - this.addToRecent(peerId); - this.onConnected(peerId, result.pid); - } else { - this.setStatus('error', 'Failed to start RustDesk'); - this.connectBtn.disabled = false; - } + if (this.serverRunning) { + this.setStatus('connecting', 'Stopping server...'); + const result = await invoke('stop_rustdesk_server'); + this.updateServerStatus(result.running, result.pid); } else { - // Fallback for non-Tauri environments - this.setStatus('error', 'RustDesk requires Tauri runtime'); - this.connectBtn.disabled = false; + this.setStatus('connecting', 'Starting server...'); + const result = await invoke('start_rustdesk_server'); + this.updateServerStatus(result.running, result.pid); } } catch (err) { - console.error('RustDesk connection error:', err); - this.setStatus('error', `Connection failed: ${err}`); - this.connectBtn.disabled = false; + console.error('Server toggle error:', err); + this.setStatus('error', `Failed: ${err}`); + this.serverRunning = false; + this.serverToggle.classList.remove('rd-toggle-active'); + this.serverToggle.textContent = 'Start Server'; } } - async disconnect() { + async openWebClient() { + this.setStatus('connecting', 'Launching web client...'); + try { if (window.__TAURI__) { const { invoke } = await import('@tauri-apps/api/core'); - await invoke('stop_rustdesk'); + const serverUrl = this.serverUrlInput?.value?.trim() || null; + await invoke('open_rustdesk_web', { serverUrl }); + this.setStatus('connected', 'Web client opened'); + } else { + // Dev fallback: open in new browser tab + const server = this.serverUrlInput?.value?.trim() || 'localhost'; + window.open('https://web.rustdesk.com', '_blank'); + this.setStatus('connected', 'Opened in browser'); } } catch (err) { - console.error('RustDesk stop error:', err); + console.error('Failed to open web client:', err); + this.setStatus('error', `Failed to launch: ${err}`); } - - this.isConnected = false; - this.currentPeerId = null; - this.setStatus('disconnected', 'Disconnected'); - this.connectBtn.disabled = false; } - async startRustdesk() { - try { - if (window.__TAURI__) { - const { invoke } = await import('@tauri-apps/api/core'); - const result = await invoke('start_rustdesk'); - this.updateStatus(result.running, result.pid); - return result; - } - } catch (err) { - console.error('RustDesk start error:', err); - this.setStatus('error', `Failed to start: ${err}`); - } - return null; - } + updateServerStatus(running, pid) { + this.serverRunning = running; + const toggle = this.serverToggle; - onConnected(peerId, pid) { - this.isConnected = true; - this.currentPeerId = peerId; - this.setStatus('connected', `Connected to ${peerId} (PID: ${pid})`); - this.connectBtn.disabled = true; - } - - updateStatus(running, pid) { if (running) { - this.setStatus('connected', `RustDesk running (PID: ${pid})`); - this.isConnected = true; + this.setStatus('connected', `Server running (PID: ${pid})`); + toggle.classList.add('rd-toggle-active'); + toggle.textContent = 'Stop Server'; } else { - this.setStatus('disconnected', 'RustDesk not running'); - this.isConnected = false; - this.currentPeerId = null; - this.connectBtn.disabled = false; + this.setStatus('disconnected', 'Server stopped'); + toggle.classList.remove('rd-toggle-active'); + toggle.textContent = 'Start Server'; } } @@ -149,84 +117,21 @@ export default class RemoteDesktop { this.statusIndicator.className = 'rd-status-indicator'; this.statusIndicator.classList.add(`rd-status-${state}`); this.statusText.textContent = text; - - if (state === 'connected') { - this.disconnectBtn.style.display = 'flex'; - this.connectBtn.style.display = 'none'; - } else { - this.disconnectBtn.style.display = 'none'; - this.connectBtn.style.display = 'flex'; - } } - addToRecent(peerId) { - // Remove duplicate if exists - this.recentConnections = this.recentConnections.filter(id => id !== peerId); - // Add to front - this.recentConnections.unshift(peerId); - // Keep only last 5 - if (this.recentConnections.length > 5) { - this.recentConnections = this.recentConnections.slice(0, 5); - } - this.renderRecentConnections(); - } - - renderRecentConnections() { - this.connectionList.innerHTML = ''; - - if (this.recentConnections.length === 0) { - this.connectionList.innerHTML = ` -
- history - No recent connections -
- `; - return; - } - - const fragment = document.createDocumentFragment(); - - this.recentConnections.forEach(peerId => { - const item = document.createElement('div'); - item.className = 'rd-recent-item'; - item.innerHTML = ` -
- computer -
- ${peerId} - Remote Desktop -
-
- - `; - - const connectBtn = item.querySelector('.rd-recent-connect'); - connectBtn.addEventListener('click', (e) => { - e.stopPropagation(); - this.peerIdInput.value = peerId; - this.connectToPeer(); - }); - - fragment.appendChild(item); - }); - - this.connectionList.appendChild(fragment); - } - - open() { + async open() { this.element.classList.add('active'); this.os.components.taskbar.updateActiveButton('remoteDesktop'); - this.renderRecentConnections(); - // Check current status + // Check current server status if (window.__TAURI__) { - import('@tauri-apps/api/core').then(({ invoke }) => { - invoke('get_rustdesk_status').then(result => { - this.updateStatus(result.running, result.pid); - }).catch(() => {}); - }); + try { + const { invoke } = await import('@tauri-apps/api/core'); + const result = await invoke('get_rustdesk_status'); + this.updateServerStatus(result.running, result.pid); + } catch (e) { + // Ignore - not running in Tauri + } } }