RDP-Verbindungen als Tabs (mstsc/FreeRDP eingebettet); App-Kürzel im Terminal auf Strg+Shift, damit Strg+W/K/T bei nano & Co. ankommen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+67
-43
@@ -1,4 +1,5 @@
|
||||
// RDP: Windows nutzt das eingebaute mstsc.exe, Linux (Arch/CachyOS) FreeRDP (xfreerdp3 / xfreerdp / wlfreerdp).
|
||||
// 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');
|
||||
@@ -11,7 +12,9 @@ function which(bin) {
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function linuxClient(pref) {
|
||||
// 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
|
||||
@@ -21,53 +24,62 @@ function linuxClient(pref) {
|
||||
}
|
||||
|
||||
function detect(settings) {
|
||||
if (process.platform === 'win32') return { available: true, client: 'mstsc' };
|
||||
if (process.platform === 'win32') return { available: true, client: 'mstsc', embedClient: 'mstsc' };
|
||||
const c = linuxClient(settings.rdpClientLinux);
|
||||
return c ? { available: true, client: c } : { available: false, hint: 'sudo pacman -S freerdp' };
|
||||
const e = linuxClient(null, true);
|
||||
return c ? { available: true, client: c, embedClient: e || null } : { available: false, hint: 'sudo pacman -S freerdp' };
|
||||
}
|
||||
|
||||
function launch(host, settings) {
|
||||
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 user = host.domain ? `${host.domain}\\${host.username || ''}` : host.username || '';
|
||||
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',
|
||||
// Im Tab keine Skalierung, damit die Auflösung beim Größenändern mitwächst
|
||||
`smart sizing:i:${size ? 0 : 1}`,
|
||||
'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;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const target = port === 3389 ? host.address : `${host.address}:${port}`;
|
||||
const lines = [
|
||||
`full address:s:${target}`,
|
||||
user && `username:s:${user}`,
|
||||
`screen mode id:i:${host.rdpFullscreen ? 2 : 1}`,
|
||||
host.rdpWidth && `desktopwidth:i:${host.rdpWidth}`,
|
||||
host.rdpHeight && `desktopheight:i:${host.rdpHeight}`,
|
||||
'dynamic resolution:i:1',
|
||||
'smart sizing:i:1',
|
||||
`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',
|
||||
].filter(Boolean);
|
||||
const file = path.join(os.tmpdir(), `mrterm-${host.id}.rdp`);
|
||||
fs.writeFileSync(file, lines.join('\r\n'), 'utf16le');
|
||||
const run = () => {
|
||||
const p = spawn('mstsc.exe', [file], { detached: true, stdio: 'ignore' });
|
||||
p.unref();
|
||||
};
|
||||
if (host.password && user) {
|
||||
// Anmeldedaten temporär im Windows-Anmeldeinformationsspeicher ablegen, danach wieder entfernen.
|
||||
execFile('cmdkey', [`/generic:TERMSRV/${host.address}`, `/user:${user}`, `/pass:${host.password}`], () => {
|
||||
run();
|
||||
setTimeout(() => execFile('cmdkey', [`/delete:TERMSRV/${host.address}`], () => {}), 20000);
|
||||
});
|
||||
} else run();
|
||||
return { client: 'mstsc' };
|
||||
}
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
const client = linuxClient(settings.rdpClientLinux);
|
||||
if (!client) throw new Error('Kein FreeRDP gefunden. Installieren mit: sudo pacman -S freerdp');
|
||||
// ---------- 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}`);
|
||||
if (host.rdpFullscreen) args.push('/f');
|
||||
if (size) args.push(`/size:${size.width}x${size.height}`, '/dynamic-resolution');
|
||||
else if (host.rdpFullscreen) args.push('/f');
|
||||
else if (host.rdpWidth && host.rdpHeight) args.push(`/size:${host.rdpWidth}x${host.rdpHeight}`);
|
||||
else args.push('/dynamic-resolution');
|
||||
if (host.rdpClipboard !== false) args.push('+clipboard');
|
||||
@@ -76,12 +88,24 @@ function launch(host, settings) {
|
||||
args.push(`/title:${host.label || host.address}`);
|
||||
// Passwort über stdin statt Kommandozeile (nicht in `ps` sichtbar)
|
||||
if (host.password) args.push('/from-stdin');
|
||||
const p = spawn(client, args, { detached: true, stdio: [host.password ? 'pipe' : 'ignore', 'ignore', 'pipe'] });
|
||||
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('Kein FreeRDP gefunden. Installieren mit: 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 += d; });
|
||||
p.stderr.on('data', (d) => { err = (err + d).slice(-4000); });
|
||||
p.unref();
|
||||
return { client, process: p, getErr: () => err };
|
||||
}
|
||||
|
||||
module.exports = { detect, launch };
|
||||
module.exports = { detect, launch, linuxClient, writeRdpFile, withWinCredentials, freerdpArgs };
|
||||
|
||||
Reference in New Issue
Block a user