diff --git a/backend/src/rdp/guacamole.service.ts b/backend/src/rdp/guacamole.service.ts index 5013997..0fe1769 100644 --- a/backend/src/rdp/guacamole.service.ts +++ b/backend/src/rdp/guacamole.service.ts @@ -64,11 +64,31 @@ export class GuacamoleService { } this.logger.log(`guacd expects ${argNames.length} args: ${argNames.join(', ')}`); - // Phase 2: CONNECT — send values in the exact order guacd expects + // Phase 2: Client capability instructions — MUST be sent before CONNECT. + // The Guacamole protocol requires: size, audio, video, image, timezone + // between receiving 'args' and sending 'connect'. Without these, guacd + // sees 0x0 resolution and immediately kills the connection. + const width = String(params.width); + const height = String(params.height); + const dpi = String(params.dpi || 96); + socket.write(this.encode('size', width, height, dpi)); + socket.write(this.encode('audio')); + socket.write(this.encode('video')); + socket.write(this.encode('image', 'image/png', 'image/jpeg', 'image/webp')); + + let tz: string; + try { + tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch { + tz = 'UTC'; + } + socket.write(this.encode('timezone', tz)); + + // Phase 3: CONNECT — send values in the exact order guacd expects const connectInstruction = this.buildConnectInstruction(params, argNames); this.logger.log( `Sending CONNECT: host=${params.hostname}:${params.port} user=${params.username} domain=${params.domain || '(none)'} ` + - `security=${params.security || 'any'} size=${params.width}x${params.height} ignoreCert=${params.ignoreCert !== false}`, + `security=${params.security || 'any'} size=${width}x${height}@${dpi}dpi ignoreCert=${params.ignoreCert !== false}`, ); socket.write(connectInstruction);