diff --git a/docs/screenshots/wraith-final.png b/docs/screenshots/wraith-final.png deleted file mode 100644 index 674a7d7..0000000 Binary files a/docs/screenshots/wraith-final.png and /dev/null differ diff --git a/frontend/src/components/common/SettingsModal.vue b/frontend/src/components/common/SettingsModal.vue index e309797..7a44ebe 100644 --- a/frontend/src/components/common/SettingsModal.vue +++ b/frontend/src/components/common/SettingsModal.vue @@ -268,8 +268,11 @@ diff --git a/frontend/src/composables/useRdp.ts b/frontend/src/composables/useRdp.ts index 17a2a74..4e8a47b 100644 --- a/frontend/src/composables/useRdp.ts +++ b/frontend/src/composables/useRdp.ts @@ -1,4 +1,7 @@ import { ref, onBeforeUnmount } from "vue"; +import { Call } from "@wailsio/runtime"; + +const APP = "github.com/vstockwell/wraith/internal/app.WraithApp"; /** * RDP mouse event flags — match the Go constants in internal/rdp/input.go @@ -199,71 +202,50 @@ export function useRdp(): UseRdpReturn { let frameCount = 0; /** - * Fetch the current frame from the backend. - * TODO: Replace with Wails binding — RDPService.GetFrame(sessionId) - * Mock: generates a gradient test pattern. + * Fetch the current frame from the Go RDP backend. + * + * Go's GetFrame returns []byte (raw RGBA, Width*Height*4 bytes). + * Wails serialises Go []byte as a base64-encoded string over the JSON bridge, + * so we decode it back to a Uint8ClampedArray and wrap it in an ImageData. */ async function fetchFrame( sessionId: string, width = 1920, height = 1080, ): Promise { - void sessionId; - - // Mock: generate a test frame with animated gradient - const imageData = new ImageData(width, height); - const data = imageData.data; - const t = Date.now() / 1000; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const i = (y * width + x) * 4; - const nx = x / width; - const ny = y / height; - const diag = (nx + ny) / 2; - - data[i + 0] = Math.floor(20 + diag * 40); // R - data[i + 1] = Math.floor(25 + (1 - diag) * 30); // G - data[i + 2] = Math.floor(80 + diag * 100); // B - data[i + 3] = 255; // A - - // Grid lines every 100px - if (x % 100 === 0 || y % 100 === 0) { - data[i + 0] = Math.min(data[i + 0] + 20, 255); - data[i + 1] = Math.min(data[i + 1] + 20, 255); - data[i + 2] = Math.min(data[i + 2] + 20, 255); - } - } + let raw: string; + try { + raw = (await Call.ByName(`${APP}.RDPGetFrame`, sessionId)) as string; + } catch { + // Session may not be connected yet or backend returned an error — skip frame + return null; } - // Animated pulsing circle at center - const cx = width / 2; - const cy = height / 2; - const radius = 40 + 20 * Math.sin(t * 2); + if (!raw) return null; - for (let dy = -70; dy <= 70; dy++) { - for (let dx = -70; dx <= 70; dx++) { - const dist = Math.sqrt(dx * dx + dy * dy); - if (dist <= radius && dist >= radius - 4) { - const px = Math.floor(cx + dx); - const py = Math.floor(cy + dy); - if (px >= 0 && px < width && py >= 0 && py < height) { - const i = (py * width + px) * 4; - data[i + 0] = 88; - data[i + 1] = 166; - data[i + 2] = 255; - data[i + 3] = 255; - } - } - } + // Decode base64 → binary string → Uint8ClampedArray + const binaryStr = atob(raw); + const bytes = new Uint8ClampedArray(binaryStr.length); + for (let i = 0; i < binaryStr.length; i++) { + bytes[i] = binaryStr.charCodeAt(i); } - return imageData; + // Validate: RGBA requires exactly width * height * 4 bytes + const expected = width * height * 4; + if (bytes.length !== expected) { + console.warn( + `[useRdp] Frame size mismatch: got ${bytes.length}, expected ${expected}`, + ); + return null; + } + + return new ImageData(bytes, width, height); } /** - * Send a mouse event. - * TODO: Replace with Wails binding — RDPService.SendMouse(sessionId, x, y, flags) + * Send a mouse event to the remote session. + * Calls Go WraithApp.RDPSendMouse(sessionId, x, y, flags). + * Fire-and-forget — mouse events are best-effort. */ function sendMouse( sessionId: string, @@ -271,16 +253,17 @@ export function useRdp(): UseRdpReturn { y: number, flags: number, ): void { - void sessionId; - void x; - void y; - void flags; - // Mock: no-op — will call Wails binding when wired + Call.ByName(`${APP}.RDPSendMouse`, sessionId, x, y, flags).catch( + (err: unknown) => { + console.warn("[useRdp] sendMouse failed:", err); + }, + ); } /** - * Send a key event, mapping JS code to RDP scancode. - * TODO: Replace with Wails binding — RDPService.SendKey(sessionId, scancode, pressed) + * Send a key event, mapping the JS KeyboardEvent.code to an RDP scancode. + * Calls Go WraithApp.RDPSendKey(sessionId, scancode, pressed). + * Unmapped keys are silently dropped — not every JS key has an RDP scancode. */ function sendKey( sessionId: string, @@ -290,19 +273,23 @@ export function useRdp(): UseRdpReturn { const scancode = jsKeyToScancode(code); if (scancode === null) return; - void sessionId; - void pressed; - // Mock: no-op — will call Wails binding when wired + Call.ByName(`${APP}.RDPSendKey`, sessionId, scancode, pressed).catch( + (err: unknown) => { + console.warn("[useRdp] sendKey failed:", err); + }, + ); } /** - * Send clipboard text to the remote session. - * TODO: Replace with Wails binding — RDPService.SendClipboard(sessionId, text) + * Send clipboard text to the remote RDP session. + * Calls Go WraithApp.RDPSendClipboard(sessionId, text). */ function sendClipboard(sessionId: string, text: string): void { - void sessionId; - void text; - // Mock: no-op + Call.ByName(`${APP}.RDPSendClipboard`, sessionId, text).catch( + (err: unknown) => { + console.warn("[useRdp] sendClipboard failed:", err); + }, + ); } /** diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index 0b999a0..8d02373 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -212,6 +212,7 @@