mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-05-26 08:05:08 +02:00
feat(hid): remove HID-RPC related code and improve keyboard handling logic
Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
@@ -270,6 +270,7 @@ function KeyboardWrapper() {
|
||||
setIsCapsLockActive(false);
|
||||
}
|
||||
sendKeyboardEvent([keys["CapsLock"]], []);
|
||||
setTimeout(resetKeyboardState, 100);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
export const MessageType = {
|
||||
Handshake: 0x01,
|
||||
KeyboardReport: 0x02,
|
||||
PointerReport: 0x03,
|
||||
WheelReport: 0x04,
|
||||
KeypressReport: 0x05,
|
||||
MouseReport: 0x06,
|
||||
KeyboardMacroReport: 0x07,
|
||||
CancelKeyboardMacro: 0x08,
|
||||
KeypressKeepAlive: 0x09,
|
||||
KeyboardLedState: 0x32,
|
||||
KeysDownState: 0x33,
|
||||
KeyboardMacroState: 0x34,
|
||||
} as const;
|
||||
|
||||
export function marshalKeypressReport(key: number, press: boolean): Uint8Array {
|
||||
return new Uint8Array([MessageType.KeypressReport, key, press ? 1 : 0]);
|
||||
}
|
||||
|
||||
export function marshalKeyboardReport(modifier: number, keys: number[]): Uint8Array {
|
||||
const data = new Uint8Array(8);
|
||||
data[0] = MessageType.KeyboardReport;
|
||||
data[1] = modifier;
|
||||
for (let i = 0; i < Math.min(keys.length, 6); i++) {
|
||||
data[2 + i] = keys[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export function marshalKeypressKeepAlive(): Uint8Array {
|
||||
return new Uint8Array([MessageType.KeypressKeepAlive]);
|
||||
}
|
||||
|
||||
export function marshalHandshake(version: number): Uint8Array {
|
||||
return new Uint8Array([MessageType.Handshake, version]);
|
||||
}
|
||||
|
||||
export interface HidRpcMessage {
|
||||
type: number;
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export function unmarshalMessage(data: ArrayBuffer): HidRpcMessage {
|
||||
const view = new Uint8Array(data);
|
||||
return {
|
||||
type: view[0],
|
||||
data: view.slice(1),
|
||||
};
|
||||
}
|
||||
|
||||
export interface KeyboardLedState {
|
||||
num_lock: boolean;
|
||||
caps_lock: boolean;
|
||||
scroll_lock: boolean;
|
||||
compose: boolean;
|
||||
kana: boolean;
|
||||
shift: boolean;
|
||||
}
|
||||
|
||||
export function parseKeyboardLedState(data: Uint8Array): KeyboardLedState {
|
||||
const raw = data[0] || 0;
|
||||
return {
|
||||
num_lock: !!(raw & (1 << 0)),
|
||||
caps_lock: !!(raw & (1 << 1)),
|
||||
scroll_lock: !!(raw & (1 << 2)),
|
||||
compose: !!(raw & (1 << 3)),
|
||||
kana: !!(raw & (1 << 4)),
|
||||
shift: !!(raw & (1 << 6)),
|
||||
};
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useRTCStore, useHidStore } from "./stores";
|
||||
import {
|
||||
marshalKeypressReport,
|
||||
marshalKeyboardReport,
|
||||
marshalKeypressKeepAlive,
|
||||
marshalHandshake,
|
||||
unmarshalMessage,
|
||||
MessageType,
|
||||
parseKeyboardLedState,
|
||||
} from "./hidRpc";
|
||||
|
||||
export function useHidRpc() {
|
||||
const hidChannel = useRTCStore(state => state.hidChannel);
|
||||
const setRpcHidReady = useHidStore(state => state.setRpcHidReady);
|
||||
const setKeyboardLedState = useHidStore(state => state.setKeyboardLedState);
|
||||
const setKeysDownState = useHidStore(state => state.setKeysDownState);
|
||||
const rpcHidReadyRef = useRef(false);
|
||||
|
||||
// Send keypress event
|
||||
const reportKeypressEvent = useCallback(
|
||||
(key: number, press: boolean) => {
|
||||
if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") {
|
||||
return false;
|
||||
}
|
||||
const data = marshalKeypressReport(key, press);
|
||||
hidChannel.send(data);
|
||||
return true;
|
||||
},
|
||||
[hidChannel]
|
||||
);
|
||||
|
||||
// Send keyboard report (for legacy compatibility)
|
||||
const reportKeyboardEvent = useCallback(
|
||||
(modifier: number, keys: number[]) => {
|
||||
if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") {
|
||||
return false;
|
||||
}
|
||||
const data = marshalKeyboardReport(modifier, keys);
|
||||
hidChannel.send(data);
|
||||
return true;
|
||||
},
|
||||
[hidChannel]
|
||||
);
|
||||
|
||||
// Send keepalive
|
||||
const reportKeypressKeepAlive = useCallback(() => {
|
||||
if (!rpcHidReadyRef.current || !hidChannel || hidChannel.readyState !== "open") {
|
||||
return false;
|
||||
}
|
||||
const data = marshalKeypressKeepAlive();
|
||||
hidChannel.send(data);
|
||||
return true;
|
||||
}, [hidChannel]);
|
||||
|
||||
// Handle incoming HID-RPC messages
|
||||
useEffect(() => {
|
||||
if (!hidChannel) return;
|
||||
|
||||
const messageHandler = (event: MessageEvent) => {
|
||||
const msg = unmarshalMessage(event.data);
|
||||
|
||||
switch (msg.type) {
|
||||
case MessageType.Handshake:
|
||||
if (msg.data[0] === 1) {
|
||||
rpcHidReadyRef.current = true;
|
||||
setRpcHidReady(true);
|
||||
}
|
||||
break;
|
||||
case MessageType.KeyboardLedState:
|
||||
setKeyboardLedState(parseKeyboardLedState(msg.data));
|
||||
break;
|
||||
case MessageType.KeysDownState:
|
||||
// Parse modifier + 6 keys
|
||||
if (msg.data.length >= 7) {
|
||||
setKeysDownState({
|
||||
modifier: msg.data[0],
|
||||
keys: Array.from(msg.data.slice(1, 7)),
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
hidChannel.addEventListener("message", messageHandler);
|
||||
|
||||
// Send handshake
|
||||
if (hidChannel.readyState === "open") {
|
||||
hidChannel.send(marshalHandshake(1));
|
||||
} else {
|
||||
hidChannel.addEventListener("open", () => {
|
||||
hidChannel.send(marshalHandshake(1));
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
hidChannel.removeEventListener("message", messageHandler);
|
||||
rpcHidReadyRef.current = false;
|
||||
setRpcHidReady(false);
|
||||
};
|
||||
}, [hidChannel, setRpcHidReady, setKeyboardLedState, setKeysDownState]);
|
||||
|
||||
return {
|
||||
rpcHidReady: rpcHidReadyRef.current,
|
||||
reportKeypressEvent,
|
||||
reportKeyboardEvent,
|
||||
reportKeypressKeepAlive,
|
||||
};
|
||||
}
|
||||
@@ -3,12 +3,10 @@ import { useCallback, useEffect, useRef } from "react";
|
||||
import notifications from "@/notifications";
|
||||
import { useHidStore, useRTCStore, useSettingsStore } from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useHidRpc } from "@/hooks/useHidRpc";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
|
||||
export default function useKeyboard() {
|
||||
const [send] = useJsonRpc();
|
||||
const { rpcHidReady, reportKeypressEvent, reportKeyboardEvent, reportKeypressKeepAlive } = useHidRpc();
|
||||
|
||||
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
|
||||
const forceHttp = useSettingsStore(state => state.forceHttp);
|
||||
@@ -30,80 +28,45 @@ export default function useKeyboard() {
|
||||
if (usbState !== "configured") return;
|
||||
const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
|
||||
|
||||
// Try HID-RPC first
|
||||
if (rpcHidReady && !forceHttp) {
|
||||
reportKeyboardEvent(accModifier, keys);
|
||||
} else {
|
||||
// Fallback to JSON-RPC
|
||||
send("keyboardReport", { keys, modifier: accModifier }, resp => {
|
||||
if ("error" in resp) {
|
||||
const msg = (resp.error.data as string) || resp.error.message || "";
|
||||
if (msg.includes("cannot send after transport endpoint shutdown") && usbState === "configured") {
|
||||
notifications.error("Please check if the cable and connection are stable.", { duration: 5000 });
|
||||
}
|
||||
// Fallback to JSON-RPC
|
||||
send("keyboardReport", { keys, modifier: accModifier }, resp => {
|
||||
if ("error" in resp) {
|
||||
const msg = (resp.error.data as string) || resp.error.message || "";
|
||||
if (msg.includes("cannot send after transport endpoint shutdown") && usbState === "configured") {
|
||||
notifications.error("Please check if the cable and connection are stable.", { duration: 5000 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// We do this for the info bar to display the currently pressed keys for the user
|
||||
updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers });
|
||||
},
|
||||
[forceHttp, rpcDataChannel?.readyState, rpcHidReady, reportKeyboardEvent, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
|
||||
[forceHttp, rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers, isReinitializingGadget, usbState],
|
||||
);
|
||||
|
||||
// Send per-key press/release (new HID-RPC method)
|
||||
// Send per-key press/release
|
||||
const sendKeypress = useCallback(
|
||||
(key: number, press: boolean) => {
|
||||
if (isReinitializingGadget || usbState !== "configured") return;
|
||||
|
||||
if (rpcHidReady && !forceHttp) {
|
||||
reportKeypressEvent(key, press);
|
||||
|
||||
// Track held keys for keepalive
|
||||
if (press) {
|
||||
heldKeysRef.current.add(key);
|
||||
// Start keepalive interval if not already running
|
||||
if (!keepaliveIntervalRef.current) {
|
||||
keepaliveIntervalRef.current = setInterval(() => {
|
||||
if (heldKeysRef.current.size > 0) {
|
||||
reportKeypressKeepAlive();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
} else {
|
||||
heldKeysRef.current.delete(key);
|
||||
if (heldKeysRef.current.size === 0 && keepaliveIntervalRef.current) {
|
||||
clearInterval(keepaliveIntervalRef.current);
|
||||
keepaliveIntervalRef.current = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy: simulate device-side key handling
|
||||
// This maintains the 6-key buffer on the frontend for legacy compatibility
|
||||
// ... (existing logic would go here, but for now use sendKeyboardEvent)
|
||||
// For simplicity in migration, we fall back to full state reports
|
||||
const modifier = press ? 0 : 0; // Simplified - would need proper modifier tracking
|
||||
sendKeyboardEvent(press ? [key] : [], [modifier]);
|
||||
}
|
||||
// Legacy: simulate device-side key handling
|
||||
// This maintains the 6-key buffer on the frontend for legacy compatibility
|
||||
// For simplicity in migration, we fall back to full state reports
|
||||
const modifier = press ? 0 : 0; // Simplified - would need proper modifier tracking
|
||||
sendKeyboardEvent(press ? [key] : [], [modifier]);
|
||||
},
|
||||
[rpcHidReady, forceHttp, reportKeypressEvent, reportKeypressKeepAlive, isReinitializingGadget, usbState, sendKeyboardEvent]
|
||||
[isReinitializingGadget, usbState, sendKeyboardEvent]
|
||||
);
|
||||
|
||||
const resetKeyboardState = useCallback(() => {
|
||||
// Release all held keys
|
||||
if (rpcHidReady && !forceHttp) {
|
||||
heldKeysRef.current.forEach(key => {
|
||||
reportKeypressEvent(key, false);
|
||||
});
|
||||
} else {
|
||||
sendKeyboardEvent([], []);
|
||||
}
|
||||
sendKeyboardEvent([], []);
|
||||
heldKeysRef.current.clear();
|
||||
if (keepaliveIntervalRef.current) {
|
||||
clearInterval(keepaliveIntervalRef.current);
|
||||
keepaliveIntervalRef.current = null;
|
||||
}
|
||||
}, [rpcHidReady, forceHttp, reportKeypressEvent, sendKeyboardEvent]);
|
||||
}, [sendKeyboardEvent]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export const useKeyboardEvents = (
|
||||
pasteCaptureRef?: React.RefObject<HTMLTextAreaElement>,
|
||||
isReinitializingGadget?: boolean
|
||||
) => {
|
||||
const { sendKeyboardEvent, sendKeypress, resetKeyboardState } = useKeyboard();
|
||||
const { sendKeyboardEvent, resetKeyboardState } = useKeyboard();
|
||||
const { setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive } = useHidStore();
|
||||
|
||||
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
||||
@@ -65,6 +65,11 @@ export const useKeyboardEvents = (
|
||||
}
|
||||
if (isReinitializingGadget) return;
|
||||
|
||||
if (e.repeat) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
const prev = useHidStore.getState();
|
||||
let code = e.code;
|
||||
@@ -94,15 +99,8 @@ export const useKeyboardEvents = (
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// Send per-key press event
|
||||
const hidKey = keys[code];
|
||||
if (hidKey !== undefined) {
|
||||
sendKeypress(hidKey, true);
|
||||
}
|
||||
|
||||
// Still update the full state for legacy compatibility and UI display
|
||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||
}, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]);
|
||||
}, [handleModifierKeys, remapCode, sendKeyboardEvent, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]);
|
||||
|
||||
const keyUpHandler = useCallback((e: KeyboardEvent) => {
|
||||
if (isOcrMode) return;
|
||||
@@ -124,15 +122,8 @@ export const useKeyboardEvents = (
|
||||
prev.activeModifiers.filter(k => k !== modifiers[code]),
|
||||
);
|
||||
|
||||
// Send per-key release event
|
||||
const hidKey = keys[code];
|
||||
if (hidKey !== undefined) {
|
||||
sendKeypress(hidKey, false);
|
||||
}
|
||||
|
||||
// Still update the full state for legacy compatibility and UI display
|
||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||
}, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]);
|
||||
}, [handleModifierKeys, remapCode, sendKeyboardEvent, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]);
|
||||
|
||||
const setupKeyboardEvents = useCallback(() => {
|
||||
const abortController = new AbortController();
|
||||
|
||||
@@ -34,7 +34,9 @@ export const useMouseEvents = (
|
||||
if (!force && settings.mouseMode !== "relative") return;
|
||||
// Don't send mouse events while reinitializing gadget
|
||||
if (isReinitializingGadget) return;
|
||||
send("relMouseReport", { dx: calcDelta(x), dy: calcDelta(y), buttons });
|
||||
const dx = calcDelta(x);
|
||||
const dy = calcDelta(y);
|
||||
send("relMouseReport", { dx, dy, buttons });
|
||||
setMouseMove({ x, y, buttons });
|
||||
},
|
||||
[send, setMouseMove, settings.mouseMode, isReinitializingGadget],
|
||||
|
||||
@@ -462,11 +462,6 @@ export default function PCHome() {
|
||||
setDiskChannel(diskDataChannel);
|
||||
};
|
||||
|
||||
const hidDataChannel = pc.createDataChannel("hid");
|
||||
hidDataChannel.onopen = () => {
|
||||
useRTCStore.getState().setHidChannel(hidDataChannel);
|
||||
};
|
||||
|
||||
setPeerConnection(pc);
|
||||
}, [
|
||||
forceHttp,
|
||||
|
||||
Reference in New Issue
Block a user