diff --git a/web/app/servers/[id]/console/page.tsx b/web/app/servers/[id]/console/page.tsx index 05cfa75..845ed9c 100644 --- a/web/app/servers/[id]/console/page.tsx +++ b/web/app/servers/[id]/console/page.tsx @@ -27,6 +27,8 @@ export default function ServerConsolePage() { const [connected, setConnected] = useState(false); const [error, setError] = useState(null); const [pending, setPending] = useState<{ token: string; wsPath: string } | null>(null); + const [zoom, setZoom] = useState(1); + const dprRef = useRef(1); // Inject the vendored Guacamole client script once. useEffect(() => { @@ -104,6 +106,7 @@ export default function ServerConsolePage() { const wsUrl = `${wsProto}://${location.host}${pending.wsPath}`; const rect = containerRef.current.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; + dprRef.current = dpr; // Request the remote at device-pixel resolution with a fixed 96 dpi, then // scale the display back down by dpr. Folding dpr into `dpi` instead makes // the remote enlarge everything, which reads as a zoomed-in view. @@ -114,12 +117,15 @@ export default function ServerConsolePage() { `&dpi=96`; connectionRef.current = openConsole(containerRef.current, wsUrl, connectData); - if (dpr !== 1) { - connectionRef.current.setScale(1 / dpr); - } + connectionRef.current.setScale(zoom / dpr); setPending(null); }, [connected, pending]); + // Apply zoom changes live without reconnecting. + useEffect(() => { + connectionRef.current?.setScale(zoom / dprRef.current); + }, [zoom]); + function handleDisconnect() { connectionRef.current?.disconnect(); connectionRef.current = null; @@ -252,12 +258,25 @@ export default function ServerConsolePage() { + + )}
); diff --git a/web/lib/guacConsole.ts b/web/lib/guacConsole.ts index e5c1104..e118656 100644 --- a/web/lib/guacConsole.ts +++ b/web/lib/guacConsole.ts @@ -17,10 +17,26 @@ export function openConsole( client.connect(connectData); - // Wire keyboard + mouse. - const mouse = new Guacamole.Mouse(client.getDisplay().getElement()); - mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = (state: any) => - client.sendMouseState(state); + const display = client.getDisplay(); + let scale = 1; + + // Wire keyboard + mouse. The display element is rendered at `scale` of the + // remote's native resolution, but Guacamole.Mouse reports coordinates in + // element (on-screen) pixels. Divide by scale to map back to remote + // coordinates, otherwise the cursor is offset. + const mouse = new Guacamole.Mouse(display.getElement()); + mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = (state: any) => { + const s = new Guacamole.Mouse.State( + state.x / scale, + state.y / scale, + state.left, + state.middle, + state.right, + state.up, + state.down + ); + client.sendMouseState(s); + }; const keyboard = new Guacamole.Keyboard(document); keyboard.onkeydown = (k: number) => client.sendKeyEvent(1, k); keyboard.onkeyup = (k: number) => client.sendKeyEvent(0, k); @@ -29,8 +45,9 @@ export function openConsole( disconnect() { client.disconnect(); }, - setScale(scale: number) { - client.getDisplay().scale(scale); + setScale(s: number) { + scale = s; + display.scale(s); }, }; }