120 lines
5.2 KiB
JavaScript
120 lines
5.2 KiB
JavaScript
/*
|
|
* RDP-Session-Client: guacamole-common-js <-> /ws/rdp/{host_id}.
|
|
*
|
|
* Vollbild: native Browser-Fullscreen-API (Konzept 4.3).
|
|
* Copy & Paste: bidirektionale Synchronisation ueber Guacamole.Client
|
|
* onclipboard-Event (Ziel -> Browser) und den `paste`-Browser-Event
|
|
* (Browser -> Ziel, sendet eine "clipboard"-Instruktion). Wird serverseitig
|
|
* zusaetzlich blockiert, wenn fuer den Host clipboard_enabled=false ist
|
|
* (siehe app/rdp_proxy/ws_tunnel.py) -- das UI blendet den Hinweis dann ein.
|
|
*/
|
|
(() => {
|
|
"use strict";
|
|
|
|
const shell = document.getElementById("session-shell");
|
|
const hostId = shell.dataset.hostId;
|
|
const statusEl = document.getElementById("status");
|
|
const displayDiv = document.getElementById("rdp-display");
|
|
|
|
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
const width = Math.round(window.innerWidth);
|
|
const height = Math.round(window.innerHeight - 40);
|
|
const dpi = Math.round(window.devicePixelRatio * 96) || 96;
|
|
|
|
// WICHTIG: die Verbindungsparameter gehoeren NICHT in die Tunnel-URL.
|
|
// Guacamole.WebSocketTunnel.connect(data) baut die Socket-URL selbst als
|
|
// `tunnelURL + "?" + data` zusammen. Standen die Parameter schon in der
|
|
// URL, entstand daraus `...?width=1280&height=800&dpi=96?undefined` -- der
|
|
// letzte Query-Parameter war damit kein gueltiger Integer mehr, FastAPI
|
|
// wies den WebSocket noch vor dem Routenhandler ab (HTTP 422) und im
|
|
// Verbindungslog des Servers tauchte kein einziger Eintrag auf.
|
|
const tunnelUrl = `${proto}//${window.location.host}/ws/rdp/${hostId}`;
|
|
const connectParams = `width=${width}&height=${height}&dpi=${dpi}`;
|
|
const tunnel = new Guacamole.WebSocketTunnel(tunnelUrl);
|
|
const client = new Guacamole.Client(tunnel);
|
|
|
|
displayDiv.appendChild(client.getDisplay().getElement());
|
|
|
|
client.onstatechange = (state) => {
|
|
// 0=idle,1=connecting,2=waiting,3=connected,4=disconnecting,5=disconnected
|
|
const labels = ["Idle", "Verbinde ...", "Warte auf Server ...", "Verbunden", "Trenne ...", "Getrennt"];
|
|
statusEl.textContent = labels[state] || `Status ${state}`;
|
|
};
|
|
const showError = (err) => {
|
|
// Der Server gibt den Abbruchgrund als WebSocket-Close-Reason mit
|
|
// (siehe _reject() in app/rdp_proxy/ws_tunnel.py); guacamole-common-js
|
|
// reicht ihn als Guacamole.Status.message hierher durch.
|
|
statusEl.textContent = "Fehler: " + ((err && err.message) || "unbekannt");
|
|
statusEl.classList.add("error");
|
|
// Troubleshooting: Statuscode + Rohobjekt zusaetzlich in die
|
|
// Browser-Konsole, fuer den Fall, dass die Klartextmeldung allein nicht
|
|
// reicht (z.B. guacd/FreeRDP-interne Codes).
|
|
console.error("RDP-Sitzung beendet/Fehler:", err);
|
|
};
|
|
client.onerror = showError;
|
|
tunnel.onerror = showError;
|
|
|
|
client.onclipboard = (stream, mimetype) => {
|
|
if (!mimetype.startsWith("text/")) return;
|
|
const reader = new Guacamole.StringReader(stream);
|
|
let data = "";
|
|
reader.ontext = (text) => { data += text; };
|
|
reader.onend = () => {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(data).catch(() => {});
|
|
}
|
|
};
|
|
};
|
|
|
|
client.connect(connectParams);
|
|
|
|
window.addEventListener("beforeunload", () => client.disconnect());
|
|
|
|
const mouse = new Guacamole.Mouse(client.getDisplay().getElement());
|
|
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove = (mouseState) => {
|
|
client.sendMouseState(mouseState);
|
|
};
|
|
|
|
const keyboard = new Guacamole.Keyboard(document);
|
|
keyboard.onkeydown = (keysym) => client.sendKeyEvent(1, keysym);
|
|
keyboard.onkeyup = (keysym) => client.sendKeyEvent(0, keysym);
|
|
|
|
document.addEventListener("paste", (ev) => {
|
|
const text = (ev.clipboardData || window.clipboardData).getData("text");
|
|
if (!text) return;
|
|
const stream = client.createClipboardStream("text/plain");
|
|
const writer = new Guacamole.StringWriter(stream);
|
|
writer.sendText(text);
|
|
writer.sendEnd();
|
|
});
|
|
|
|
document.getElementById("fullscreen-btn").addEventListener("click", () => {
|
|
if (!document.fullscreenElement) {
|
|
shell.requestFullscreen().catch(() => {});
|
|
} else {
|
|
document.exitFullscreen();
|
|
}
|
|
});
|
|
|
|
// Strg+Alt+Entf: der Browser faengt diese Kombination selbst ab (Windows
|
|
// reserviert sie systemweit), sie kommt also nie als normales Tastatur-
|
|
// Event beim RDP-Ziel an. Abhilfe wie im offiziellen Guacamole-Client:
|
|
// die drei Tasten einzeln als synthetische Key-Events senden (X11-
|
|
// Keysyms: Ctrl=0xFFE3, Alt=0xFFE9, Delete=0xFFFF), erst alle drei
|
|
// herunter- dann in umgekehrter Reihenfolge wieder hochdruecken.
|
|
document.getElementById("ctrlaltdel-btn").addEventListener("click", () => {
|
|
const keys = [0xffe3, 0xffe9, 0xffff];
|
|
for (const keysym of keys) client.sendKeyEvent(1, keysym);
|
|
for (const keysym of keys.slice().reverse()) client.sendKeyEvent(0, keysym);
|
|
});
|
|
|
|
document.getElementById("exit-btn").addEventListener("click", () => {
|
|
try { client.disconnect(); } catch (_) { /* bereits getrennt */ }
|
|
window.close();
|
|
// Siehe terminal.js fuer die ausfuehrliche Begruendung: window.close()
|
|
// schliesst den Tab nur unter bestimmten Voraussetzungen lautlos --
|
|
// deshalb zusaetzlich immer ein Fallback zum Dashboard.
|
|
window.setTimeout(() => { window.location.href = "/dashboard"; }, 300);
|
|
});
|
|
})();
|