From 82d516afd29e73ef6727246aeae8a15add0f30d1 Mon Sep 17 00:00:00 2001 From: Baobhan Sith <80159437+Heavrnl@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:00:38 +0800 Subject: [PATCH] update --- packages/backend/src/websocket.ts | 33 +++++++--- .../src/components/RemoteDesktopModal.vue | 66 +++++++++++++++++-- packages/rdp/src/server.ts | 3 +- 3 files changed, 86 insertions(+), 16 deletions(-) diff --git a/packages/backend/src/websocket.ts b/packages/backend/src/websocket.ts index 6823f09..660d1ca 100644 --- a/packages/backend/src/websocket.ts +++ b/packages/backend/src/websocket.ts @@ -465,17 +465,34 @@ export const initializeWebSocket = async (server: http.Server, sessionParser: Re if (isRdpProxy) { // Retrieve all necessary parameters passed from the upgrade handler const rdpToken = (request as any).rdpToken; - const rdpWidth = (request as any).rdpWidth; - const rdpHeight = (request as any).rdpHeight; - const rdpDpi = (request as any).rdpDpi; + const rdpWidthStr = (request as any).rdpWidth; // Get as string first + const rdpHeightStr = (request as any).rdpHeight; // Get as string first + // const rdpDpi = (request as any).rdpDpi; // Original DPI from URL - we will recalculate - if (!rdpToken || !rdpWidth || !rdpHeight || !rdpDpi) { - console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing required parameters (token, width, height, dpi).`); - ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection parameters.' })); + // --- 新增:参数验证和 DPI 计算 --- + if (!rdpToken || !rdpWidthStr || !rdpHeightStr) { // Check string presence + 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'); 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 const deploymentMode = process.env.DEPLOYMENT_MODE; // Default to docker mode 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 const cleanRdpBaseUrl = rdpBaseUrl.endsWith('/') ? rdpBaseUrl.slice(0, -1) : rdpBaseUrl; - // Append ALL parameters to the target URL - const rdpTargetUrl = `${cleanRdpBaseUrl}/?token=${encodeURIComponent(rdpToken)}&width=${encodeURIComponent(rdpWidth)}&height=${encodeURIComponent(rdpHeight)}&dpi=${encodeURIComponent(rdpDpi)}`; + // Append ALL parameters to the target URL, using calculated DPI + 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}`); diff --git a/packages/frontend/src/components/RemoteDesktopModal.vue b/packages/frontend/src/components/RemoteDesktopModal.vue index 79e0b45..025cea7 100644 --- a/packages/frontend/src/components/RemoteDesktopModal.vue +++ b/packages/frontend/src/components/RemoteDesktopModal.vue @@ -27,8 +27,9 @@ const connectionStatus = ref<'disconnected' | 'connecting' | 'connected' | 'erro const statusMessage = ref(''); const keyboard = ref(null); const mouse = ref(null); -const desiredModalWidth = ref(1064); -const desiredModalHeight = ref(858); +const desiredModalWidth = ref(1064); +const desiredModalHeight = ref(858); +const isKeyboardDisabledForInput = ref(false); // 标记键盘是否因输入框聚焦而禁用 const MIN_MODAL_WIDTH = 1024; const MIN_MODAL_HEIGHT = 768; @@ -134,6 +135,16 @@ const connectRdp = async () => { statusMessage.value = t('remoteDesktopModal.status.connected'); connectionStatus.value = 'connected'; 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(() => { nextTick(() => { @@ -179,6 +190,18 @@ const setupInputListeners = () => { if (!guacClient.value || !rdpDisplayRef.value) return; try { 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 mouse.value = new Guacamole.Mouse(displayEl); @@ -188,17 +211,25 @@ const setupInputListeners = () => { guacClient.value.sendMouseState(mouseState); } }; + // @ts-ignore + mouse.value.onmouseup = mouse.value.onmousemove = (mouseState: any) => { + if (guacClient.value) { + guacClient.value.sendMouseState(mouseState); + } + }; // @ts-ignore - keyboard.value = new Guacamole.Keyboard(document); // 将监听器附加到 document 以便更好地捕获 + keyboard.value = new Guacamole.Keyboard(displayEl); // 将监听器附加到 RDP 显示元素 keyboard.value.onkeydown = (keysym: number) => { - if (guacClient.value) { + // 仅当输入框未聚焦时发送按键事件 + if (guacClient.value && !isKeyboardDisabledForInput.value) { guacClient.value.sendKeyEvent(1, keysym); } }; keyboard.value.onkeyup = (keysym: number) => { - if (guacClient.value) { + // 仅当输入框未聚焦时发送按键事件 + if (guacClient.value && !isKeyboardDisabledForInput.value) { guacClient.value.sendKeyEvent(0, keysym); } }; @@ -212,9 +243,11 @@ const removeInputListeners = () => { if (keyboard.value) { keyboard.value.onkeydown = null; keyboard.value.onkeyup = null; - keyboard.value = null; + keyboard.value = null; // Guacamole 内部可能会处理移除监听器,但显式置 null 更好 } if (mouse.value) { + // Guacamole Mouse 对象没有明确的 'destroy' 或 'removeListeners' 方法 + // 我们需要确保事件处理器被移除 mouse.value.onmousedown = null; mouse.value.onmouseup = 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 = () => { removeInputListeners(); + isKeyboardDisabledForInput.value = false; // 确保状态重置 if (guacClient.value) { guacClient.value.disconnect(); guacClient.value = null; @@ -415,6 +465,8 @@ const computedModalStyle = computed(() => { v-model.number="desiredModalWidth" 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" + @focus="disableRdpKeyboard" + @blur="enableRdpKeyboard" /> { v-model.number="desiredModalHeight" 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" + @focus="disableRdpKeyboard" + @blur="enableRdpKeyboard" />