mirror of https://github.com/jetkvm/kvm.git
chore: simplify handshake of hid rpc
This commit is contained in:
parent
58b72add90
commit
eacc2a6621
|
@ -10,11 +10,13 @@ import {
|
|||
VideoState
|
||||
} from "@/hooks/stores";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import { useHidRpc } from "@/hooks/useHidRpc";
|
||||
|
||||
export default function InfoBar() {
|
||||
const { keysDownState } = useHidStore();
|
||||
const { mouseX, mouseY, mouseMove } = useMouseStore();
|
||||
|
||||
const { rpcHidReady } = useHidRpc();
|
||||
|
||||
const videoClientSize = useVideoStore(
|
||||
(state: VideoState) => `${Math.round(state.clientWidth)}x${Math.round(state.clientHeight)}`,
|
||||
);
|
||||
|
@ -100,6 +102,12 @@ export default function InfoBar() {
|
|||
<span className="text-xs">{hdmiState}</span>
|
||||
</div>
|
||||
)}
|
||||
{debugMode && (
|
||||
<div className="flex w-[156px] items-center gap-x-1">
|
||||
<span className="text-xs font-semibold">HidRPC State:</span>
|
||||
<span className="text-xs">{rpcHidReady ? "Ready" : "Not Ready"}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showPressedKeys && (
|
||||
<div className="flex items-center gap-x-1">
|
||||
|
|
|
@ -61,7 +61,7 @@ export default function WebRTCVideo() {
|
|||
|
||||
// Misc states and hooks
|
||||
const { send } = useJsonRpc();
|
||||
const { reportAbsMouseEvent, reportRelMouseEvent, handshakeCompleted } = useHidRpc();
|
||||
const { reportAbsMouseEvent, reportRelMouseEvent, rpcHidReady } = useHidRpc();
|
||||
|
||||
// Video-related
|
||||
const handleResize = useCallback(
|
||||
|
@ -226,7 +226,7 @@ export default function WebRTCVideo() {
|
|||
// if (x === 0 && y === 0 && buttons === 0) return;
|
||||
const dx = calcDelta(x);
|
||||
const dy = calcDelta(y);
|
||||
if (handshakeCompleted) {
|
||||
if (rpcHidReady) {
|
||||
reportRelMouseEvent(dx, dy, buttons);
|
||||
} else {
|
||||
send("relMouseReport", { dx, dy, buttons });
|
||||
|
@ -238,7 +238,7 @@ export default function WebRTCVideo() {
|
|||
reportRelMouseEvent,
|
||||
setMouseMove,
|
||||
settings.mouseMode,
|
||||
handshakeCompleted,
|
||||
rpcHidReady,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -257,7 +257,7 @@ export default function WebRTCVideo() {
|
|||
const sendAbsMouseMovement = useCallback(
|
||||
(x: number, y: number, buttons: number) => {
|
||||
if (settings.mouseMode !== "absolute") return;
|
||||
if (handshakeCompleted) {
|
||||
if (rpcHidReady) {
|
||||
reportAbsMouseEvent(x, y, buttons);
|
||||
} else {
|
||||
send("absMouseReport", { x, y, buttons });
|
||||
|
@ -270,7 +270,7 @@ export default function WebRTCVideo() {
|
|||
reportAbsMouseEvent,
|
||||
setMousePosition,
|
||||
settings.mouseMode,
|
||||
handshakeCompleted,
|
||||
rpcHidReady,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
@ -105,6 +105,9 @@ export interface RTCState {
|
|||
setRpcDataChannel: (channel: RTCDataChannel) => void;
|
||||
rpcDataChannel: RTCDataChannel | null;
|
||||
|
||||
rpcHidProtocolVersion: number | null;
|
||||
setRpcHidProtocolVersion: (version: number) => void;
|
||||
|
||||
setRpcHidChannel: (channel: RTCDataChannel) => void;
|
||||
rpcHidChannel: RTCDataChannel | null;
|
||||
|
||||
|
@ -154,6 +157,9 @@ export const useRTCStore = create<RTCState>(set => ({
|
|||
rpcDataChannel: null,
|
||||
setRpcDataChannel: (channel: RTCDataChannel) => set({ rpcDataChannel: channel }),
|
||||
|
||||
rpcHidProtocolVersion: null,
|
||||
setRpcHidProtocolVersion: (version: number) => set({ rpcHidProtocolVersion: version }),
|
||||
|
||||
rpcHidChannel: null,
|
||||
setRpcHidChannel: (channel: RTCDataChannel) => set({ rpcHidChannel: channel }),
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
|
||||
import { KeysDownState, useRTCStore } from "@/hooks/stores";
|
||||
import { KeyboardLedState, KeysDownState, useRTCStore } from "@/hooks/stores";
|
||||
|
||||
export const HID_RPC_MESSAGE_TYPES = {
|
||||
Handshake: 0x01,
|
||||
|
@ -40,6 +40,30 @@ const fromInt8ToUint8 = (n: number) => {
|
|||
return (n >> 0) & 0xFF;
|
||||
};
|
||||
|
||||
const keyboardLedStateMasks = {
|
||||
num_lock: 1 << 0,
|
||||
caps_lock: 1 << 1,
|
||||
scroll_lock: 1 << 2,
|
||||
compose: 1 << 3,
|
||||
kana: 1 << 4,
|
||||
shift: 1 << 6,
|
||||
}
|
||||
|
||||
export const toKeyboardLedState = (s: number): KeyboardLedState => {
|
||||
if (!withinUint8Range(s)) {
|
||||
throw new Error(`State ${s} is not within the uint8 range`);
|
||||
}
|
||||
|
||||
return {
|
||||
num_lock: (s & keyboardLedStateMasks.num_lock) !== 0,
|
||||
caps_lock: (s & keyboardLedStateMasks.caps_lock) !== 0,
|
||||
scroll_lock: (s & keyboardLedStateMasks.scroll_lock) !== 0,
|
||||
compose: (s & keyboardLedStateMasks.compose) !== 0, // TODO: check if this is correct
|
||||
kana: (s & keyboardLedStateMasks.kana) !== 0,
|
||||
shift: (s & keyboardLedStateMasks.shift) !== 0,
|
||||
} as KeyboardLedState;
|
||||
};
|
||||
|
||||
const toPointerReportEvent = (x: number, y: number, buttons: number) => {
|
||||
if (!withinUint8Range(buttons)) {
|
||||
throw new Error(`Buttons ${buttons} is not within the uint8 range`);
|
||||
|
@ -130,47 +154,49 @@ const unmarshalHidRpcMessage = (data: Uint8Array): HidRpcMessage | undefined =>
|
|||
};
|
||||
|
||||
export function useHidRpc(onHidRpcMessage?: (payload: HidRpcMessage) => void) {
|
||||
const { rpcHidChannel } = useRTCStore();
|
||||
const [handshakeCompleted, setHandshakeCompleted] = useState(false);
|
||||
const { rpcHidChannel, setRpcHidProtocolVersion, rpcHidProtocolVersion } = useRTCStore();
|
||||
const rpcHidReady = useMemo(() => {
|
||||
return rpcHidChannel?.readyState === "open" && rpcHidProtocolVersion !== null;
|
||||
}, [rpcHidChannel, rpcHidProtocolVersion]);
|
||||
|
||||
const reportKeyboardEvent = useCallback(
|
||||
(keys: number[], modifier: number) => {
|
||||
if (rpcHidChannel?.readyState !== "open") return;
|
||||
rpcHidChannel.send(toKeyboardReportEvent(keys, modifier));
|
||||
if (!rpcHidReady) return;
|
||||
rpcHidChannel?.send(toKeyboardReportEvent(keys, modifier));
|
||||
},
|
||||
[rpcHidChannel],
|
||||
[rpcHidChannel, rpcHidReady],
|
||||
);
|
||||
|
||||
const reportKeypressEvent = useCallback(
|
||||
(key: number, press: boolean) => {
|
||||
if (rpcHidChannel?.readyState !== "open") return;
|
||||
rpcHidChannel.send(toKeypressReportEvent(key, press));
|
||||
if (!rpcHidReady) return;
|
||||
rpcHidChannel?.send(toKeypressReportEvent(key, press));
|
||||
},
|
||||
[rpcHidChannel],
|
||||
[rpcHidChannel, rpcHidReady],
|
||||
);
|
||||
|
||||
const reportAbsMouseEvent = useCallback(
|
||||
(x: number, y: number, buttons: number) => {
|
||||
if (rpcHidChannel?.readyState !== "open") return;
|
||||
rpcHidChannel.send(toPointerReportEvent(x, y, buttons));
|
||||
if (!rpcHidReady) return;
|
||||
rpcHidChannel?.send(toPointerReportEvent(x, y, buttons));
|
||||
},
|
||||
[rpcHidChannel],
|
||||
[rpcHidChannel, rpcHidReady],
|
||||
);
|
||||
|
||||
const reportRelMouseEvent = useCallback(
|
||||
(dx: number, dy: number, buttons: number) => {
|
||||
if (rpcHidChannel?.readyState !== "open") return;
|
||||
rpcHidChannel.send(toMouseReportEvent(dx, dy, buttons));
|
||||
if (!rpcHidReady) return;
|
||||
rpcHidChannel?.send(toMouseReportEvent(dx, dy, buttons));
|
||||
},
|
||||
[rpcHidChannel],
|
||||
[rpcHidChannel, rpcHidReady],
|
||||
);
|
||||
|
||||
const doHandshake = useCallback(() => {
|
||||
if (handshakeCompleted) return;
|
||||
if (rpcHidProtocolVersion) return;
|
||||
if (!rpcHidChannel) return;
|
||||
|
||||
rpcHidChannel.send(toHandshakeMessage());
|
||||
}, [rpcHidChannel, handshakeCompleted]);
|
||||
rpcHidChannel?.send(toHandshakeMessage());
|
||||
}, [rpcHidChannel, rpcHidProtocolVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rpcHidChannel) return;
|
||||
|
@ -184,7 +210,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: HidRpcMessage) => void) {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log("Received HID RPC message", e.data);
|
||||
console.debug("Received HID RPC message", e.data);
|
||||
|
||||
const message = unmarshalHidRpcMessage(new Uint8Array(e.data));
|
||||
if (!message) {
|
||||
|
@ -193,7 +219,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: HidRpcMessage) => void) {
|
|||
}
|
||||
|
||||
if (message.type === HID_RPC_MESSAGE_TYPES.Handshake) {
|
||||
setHandshakeCompleted(true);
|
||||
setRpcHidProtocolVersion(1);
|
||||
}
|
||||
|
||||
onHidRpcMessage?.(message);
|
||||
|
@ -205,13 +231,21 @@ export function useHidRpc(onHidRpcMessage?: (payload: HidRpcMessage) => void) {
|
|||
rpcHidChannel.removeEventListener("message", messageHandler);
|
||||
};
|
||||
},
|
||||
[rpcHidChannel, onHidRpcMessage, setHandshakeCompleted, doHandshake]);
|
||||
[
|
||||
rpcHidChannel,
|
||||
onHidRpcMessage,
|
||||
setRpcHidProtocolVersion,
|
||||
doHandshake,
|
||||
rpcHidReady,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
reportKeyboardEvent,
|
||||
reportKeypressEvent,
|
||||
reportAbsMouseEvent,
|
||||
reportRelMouseEvent,
|
||||
handshakeCompleted,
|
||||
rpcHidProtocolVersion,
|
||||
rpcHidReady,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { hidKeyToModifierMask, keys, modifiers } from "@/keyboardMappings";
|
|||
|
||||
export default function useKeyboard() {
|
||||
const { send } = useJsonRpc();
|
||||
const { rpcDataChannel, rpcHidChannel } = useRTCStore();
|
||||
const { rpcDataChannel } = useRTCStore();
|
||||
const { keysDownState, setKeysDownState } = useHidStore();
|
||||
|
||||
// INTRODUCTION: The earlier version of the JetKVM device shipped with all keyboard state
|
||||
|
@ -23,7 +23,7 @@ export default function useKeyboard() {
|
|||
const { keyPressReportApiAvailable, setkeyPressReportApiAvailable } = useHidStore();
|
||||
|
||||
// HidRPC is a binary format for exchanging keyboard and mouse events
|
||||
const { reportKeyboardEvent, reportKeypressEvent } = useHidRpc((message) => {
|
||||
const { reportKeyboardEvent, reportKeypressEvent, rpcHidReady } = useHidRpc((message) => {
|
||||
if (message.type === HID_RPC_MESSAGE_TYPES.KeysDownState) {
|
||||
if (!message.keysDownState) {
|
||||
return;
|
||||
|
@ -40,11 +40,11 @@ export default function useKeyboard() {
|
|||
// or just accept the state if it does not support (returning no result)
|
||||
const sendKeyboardEvent = useCallback(
|
||||
async (state: KeysDownState) => {
|
||||
if (rpcDataChannel?.readyState !== "open" && rpcHidChannel?.readyState !== "open") return;
|
||||
if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return;
|
||||
|
||||
console.debug(`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`);
|
||||
|
||||
if (rpcHidChannel?.readyState === "open") {
|
||||
if (rpcHidReady) {
|
||||
console.debug("Sending keyboard report via HidRPC");
|
||||
reportKeyboardEvent(state.keys, state.modifier);
|
||||
return;
|
||||
|
@ -72,7 +72,7 @@ export default function useKeyboard() {
|
|||
},
|
||||
[
|
||||
rpcDataChannel?.readyState,
|
||||
rpcHidChannel?.readyState,
|
||||
rpcHidReady,
|
||||
send,
|
||||
reportKeyboardEvent,
|
||||
setKeysDownState,
|
||||
|
@ -88,11 +88,11 @@ export default function useKeyboard() {
|
|||
// in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices.
|
||||
const sendKeypressEvent = useCallback(
|
||||
async (key: number, press: boolean) => {
|
||||
if (rpcDataChannel?.readyState !== "open" && rpcHidChannel?.readyState !== "open") return;
|
||||
if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return;
|
||||
|
||||
console.debug(`Send keypressEvent key: ${key}, press: ${press}`);
|
||||
|
||||
if (rpcHidChannel?.readyState === "open") {
|
||||
if (rpcHidReady) {
|
||||
console.debug("Sending keypress event via HidRPC");
|
||||
reportKeypressEvent(key, press);
|
||||
return;
|
||||
|
@ -119,7 +119,7 @@ export default function useKeyboard() {
|
|||
},
|
||||
[
|
||||
rpcDataChannel?.readyState,
|
||||
rpcHidChannel?.readyState,
|
||||
rpcHidReady,
|
||||
send,
|
||||
setkeyPressReportApiAvailable,
|
||||
setKeysDownState,
|
||||
|
@ -176,10 +176,10 @@ export default function useKeyboard() {
|
|||
// It then sends the full keyboard state to the device.
|
||||
const handleKeyPress = useCallback(
|
||||
async (key: number, press: boolean) => {
|
||||
if (rpcDataChannel?.readyState !== "open" && rpcHidChannel?.readyState !== "open") return;
|
||||
if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return;
|
||||
if ((key || 0) === 0) return; // ignore zero key presses (they are bad mappings)
|
||||
|
||||
if (rpcHidChannel?.readyState === "open") {
|
||||
if (rpcHidReady) {
|
||||
console.debug("Sending keypress event via HidRPC");
|
||||
reportKeypressEvent(key, press);
|
||||
return;
|
||||
|
@ -204,7 +204,7 @@ export default function useKeyboard() {
|
|||
keysDownState,
|
||||
resetKeyboardState,
|
||||
rpcDataChannel?.readyState,
|
||||
rpcHidChannel?.readyState,
|
||||
rpcHidReady,
|
||||
sendKeyboardEvent,
|
||||
sendKeypressEvent,
|
||||
reportKeypressEvent,
|
||||
|
|
|
@ -28,6 +28,9 @@ export default defineConfig(({ mode, command }) => {
|
|||
|
||||
return {
|
||||
plugins,
|
||||
esbuild: {
|
||||
pure: ["console.debug"],
|
||||
},
|
||||
build: { outDir: isCloud ? "dist" : "../static" },
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
|
|
Loading…
Reference in New Issue