Files
XCDesktop/remote/public/js/input.js

230 lines
7.9 KiB
JavaScript
Raw Normal View History

2026-03-08 01:34:54 +08:00
class InputHandler {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.wsUrl = options.wsUrl;
this.screenWidth = options.screenWidth || 1280;
this.screenHeight = options.screenHeight || 720;
this.ws = null;
this.wsReady = false;
this.lastMoveTime = 0;
this.MOVE_THROTTLE_MS = 33;
this.pendingMove = null;
this.moveThrottleTimer = null;
this.pressedKeys = new Set();
this.codeToKey = {
'Enter': 'enter', 'Backspace': 'backspace', 'Tab': 'tab',
'Escape': 'escape', 'Delete': 'delete', 'Home': 'home',
'End': 'end', 'PageUp': 'pageup', 'PageDown': 'pagedown',
'ArrowUp': 'up', 'ArrowDown': 'down', 'ArrowLeft': 'left', 'ArrowRight': 'right',
'F1': 'f1', 'F2': 'f2', 'F3': 'f3', 'F4': 'f4',
'F5': 'f5', 'F6': 'f6', 'F7': 'f7', 'F8': 'f8',
'F9': 'f9', 'F10': 'f10', 'F11': 'f11', 'F12': 'f12',
'ControlLeft': 'ctrl', 'ControlRight': 'ctrl',
'AltLeft': 'alt', 'AltRight': 'alt',
'ShiftLeft': 'shift', 'ShiftRight': 'shift',
'MetaLeft': 'win', 'MetaRight': 'win',
'Space': 'space',
'Digit0': '0', 'Digit1': '1', 'Digit2': '2', 'Digit3': '3', 'Digit4': '4',
'Digit5': '5', 'Digit6': '6', 'Digit7': '7', 'Digit8': '8', 'Digit9': '9',
'KeyA': 'a', 'KeyB': 'b', 'KeyC': 'c', 'KeyD': 'd', 'KeyE': 'e',
'KeyF': 'f', 'KeyG': 'g', 'KeyH': 'h', 'KeyI': 'i', 'KeyJ': 'j',
'KeyK': 'k', 'KeyL': 'l', 'KeyM': 'm', 'KeyN': 'n', 'KeyO': 'o',
'KeyP': 'p', 'KeyQ': 'q', 'KeyR': 'r', 'KeyS': 's', 'KeyT': 't',
'KeyU': 'u', 'KeyV': 'v', 'KeyW': 'w', 'KeyX': 'x', 'KeyY': 'y', 'KeyZ': 'z',
'Comma': ',', 'Period': '.', 'Slash': '/', 'Semicolon': ';',
'Quote': "'", 'BracketLeft': '[', 'BracketRight': ']',
'Backslash': '\\', 'Minus': '-', 'Equal': '=', 'Backquote': '`'
};
this._boundHandlers = {
mousemove: this._handleMouseMove.bind(this),
mousedown: this._handleMouseDown.bind(this),
mouseup: this._handleMouseUp.bind(this),
contextmenu: this._handleContextMenu.bind(this),
wheel: this._handleWheel.bind(this),
keydown: this._handleKeyDown.bind(this),
keyup: this._handleKeyUp.bind(this),
blur: this._handleBlur.bind(this)
};
}
init() {
this._initWebSocket();
this._bindEvents();
return this;
}
_initWebSocket() {
this.ws = createReconnectingWebSocket(this.wsUrl, {
onOpen: () => {
this.wsReady = true;
console.log('Input WebSocket connected');
},
onClose: () => {
this.wsReady = false;
console.log('Input WebSocket disconnected');
},
onMessage: (e) => {
try {
const msg = JSON.parse(e.data);
if (msg.type === 'screenInfo') {
this.screenWidth = msg.width;
this.screenHeight = msg.height;
console.log('Screen resolution:', this.screenWidth, 'x', this.screenHeight);
}
} catch (err) {}
},
onError: (err) => {
console.error('Input WebSocket error:', err);
}
});
}
_bindEvents() {
this.canvas.addEventListener('mousemove', this._boundHandlers.mousemove);
this.canvas.addEventListener('mousedown', this._boundHandlers.mousedown);
this.canvas.addEventListener('mouseup', this._boundHandlers.mouseup);
this.canvas.addEventListener('contextmenu', this._boundHandlers.contextmenu);
this.canvas.addEventListener('wheel', this._boundHandlers.wheel);
document.addEventListener('keydown', this._boundHandlers.keydown);
document.addEventListener('keyup', this._boundHandlers.keyup);
window.addEventListener('blur', this._boundHandlers.blur);
}
_unbindEvents() {
this.canvas.removeEventListener('mousemove', this._boundHandlers.mousemove);
this.canvas.removeEventListener('mousedown', this._boundHandlers.mousedown);
this.canvas.removeEventListener('mouseup', this._boundHandlers.mouseup);
this.canvas.removeEventListener('contextmenu', this._boundHandlers.contextmenu);
this.canvas.removeEventListener('wheel', this._boundHandlers.wheel);
document.removeEventListener('keydown', this._boundHandlers.keydown);
document.removeEventListener('keyup', this._boundHandlers.keyup);
window.removeEventListener('blur', this._boundHandlers.blur);
}
sendInputEvent(event) {
if (this.wsReady && this.ws && this.ws.isReady()) {
this.ws.getWebSocket().send(JSON.stringify(event));
}
}
_sendMouseMove(x, y) {
this.pendingMove = { x, y };
const now = Date.now();
if (now - this.lastMoveTime >= this.MOVE_THROTTLE_MS) {
this._flushMouseMove();
} else if (!this.moveThrottleTimer) {
this.moveThrottleTimer = setTimeout(() => {
this._flushMouseMove();
this.moveThrottleTimer = null;
}, this.MOVE_THROTTLE_MS - (now - this.lastMoveTime));
}
}
_flushMouseMove() {
if (this.pendingMove) {
this.sendInputEvent({
type: 'mouseMove',
x: this.pendingMove.x,
y: this.pendingMove.y
});
this.lastMoveTime = Date.now();
this.pendingMove = null;
}
}
_handleMouseMove(e) {
const { x, y } = getScreenCoordinates(e.clientX, e.clientY, this.canvas, this.screenWidth, this.screenHeight);
this._sendMouseMove(x, y);
}
_handleMouseDown(e) {
const button = e.button === 2 ? 'right' : 'left';
this.sendInputEvent({
type: 'mouseDown',
button: button
});
}
_handleMouseUp(e) {
const button = e.button === 2 ? 'right' : 'left';
this.sendInputEvent({
type: 'mouseUp',
button: button
});
}
_handleContextMenu(e) {
e.preventDefault();
}
_handleWheel(e) {
e.preventDefault();
const delta = -Math.sign(e.deltaY) * 120;
this.sendInputEvent({
type: 'mouseWheel',
delta: delta
});
}
_getKeyFromEvent(e) {
if (this.codeToKey[e.code]) {
return this.codeToKey[e.code];
}
return e.key;
}
_handleKeyDown(e) {
const key = this._getKeyFromEvent(e);
const keyId = key.toLowerCase();
if (!this.pressedKeys.has(keyId)) {
this.pressedKeys.add(keyId);
this.sendInputEvent({
type: 'keyDown',
key: key
});
}
if (e.key === 'Tab' || e.key === ' ' || e.key.startsWith('Arrow')) {
e.preventDefault();
}
}
_handleKeyUp(e) {
const key = this._getKeyFromEvent(e);
const keyId = key.toLowerCase();
this.pressedKeys.delete(keyId);
this.sendInputEvent({
type: 'keyUp',
key: key
});
}
_handleBlur() {
this.pressedKeys.forEach(keyId => {
this.sendInputEvent({
type: 'keyUp',
key: keyId
});
});
this.pressedKeys.clear();
}
destroy() {
this._unbindEvents();
if (this.moveThrottleTimer) {
clearTimeout(this.moveThrottleTimer);
}
if (this.ws) {
this.ws.close();
}
}
}