This commit is contained in:
Baobhan Sith
2025-04-30 13:00:38 +08:00
parent 7b4bc55dd3
commit 82d516afd2
3 changed files with 86 additions and 16 deletions
+25 -8
View File
@@ -465,17 +465,34 @@ export const initializeWebSocket = async (server: http.Server, sessionParser: Re
if (isRdpProxy) { if (isRdpProxy) {
// Retrieve all necessary parameters passed from the upgrade handler // Retrieve all necessary parameters passed from the upgrade handler
const rdpToken = (request as any).rdpToken; const rdpToken = (request as any).rdpToken;
const rdpWidth = (request as any).rdpWidth; const rdpWidthStr = (request as any).rdpWidth; // Get as string first
const rdpHeight = (request as any).rdpHeight; const rdpHeightStr = (request as any).rdpHeight; // Get as string first
const rdpDpi = (request as any).rdpDpi; // const rdpDpi = (request as any).rdpDpi; // Original DPI from URL - we will recalculate
if (!rdpToken || !rdpWidth || !rdpHeight || !rdpDpi) { // --- 新增:参数验证和 DPI 计算 ---
console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing required parameters (token, width, height, dpi).`); if (!rdpToken || !rdpWidthStr || !rdpHeightStr) { // Check string presence
ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection parameters.' })); console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing required parameters (token, width, height).`);
ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection parameters (token, width, height).' }));
ws.close(1008, 'Missing RDP parameters'); ws.close(1008, 'Missing RDP parameters');
return; return;
} }
const rdpWidth = parseInt(rdpWidthStr, 10);
const rdpHeight = parseInt(rdpHeightStr, 10);
if (isNaN(rdpWidth) || isNaN(rdpHeight) || rdpWidth <= 0 || rdpHeight <= 0) {
console.error(`WebSocket: RDP Proxy connection for ${ws.username} has invalid width or height parameters.`);
ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Invalid width or height parameters.' }));
ws.close(1008, 'Invalid RDP dimensions');
return;
}
// 根据宽高的简单 DPI 计算逻辑 (如果宽度 > 1920,则 DPI=120,否则 DPI=96)
const calculatedDpi = rdpWidth > 1920 ? 120 : 96;
console.log(`WebSocket: RDP Proxy calculated DPI for ${ws.username} based on width ${rdpWidth}: ${calculatedDpi}`);
// --- 结束新增 ---
// Determine RDP target URL based on deployment mode // Determine RDP target URL based on deployment mode
const deploymentMode = process.env.DEPLOYMENT_MODE; // Default to docker mode const deploymentMode = process.env.DEPLOYMENT_MODE; // Default to docker mode
let rdpBaseUrl: string; let rdpBaseUrl: string;
@@ -492,8 +509,8 @@ export const initializeWebSocket = async (server: http.Server, sessionParser: Re
// Ensure base URL doesn't end with a slash before appending query params // Ensure base URL doesn't end with a slash before appending query params
const cleanRdpBaseUrl = rdpBaseUrl.endsWith('/') ? rdpBaseUrl.slice(0, -1) : rdpBaseUrl; const cleanRdpBaseUrl = rdpBaseUrl.endsWith('/') ? rdpBaseUrl.slice(0, -1) : rdpBaseUrl;
// Append ALL parameters to the target URL // Append ALL parameters to the target URL, using calculated DPI
const rdpTargetUrl = `${cleanRdpBaseUrl}/?token=${encodeURIComponent(rdpToken)}&width=${encodeURIComponent(rdpWidth)}&height=${encodeURIComponent(rdpHeight)}&dpi=${encodeURIComponent(rdpDpi)}`; const rdpTargetUrl = `${cleanRdpBaseUrl}/?token=${encodeURIComponent(rdpToken)}&width=${encodeURIComponent(rdpWidth)}&height=${encodeURIComponent(rdpHeight)}&dpi=${encodeURIComponent(calculatedDpi)}`; // 使用 calculatedDpi
console.log(`WebSocket: RDP Proxy for ${ws.username} attempting to connect to ${rdpTargetUrl}`); console.log(`WebSocket: RDP Proxy for ${ws.username} attempting to connect to ${rdpTargetUrl}`);
@@ -27,8 +27,9 @@ const connectionStatus = ref<'disconnected' | 'connecting' | 'connected' | 'erro
const statusMessage = ref(''); const statusMessage = ref('');
const keyboard = ref<any | null>(null); const keyboard = ref<any | null>(null);
const mouse = ref<any | null>(null); const mouse = ref<any | null>(null);
const desiredModalWidth = ref(1064); const desiredModalWidth = ref(1064);
const desiredModalHeight = ref(858); const desiredModalHeight = ref(858);
const isKeyboardDisabledForInput = ref(false); //
const MIN_MODAL_WIDTH = 1024; const MIN_MODAL_WIDTH = 1024;
const MIN_MODAL_HEIGHT = 768; const MIN_MODAL_HEIGHT = 768;
@@ -134,6 +135,16 @@ const connectRdp = async () => {
statusMessage.value = t('remoteDesktopModal.status.connected'); statusMessage.value = t('remoteDesktopModal.status.connected');
connectionStatus.value = 'connected'; connectionStatus.value = 'connected';
setupInputListeners(); setupInputListeners();
// RDP
nextTick(() => {
const displayEl = guacClient.value?.getDisplay()?.getElement();
if (displayEl && typeof displayEl.focus === 'function') {
displayEl.focus();
console.log('[RDP Modal] Focused RDP display after connection.');
} else {
console.warn('[RDP Modal] Could not focus RDP display after connection.');
}
});
setTimeout(() => { setTimeout(() => {
nextTick(() => { nextTick(() => {
@@ -179,6 +190,18 @@ const setupInputListeners = () => {
if (!guacClient.value || !rdpDisplayRef.value) return; if (!guacClient.value || !rdpDisplayRef.value) return;
try { try {
const displayEl = guacClient.value.getDisplay().getElement() as HTMLElement; const displayEl = guacClient.value.getDisplay().getElement() as HTMLElement;
displayEl.tabIndex = 0; // 使 RDP
//
const handleRdpDisplayClick = () => {
const activeElement = document.activeElement as HTMLElement;
//
if (activeElement && (activeElement.id === 'modal-width' || activeElement.id === 'modal-height')) {
activeElement.blur();
console.log('[RDP Modal] Blurred input field on RDP display click.');
}
};
displayEl.addEventListener('click', handleRdpDisplayClick);
// @ts-ignore // @ts-ignore
mouse.value = new Guacamole.Mouse(displayEl); mouse.value = new Guacamole.Mouse(displayEl);
@@ -188,17 +211,25 @@ const setupInputListeners = () => {
guacClient.value.sendMouseState(mouseState); guacClient.value.sendMouseState(mouseState);
} }
}; };
// @ts-ignore
mouse.value.onmouseup = mouse.value.onmousemove = (mouseState: any) => {
if (guacClient.value) {
guacClient.value.sendMouseState(mouseState);
}
};
// @ts-ignore // @ts-ignore
keyboard.value = new Guacamole.Keyboard(document); // document 便 keyboard.value = new Guacamole.Keyboard(displayEl); // RDP
keyboard.value.onkeydown = (keysym: number) => { keyboard.value.onkeydown = (keysym: number) => {
if (guacClient.value) { //
if (guacClient.value && !isKeyboardDisabledForInput.value) {
guacClient.value.sendKeyEvent(1, keysym); guacClient.value.sendKeyEvent(1, keysym);
} }
}; };
keyboard.value.onkeyup = (keysym: number) => { keyboard.value.onkeyup = (keysym: number) => {
if (guacClient.value) { //
if (guacClient.value && !isKeyboardDisabledForInput.value) {
guacClient.value.sendKeyEvent(0, keysym); guacClient.value.sendKeyEvent(0, keysym);
} }
}; };
@@ -212,9 +243,11 @@ const removeInputListeners = () => {
if (keyboard.value) { if (keyboard.value) {
keyboard.value.onkeydown = null; keyboard.value.onkeydown = null;
keyboard.value.onkeyup = null; keyboard.value.onkeyup = null;
keyboard.value = null; keyboard.value = null; // Guacamole null
} }
if (mouse.value) { if (mouse.value) {
// Guacamole Mouse 'destroy' 'removeListeners'
//
mouse.value.onmousedown = null; mouse.value.onmousedown = null;
mouse.value.onmouseup = null; mouse.value.onmouseup = null;
mouse.value.onmousemove = null; mouse.value.onmousemove = null;
@@ -222,9 +255,26 @@ const removeInputListeners = () => {
} }
}; };
const disableRdpKeyboard = () => {
isKeyboardDisabledForInput.value = true;
console.log('[RDP Modal] Keyboard disabled for input focus.');
};
const enableRdpKeyboard = () => {
isKeyboardDisabledForInput.value = false;
console.log('[RDP Modal] Keyboard enabled after input blur.');
// RDP
nextTick(() => {
const displayEl = guacClient.value?.getDisplay()?.getElement();
if (displayEl && typeof displayEl.focus === 'function') {
displayEl.focus();
}
});
};
const disconnectRdp = () => { const disconnectRdp = () => {
removeInputListeners(); removeInputListeners();
isKeyboardDisabledForInput.value = false; //
if (guacClient.value) { if (guacClient.value) {
guacClient.value.disconnect(); guacClient.value.disconnect();
guacClient.value = null; guacClient.value = null;
@@ -415,6 +465,8 @@ const computedModalStyle = computed(() => {
v-model.number="desiredModalWidth" v-model.number="desiredModalWidth"
step="10" step="10"
class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary" class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
@focus="disableRdpKeyboard"
@blur="enableRdpKeyboard"
/> />
<label for="modal-height" class="text-xs">{{ t('common.height') }}:</label> <label for="modal-height" class="text-xs">{{ t('common.height') }}:</label>
<input <input
@@ -423,6 +475,8 @@ const computedModalStyle = computed(() => {
v-model.number="desiredModalHeight" v-model.number="desiredModalHeight"
step="10" step="10"
class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary" class="w-16 px-1 py-0.5 text-xs border border-border rounded bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
@focus="disableRdpKeyboard"
@blur="enableRdpKeyboard"
/> />
<!-- 添加重新连接按钮 --> <!-- 添加重新连接按钮 -->
<button <button
+1 -2
View File
@@ -200,7 +200,7 @@ const gracefulShutdown = (signal: string) => {
// 超时后强制退出 // 超时后强制退出
setTimeout(() => { setTimeout(() => {
console.error("优雅关闭超时。强制退出。"); console.error("关闭超时。强制退出。");
process.exit(1); process.exit(1);
}, 10000); // 10 秒超时 }, 10000); // 10 秒超时
}; };
@@ -209,6 +209,5 @@ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGUSR2', () => { process.on('SIGUSR2', () => {
// 优雅地处理 nodemon 重启
gracefulShutdown('SIGUSR2 (nodemon restart)'); gracefulShutdown('SIGUSR2 (nodemon restart)');
}); });