/** * RustDesk Standalone Web Client - Input Handler * * Captures mouse and keyboard events from the remote desktop canvas * and converts them to RustDesk protocol messages. */ const RDInput = (() => { let _canvas = null; let _enabled = false; let _displayWidth = 0; let _displayHeight = 0; let _scaleX = 1; let _scaleY = 1; let _mouseButtons = 0; // Current mouse button mask let _modifiers = []; // Current modifier keys let _keyboardLocked = false; let _viewOnly = false; // Mouse button masks const MOUSE = { LEFT: 1, RIGHT: 2, MIDDLE: 4, BUTTON4: 8, BUTTON5: 16 }; // Modifier key mappings const MODIFIER_MAP = { 'ControlLeft': 50, // LeftControl 'ControlRight': 54, // RightControl 'ShiftLeft': 51, // LeftShift 'ShiftRight': 55, // RightShift 'AltLeft': 52, // LeftAlt 'AltRight': 56, // RightAlt 'MetaLeft': 53, // LeftMeta 'MetaRight': 57, // RightMeta }; // Key name to ControlKey enum mapping const KEY_MAP = { 'Alt': 1, 'Backspace': 2, 'CapsLock': 3, 'Control': 4, 'Delete': 5, 'ArrowDown': 6, 'End': 7, 'Escape': 8, 'F1': 9, 'F10': 10, 'F11': 11, 'F12': 12, 'F2': 13, 'F3': 14, 'F4': 15, 'F5': 16, 'F6': 17, 'F7': 18, 'F8': 19, 'F9': 20, 'Home': 21, 'ArrowLeft': 22, 'Meta': 23, 'PageDown': 24, 'PageUp': 25, 'Enter': 26, 'Return': 26, 'ArrowRight': 27, 'Shift': 28, ' ': 29, 'Space': 29, 'Tab': 30, 'ArrowUp': 31, 'Insert': 49, 'NumLock': 63, 'ScrollLock': 64, 'PrintScreen': 66, 'Numpad0': 32, 'Numpad1': 33, 'Numpad2': 34, 'Numpad3': 35, 'Numpad4': 36, 'Numpad5': 37, 'Numpad6': 38, 'Numpad7': 39, 'Numpad8': 40, 'Numpad9': 41, 'NumpadAdd': 42, 'NumpadDecimal': 43, 'NumpadDelete': 44, 'NumpadDivide': 45, 'NumpadEnter': 46, 'NumpadMultiply': 47, 'NumpadSubtract': 48, 'VolumeDown': 58, 'VolumeMute': 59, 'VolumeUp': 60, 'ContextMenu': 61, }; /** * Initialize input handler * @param {HTMLCanvasElement} canvas - The canvas to capture events from */ function init(canvas) { _canvas = canvas; attachMouseEvents(); attachKeyboardEvents(); attachTouchEvents(); attachWheelEvents(); console.log('[RDInput] Initialized'); } /** * Enable/disable input capture */ function setEnabled(enabled) { _enabled = enabled; if (enabled) { _canvas.style.cursor = 'default'; } else { _canvas.style.cursor = 'not-allowed'; } } /** * Set view-only mode */ function setViewOnly(viewOnly) { _viewOnly = viewOnly; } /** * Update display dimensions for coordinate mapping */ function setDisplaySize(width, height) { _displayWidth = width; _displayHeight = height; updateScale(); } /** * Calculate scale factors for coordinate mapping */ function updateScale() { if (_canvas && _displayWidth > 0 && _displayHeight > 0) { _scaleX = _displayWidth / _canvas.clientWidth; _scaleY = _displayHeight / _canvas.clientHeight; } else { _scaleX = 1; _scaleY = 1; } } /** * Convert canvas coordinates to display coordinates */ function canvasToDisplay(canvasX, canvasY) { return { x: Math.round(canvasX * _scaleX), y: Math.round(canvasY * _scaleY) }; } /** * Attach mouse event listeners */ function attachMouseEvents() { _canvas.addEventListener('mousedown', onMouseDown); _canvas.addEventListener('mouseup', onMouseUp); _canvas.addEventListener('mousemove', onMouseMove); _canvas.addEventListener('mouseenter', onMouseEnter); _canvas.addEventListener('mouseleave', onMouseLeave); _canvas.addEventListener('contextmenu', (e) => e.preventDefault()); } /** * Attach keyboard event listeners */ function attachKeyboardEvents() { document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); } /** * Attach touch event listeners */ function attachTouchEvents() { _canvas.addEventListener('touchstart', onTouchStart, { passive: false }); _canvas.addEventListener('touchmove', onTouchMove, { passive: false }); _canvas.addEventListener('touchend', onTouchEnd, { passive: false }); _canvas.addEventListener('touchcancel', onTouchEnd, { passive: false }); } /** * Attach wheel event listeners */ function attachWheelEvents() { _canvas.addEventListener('wheel', onWheel, { passive: false }); } /** * Handle mouse down */ function onMouseDown(e) { if (!_enabled || _viewOnly) return; e.preventDefault(); updateModifiers(e); let buttonMask = 0; switch (e.button) { case 0: buttonMask = MOUSE.LEFT; break; case 1: buttonMask = MOUSE.MIDDLE; break; case 2: buttonMask = MOUSE.RIGHT; break; case 3: buttonMask = MOUSE.BUTTON4; break; case 4: buttonMask = MOUSE.BUTTON5; break; } _mouseButtons |= buttonMask; const pos = canvasToDisplay(e.offsetX, e.offsetY); RDConnection.sendMouseEvent(_mouseButtons | getModifierMask(), pos.x, pos.y, getModifierList()); // Focus for keyboard events _canvas.focus(); } /** * Handle mouse up */ function onMouseUp(e) { if (!_enabled || _viewOnly) return; e.preventDefault(); updateModifiers(e); let buttonMask = 0; switch (e.button) { case 0: buttonMask = MOUSE.LEFT; break; case 1: buttonMask = MOUSE.MIDDLE; break; case 2: buttonMask = MOUSE.RIGHT; break; case 3: buttonMask = MOUSE.BUTTON4; break; case 4: buttonMask = MOUSE.BUTTON5; break; } _mouseButtons &= ~buttonMask; const pos = canvasToDisplay(e.offsetX, e.offsetY); RDConnection.sendMouseEvent(_mouseButtons | getModifierMask(), pos.x, pos.y, getModifierList()); } /** * Handle mouse move */ function onMouseMove(e) { if (!_enabled || _viewOnly) return; const pos = canvasToDisplay(e.offsetX, e.offsetY); RDConnection.sendMouseEvent(_mouseButtons | getModifierMask(), pos.x, pos.y, getModifierList()); } /** * Handle mouse enter */ function onMouseEnter(e) { // Notify peer that mouse entered the canvas } /** * Handle mouse leave */ function onMouseLeave(e) { if (!_enabled || _viewOnly) return; // Send a release event when mouse leaves RDConnection.sendMouseEvent(0, -1, -1, []); _mouseButtons = 0; } /** * Handle mouse wheel */ function onWheel(e) { if (!_enabled || _viewOnly) return; e.preventDefault(); const buttonMask = e.deltaY > 0 ? MOUSE.BUTTON5 : MOUSE.BUTTON4; const pos = canvasToDisplay(e.offsetX, e.offsetY); // Press RDConnection.sendMouseEvent(buttonMask | getModifierMask(), pos.x, pos.y, getModifierList()); // Release setTimeout(() => { RDConnection.sendMouseEvent(getModifierMask(), pos.x, pos.y, getModifierList()); }, 20); } /** * Handle touch start */ function onTouchStart(e) { if (!_enabled || _viewOnly) return; e.preventDefault(); if (e.touches.length === 1) { const touch = e.touches[0]; const rect = _canvas.getBoundingClientRect(); const x = touch.clientX - rect.left; const y = touch.clientY - rect.top; const pos = canvasToDisplay(x, y); RDConnection.sendMouseEvent(MOUSE.LEFT | getModifierMask(), pos.x, pos.y, getModifierList()); _mouseButtons = MOUSE.LEFT; } } /** * Handle touch move */ function onTouchMove(e) { if (!_enabled || _viewOnly) return; e.preventDefault(); if (e.touches.length === 1) { const touch = e.touches[0]; const rect = _canvas.getBoundingClientRect(); const x = touch.clientX - rect.left; const y = touch.clientY - rect.top; const pos = canvasToDisplay(x, y); RDConnection.sendMouseEvent(MOUSE.LEFT | getModifierMask(), pos.x, pos.y, getModifierList()); } } /** * Handle touch end */ function onTouchEnd(e) { if (!_enabled || _viewOnly) return; e.preventDefault(); RDConnection.sendMouseEvent(getModifierMask(), -1, -1, []); _mouseButtons = 0; } /** * Handle key down */ function onKeyDown(e) { if (!_enabled || _viewOnly) return; // Don't capture if we're typing in an input field if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; e.preventDefault(); e.stopPropagation(); updateModifiers(e); const controlKey = KEY_MAP[e.key] || KEY_MAP[e.code]; if (controlKey) { // Special key RDConnection.sendKeyEvent(controlKey, true, false, getModifierList()); } else { // Regular character const charCode = e.key.charCodeAt(0); if (charCode > 0 && charCode < 65536) { RDConnection.sendCharEvent(charCode, true, false, getModifierList()); } } } /** * Handle key up */ function onKeyUp(e) { if (!_enabled || _viewOnly) return; if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; e.preventDefault(); e.stopPropagation(); updateModifiers(e); const controlKey = KEY_MAP[e.key] || KEY_MAP[e.code]; if (controlKey) { RDConnection.sendKeyEvent(controlKey, false, false, getModifierList()); } else { const charCode = e.key.charCodeAt(0); if (charCode > 0 && charCode < 65536) { RDConnection.sendCharEvent(charCode, false, false, getModifierList()); } } } /** * Update current modifier state */ function updateModifiers(e) { _modifiers = []; if (e.ctrlKey || e.metaKey) _modifiers.push(4); // Control if (e.shiftKey) _modifiers.push(28); // Shift if (e.altKey) _modifiers.push(1); // Alt } /** * Get current modifier key list */ function getModifierList() { return [..._modifiers]; } /** * Get modifier bitmask for mouse events */ function getModifierMask() { let mask = 0; if (_modifiers.includes(4)) mask |= 0x10; // Ctrl if (_modifiers.includes(28)) mask |= 0x20; // Shift if (_modifiers.includes(1)) mask |= 0x40; // Alt if (_modifiers.includes(23)) mask |= 0x80; // Meta return mask; } /** * Type a string of text (for paste/keyboard input) */ function typeString(text) { if (!_enabled || _viewOnly) return; for (const char of text) { const charCode = char.charCodeAt(0); RDConnection.sendCharEvent(charCode, true, true, getModifierList()); } } return { init, setEnabled, setViewOnly, setDisplaySize, typeString, MOUSE, get enabled() { return _enabled; } }; })();