diff --git a/frontend/components/rdp/RdpCanvas.vue b/frontend/components/rdp/RdpCanvas.vue index 58e0f9c..4ba0cbc 100644 --- a/frontend/components/rdp/RdpCanvas.vue +++ b/frontend/components/rdp/RdpCanvas.vue @@ -56,12 +56,8 @@ onBeforeUnmount(() => { @apply absolute inset-0 bg-gray-950 overflow-hidden cursor-default; } -/* Guacamole appends a display div; make it fill the container */ -.rdp-canvas-container :deep(> div) { - width: 100% !important; - height: 100% !important; -} - +/* Guacamole manages its own display element sizing via display.scale(). + Do NOT override width/height — it breaks the internal rendering pipeline. */ .rdp-canvas-container :deep(canvas) { display: block; } diff --git a/frontend/composables/useRdp.ts b/frontend/composables/useRdp.ts index 74d224f..f18fc6b 100644 --- a/frontend/composables/useRdp.ts +++ b/frontend/composables/useRdp.ts @@ -17,7 +17,6 @@ function createJsonWsTunnel(wsUrl: string, connectMsg: object) { let onDisconnected: ((reason: string) => void) | null = null let onGatewayError: ((message: string) => void) | null = null - let instructionCount = 0 function dispatchInstructions(raw: string) { if (!tunnel.oninstruction) return let remaining = raw @@ -38,17 +37,6 @@ function createJsonWsTunnel(wsUrl: string, connectMsg: object) { pos = dotIdx + 1 + len + 1 } if (parts.length > 0) { - instructionCount++ - const opcode = parts[0] - // Log first 50 instructions, then every 200th, plus any layer-0 draw ops - const isLayer0Draw = (opcode === 'img' || opcode === 'rect' || opcode === 'cfill' || opcode === 'copy' || opcode === 'transfer') - && parts[1] !== undefined && parseInt(parts[1]) === 0 - if (instructionCount <= 50 || instructionCount % 200 === 0 || isLayer0Draw) { - const argSummary = opcode === 'blob' - ? `[stream=${parts[1]}, ${(parts[2] || '').length} bytes]` - : parts.slice(1).join(',') - console.log(`[RDP] Instruction #${instructionCount}: ${opcode}(${argSummary})`) - } tunnel.oninstruction(parts[0], parts.slice(1)) } } @@ -202,22 +190,48 @@ export function useRdp() { console.log(`[RDP] State: ${states[state] || state}`) } - // Log when display is ready - client.onready = () => { - const display = client.getDisplay() - console.log(`[RDP] Ready! Display: ${display.getWidth()}x${display.getHeight()}, element: ${displayEl.offsetWidth}x${displayEl.offsetHeight}, container: ${container.offsetWidth}x${container.offsetHeight}`) - } - // Attach Guacamole display element to container - const displayEl = client.getDisplay().getElement() + const display = client.getDisplay() + const displayEl = display.getElement() container.appendChild(displayEl) - // Mouse input + // Auto-scale the Guacamole display to fit the container + function fitDisplay() { + const dw = display.getWidth() + const dh = display.getHeight() + if (!dw || !dh) return + const scale = Math.min(container.clientWidth / dw, container.clientHeight / dh) + display.scale(scale) + } + + // Re-fit when guacd sends a sync (display dimensions may have changed) + const origOnSync = display.onresize + display.onresize = (w: number, h: number) => { + origOnSync?.call(display, w, h) + fitDisplay() + } + + // Re-fit when the browser container resizes + const resizeObserver = new ResizeObserver(() => fitDisplay()) + resizeObserver.observe(container) + + // Mouse input — bind to the display element const mouse = new Guacamole.Mouse(displayEl) mouse.onEach( ['mousedown', 'mousemove', 'mouseup'], (e: Guacamole.Mouse.Event) => { - client.sendMouseState(e.state) + // Scale mouse coordinates back to remote display space + const scale = display.getScale() + const scaledState = new Guacamole.Mouse.State( + e.state.x / scale, + e.state.y / scale, + e.state.left, + e.state.middle, + e.state.right, + e.state.up, + e.state.down, + ) + client.sendMouseState(scaledState) }, ) @@ -230,6 +244,7 @@ export function useRdp() { client.connect() function disconnect() { + resizeObserver.disconnect() keyboard.onkeydown = null keyboard.onkeyup = null client.disconnect()