// Thin wrapper over the vendored guacamole-common-js client. // The library attaches a global `Guacamole` object when loaded. declare const Guacamole: any; export function openConsole( container: HTMLElement, wsUrl: string, connectData = "" ): { disconnect: () => void; setScale: (scale: number) => void; resize: (width: number, height: number) => void } { // Guacamole's WebSocketTunnel builds the socket URL as `wsUrl + "?" + data`, // so wsUrl must NOT already contain a query string — pass params via connectData. const tunnel = new Guacamole.WebSocketTunnel(wsUrl); const client = new Guacamole.Client(tunnel); container.innerHTML = ""; container.appendChild(client.getDisplay().getElement()); // Make the console focusable so keyboard capture is scoped to it (see below). container.tabIndex = 0; client.connect(connectData); 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); }; // Scope keyboard capture to the container rather than `document`, so it only // grabs keys while the console is focused and stops entirely once the element // is removed (navigating away / disconnect). Attaching to `document` leaks the // capture and swallows keystrokes in unrelated inputs. const keyboard = new Guacamole.Keyboard(container); keyboard.onkeydown = (k: number) => client.sendKeyEvent(1, k); keyboard.onkeyup = (k: number) => client.sendKeyEvent(0, k); // Guacamole.Mouse consumes the native mousedown, so clicking the console never // moves DOM focus back to it. Refocus explicitly so keyboard capture resumes. const refocus = () => container.focus(); container.addEventListener("mousedown", refocus); container.focus(); return { disconnect() { container.removeEventListener("mousedown", refocus); keyboard.onkeydown = null; keyboard.onkeyup = null; if (typeof keyboard.reset === "function") keyboard.reset(); client.disconnect(); }, setScale(s: number) { scale = s; display.scale(s); }, resize(width: number, height: number) { client.sendSize(width, height); }, }; }