Wire mouse + keyboard input for RDP sessions (#47)

The multi-session RemoteDesktop view rendered the remote display but never
captured input — no Guacamole.Mouse / Guacamole.Keyboard were created — so the
desktop showed but the mouse and keyboard did nothing.

Add per-session input:
- Guacamole.Mouse on the display element, forwarding mouse state via
  client.sendMouseState with coordinates divided by the current display scale so
  clicks land correctly on the scaled-down canvas.
- Guacamole.Keyboard on document, forwarding key events via client.sendKeyEvent,
  but only while that session is the active/visible tab so a background session
  can't steal keystrokes.
- Detach keyboard handlers on session close and on unmount.

Co-authored-by: Samuel James <ssamjame@amazon.com>
Co-authored-by: Kiro <noreply@kiro.dev>
This commit is contained in:
Samuel James 2026-06-22 16:00:09 -04:00 committed by GitHub
parent 5f27943974
commit 8ccc959dc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -18,6 +18,8 @@ interface SessionHandle {
client: any
container: HTMLDivElement
display: any
mouse: any
keyboard: any
}
// Scale the Guacamole display to fit its visible container. The display canvas
@ -49,7 +51,13 @@ export default function RemoteDesktop() {
useEffect(() => {
return () => {
handlesRef.current.forEach(({ client }) => client.disconnect())
handlesRef.current.forEach(({ client, keyboard }) => {
if (keyboard) {
keyboard.onkeydown = null
keyboard.onkeyup = null
}
client.disconnect()
})
}
}, [])
@ -79,7 +87,38 @@ export default function RemoteDesktop() {
const tunnel = new Guacamole.WebSocketTunnel(`${proto}://${window.location.host}/api/guacamole`)
const client = new Guacamole.Client(tunnel)
const display = client.getDisplay()
handlesRef.current.set(sessionId, { client, container, display })
// --- Input wiring (mouse + keyboard) ---
// Mouse events are captured on the display element. Coordinates must be
// divided by the current display scale so clicks land where the user sees
// them (the canvas is scaled down to fit the panel).
const mouse = new Guacamole.Mouse(display.getElement())
const sendMouse = (mouseState: any) => {
const scale = display.getScale() || 1
const scaled = new Guacamole.Mouse.State(
mouseState.x / scale,
mouseState.y / scale,
mouseState.left,
mouseState.middle,
mouseState.right,
mouseState.up,
mouseState.down,
)
client.sendMouseState(scaled)
}
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = sendMouse
// Keyboard is captured on the document but only forwarded while THIS session
// is the active/visible one (otherwise a background session would steal keys).
const keyboard = new Guacamole.Keyboard(document)
keyboard.onkeydown = (keysym: number) => {
if (activeSessionIdRef.current === sessionId) client.sendKeyEvent(1, keysym)
}
keyboard.onkeyup = (keysym: number) => {
if (activeSessionIdRef.current === sessionId) client.sendKeyEvent(0, keysym)
}
handlesRef.current.set(sessionId, { client, container, display, mouse, keyboard })
client.onerror = (err: { message?: string }) => {
patchSession(sessionId, { status: 'error', errorMessage: err?.message ?? 'Connection failed' })
@ -99,6 +138,8 @@ export default function RemoteDesktop() {
function closeSession(sessionId: string) {
const handle = handlesRef.current.get(sessionId)
handle?.keyboard?.onkeydown && (handle.keyboard.onkeydown = null)
handle?.keyboard?.onkeyup && (handle.keyboard.onkeyup = null)
handle?.client.disconnect()
handle?.container.remove()
handlesRef.current.delete(sessionId)