update
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)');
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user