fix(rdp): proper display scaling via Guacamole display.scale()

Remove CSS width/height !important override that broke Guacamole's
internal rendering pipeline. Replace with display.scale() auto-fitting
using ResizeObserver for responsive container sizing. Scale mouse
coordinates back to remote display space to keep input accurate.
Clean up diagnostic instruction logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-14 12:20:21 -04:00
parent f9070c81f3
commit 95271f065a
2 changed files with 38 additions and 27 deletions

View File

@ -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;
}

View File

@ -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()