// RDP: Windows nutzt das eingebaute mstsc.exe, Linux (Arch/CachyOS) FreeRDP (xfreerdp3 / xfreerdp / sdl-freerdp3 / wlfreerdp3). // Verbindungen laufen entweder eingebettet im MrTerm-Tab (siehe rdp-embed.js) oder im eigenen Fenster. const { spawn, execFile, execFileSync } = require('child_process'); const fs = require('fs'); const os = require('os'); const path = require('path'); const i18n = require('../i18n'); function which(bin) { try { execFileSync(process.platform === 'win32' ? 'where' : 'which', [bin], { stdio: 'ignore' }); return true; } catch { return false; } } // Eingebettet geht nur der X11-Client (unterstützt /parent-window) function linuxClient(pref, embedded) { if (embedded) return ['xfreerdp3', 'xfreerdp'].find(which); const candidates = pref && pref !== 'auto' ? [pref] : process.env.WAYLAND_DISPLAY ? ['sdl-freerdp3', 'wlfreerdp3', 'xfreerdp3', 'wlfreerdp', 'xfreerdp'] : ['xfreerdp3', 'sdl-freerdp3', 'xfreerdp']; return candidates.find(which); } function detect(settings) { if (process.platform === 'win32') return { available: true, client: 'mstsc', embedClient: 'mstsc' }; const c = linuxClient(settings.rdpClientLinux); const e = linuxClient(null, true); return c ? { available: true, client: c, embedClient: e || null } : { available: false, hint: 'sudo pacman -S freerdp' }; } const userOf = (host) => (host.domain ? `${host.domain}\\${host.username || ''}` : host.username || ''); // ---------- Windows: .rdp-Datei + temporäre Anmeldedaten function writeRdpFile(host, size) { const port = Number(host.port) || 3389; const target = port === 3389 ? host.address : `${host.address}:${port}`; const user = userOf(host); const w = size?.width || host.rdpWidth; const hgt = size?.height || host.rdpHeight; const lines = [ `full address:s:${target}`, user && `username:s:${user}`, `screen mode id:i:${!size && host.rdpFullscreen ? 2 : 1}`, w && `desktopwidth:i:${w}`, hgt && `desktopheight:i:${hgt}`, 'dynamic resolution:i:1', // Standard: Auflösung folgt der Fenstergröße (Windows 8.1 / Server 2012 R2 und neuer). "smart sizing" würde stattdessen nur skalieren. `smart sizing:i:${host.rdpResize === 'scale' ? 1 : 0}`, 'displayconnectionbar:i:0', `redirectclipboard:i:${host.rdpClipboard === false ? 0 : 1}`, `redirectdrives:i:${host.rdpDrives ? 1 : 0}`, `audiomode:i:${host.rdpAudio === false ? 2 : 0}`, 'prompt for credentials:i:0', 'authentication level:i:2', 'autoreconnection enabled:i:1', ].filter(Boolean); const file = path.join(os.tmpdir(), `mrterm-${host.id || 'quick'}-${Date.now()}.rdp`); fs.writeFileSync(file, lines.join('\r\n'), 'utf16le'); setTimeout(() => fs.rm(file, () => {}), 60000); return file; } // Legt Anmeldedaten kurzzeitig im Windows-Anmeldeinformationsspeicher ab, damit mstsc ohne Rückfrage verbindet. function withWinCredentials(host, run) { const user = userOf(host); if (!(host.password && user)) return run(); execFile('cmdkey', [`/generic:TERMSRV/${host.address}`, `/user:${user}`, `/pass:${host.password}`], () => { run(); setTimeout(() => execFile('cmdkey', [`/delete:TERMSRV/${host.address}`], () => {}), 20000); }); } // ---------- Linux: FreeRDP-Argumente function freerdpArgs(host, size) { const port = Number(host.port) || 3389; const args = [`/v:${host.address}:${port}`, '/cert:tofu', '+auto-reconnect']; if (host.username) args.push(`/u:${host.username}`); if (host.domain) args.push(`/d:${host.domain}`); const resize = host.rdpResize === 'scale' ? '/smart-sizing' : '/dynamic-resolution'; if (size) args.push(`/size:${size.width}x${size.height}`, resize); else if (host.rdpFullscreen) args.push('/f'); else if (host.rdpWidth && host.rdpHeight) args.push(`/size:${host.rdpWidth}x${host.rdpHeight}`, resize); else args.push(resize); if (host.rdpClipboard !== false) args.push('+clipboard'); if (host.rdpDrives) args.push(`/drive:home,${os.homedir()}`); if (host.rdpAudio !== false) args.push('/sound'); args.push(`/title:${host.label || host.address}`); // Passwort über stdin statt Kommandozeile (nicht in `ps` sichtbar) if (host.password) args.push('/from-stdin'); return args; } // Externes Fenster function launch(host, settings) { if (process.platform === 'win32') { const file = writeRdpFile(host); withWinCredentials(host, () => spawn('mstsc.exe', [file], { detached: true, stdio: 'ignore' }).unref()); return { client: 'mstsc' }; } const client = linuxClient(settings.rdpClientLinux); if (!client) throw new Error(i18n.t('No FreeRDP found. Install with: sudo pacman -S freerdp')); const p = spawn(client, freerdpArgs(host), { detached: true, stdio: [host.password ? 'pipe' : 'ignore', 'ignore', 'pipe'] }); if (host.password) { p.stdin.write(host.password + '\n'); p.stdin.end(); } let err = ''; p.stderr.on('data', (d) => { err = (err + d).slice(-4000); }); p.unref(); return { client, process: p, getErr: () => err }; } module.exports = { detect, launch, linuxClient, writeRdpFile, withWinCredentials, freerdpArgs };