mirror of https://github.com/jetkvm/kvm.git
refactor: send keypress as early as possible
This commit is contained in:
parent
5a3729befb
commit
5e18e08907
|
@ -1,5 +1,12 @@
|
||||||
import { useCallback, useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
hidErrorRollOver,
|
||||||
|
hidKeyBufferSize,
|
||||||
|
KeysDownState,
|
||||||
|
useHidStore,
|
||||||
|
useRTCStore,
|
||||||
|
} from "@/hooks/stores";
|
||||||
import {
|
import {
|
||||||
hidErrorRollOver,
|
hidErrorRollOver,
|
||||||
hidKeyBufferSize,
|
hidKeyBufferSize,
|
||||||
|
@ -54,8 +61,7 @@ export default function useKeyboard() {
|
||||||
// support the keyPressReport API. In that case, we need to handle the key presses locally
|
// support the keyPressReport API. In that case, we need to handle the key presses locally
|
||||||
// and send the full state to the device, so it can behave like a real USB HID keyboard.
|
// and send the full state to the device, so it can behave like a real USB HID keyboard.
|
||||||
// This flag indicates whether the keyPressReport API is available on the device which is
|
// This flag indicates whether the keyPressReport API is available on the device which is
|
||||||
// dynamically set when the device responds to the first key press event or reports its
|
// dynamically set when the device responds to the first key press event or reports its // keysDownState when queried since the keyPressReport was introduced together with the
|
||||||
// keysDownState when queried since the keyPressReport was introduced together with the
|
|
||||||
// getKeysDownState API.
|
// getKeysDownState API.
|
||||||
|
|
||||||
// HidRPC is a binary format for exchanging keyboard and mouse events
|
// HidRPC is a binary format for exchanging keyboard and mouse events
|
||||||
|
@ -66,6 +72,7 @@ export default function useKeyboard() {
|
||||||
cancelOngoingKeyboardMacro: cancelOngoingKeyboardMacroHidRpc,
|
cancelOngoingKeyboardMacro: cancelOngoingKeyboardMacroHidRpc,
|
||||||
reportKeypressKeepAlive: sendKeypressKeepAliveHidRpc,
|
reportKeypressKeepAlive: sendKeypressKeepAliveHidRpc,
|
||||||
rpcHidReady,
|
rpcHidReady,
|
||||||
|
} = useHidRpc(message => {
|
||||||
} = useHidRpc(message => {
|
} = useHidRpc(message => {
|
||||||
switch (message.constructor) {
|
switch (message.constructor) {
|
||||||
case KeysDownStateMessage:
|
case KeysDownStateMessage:
|
||||||
|
@ -93,6 +100,33 @@ export default function useKeyboard() {
|
||||||
// On older backends, we need to set the keysDownState manually since without the hidRpc API, the state doesn't trickle down from the backend
|
// On older backends, we need to set the keysDownState manually since without the hidRpc API, the state doesn't trickle down from the backend
|
||||||
setKeysDownState({ modifier, keys });
|
setKeysDownState({ modifier, keys });
|
||||||
});
|
});
|
||||||
|
// sendKeyboardEvent is used to send the full keyboard state to the device for macro handling
|
||||||
|
// and resetting keyboard state. It sends the keys currently pressed and the modifier state.
|
||||||
|
// The device will respond with the keysDownState if it supports the keyPressReport API
|
||||||
|
// or just accept the state if it does not support (returning no result)
|
||||||
|
const sendKeyboardEvent = useCallback(
|
||||||
|
async (state: KeysDownState) => {
|
||||||
|
if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return;
|
||||||
|
|
||||||
|
console.debug(
|
||||||
|
`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rpcHidReady) {
|
||||||
|
console.debug("Sending keyboard report via HidRPC");
|
||||||
|
sendKeyboardEventHidRpc(state.keys, state.modifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(
|
||||||
|
"keyboardReport",
|
||||||
|
{ keys: state.keys, modifier: state.modifier },
|
||||||
|
(resp: JsonRpcResponse) => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
console.error(`Failed to send keyboard report ${state}`, resp.error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[send, setKeysDownState],
|
[send, setKeysDownState],
|
||||||
);
|
);
|
||||||
|
@ -129,6 +163,8 @@ export default function useKeyboard() {
|
||||||
}
|
}
|
||||||
}, [rpcHidReady, sendKeyboardEventHidRpc, handleLegacyKeyboardReport]);
|
}, [rpcHidReady, sendKeyboardEventHidRpc, handleLegacyKeyboardReport]);
|
||||||
|
|
||||||
|
[rpcDataChannel?.readyState, rpcHidReady, send, sendKeyboardEventHidRpc],
|
||||||
|
);
|
||||||
|
|
||||||
// executeMacro is used to execute a macro consisting of multiple steps.
|
// executeMacro is used to execute a macro consisting of multiple steps.
|
||||||
// Each step can have multiple keys, multiple modifiers and a delay.
|
// Each step can have multiple keys, multiple modifiers and a delay.
|
||||||
|
@ -136,13 +172,17 @@ export default function useKeyboard() {
|
||||||
// After the delay, the keys and modifiers are released and the next step is executed.
|
// After the delay, the keys and modifiers are released and the next step is executed.
|
||||||
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
||||||
// A small pause is added between steps to ensure that the device can process the events.
|
// A small pause is added between steps to ensure that the device can process the events.
|
||||||
const executeMacroRemote = useCallback(async (steps: MacroSteps) => {
|
const executeMacroRemote = useCallback(async (
|
||||||
|
steps: MacroSteps,
|
||||||
|
) => {
|
||||||
const macro: KeyboardMacroStep[] = [];
|
const macro: KeyboardMacroStep[] = [];
|
||||||
|
|
||||||
for (const [_, step] of steps.entries()) {
|
for (const [_, step] of steps.entries()) {
|
||||||
const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean);
|
const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean);
|
||||||
const modifierMask: number = (step.modifiers || [])
|
const modifierMask: number = (step.modifiers || [])
|
||||||
|
|
||||||
.map(mod => modifiers[mod])
|
.map(mod => modifiers[mod])
|
||||||
|
|
||||||
.reduce((acc, val) => acc + val, 0);
|
.reduce((acc, val) => acc + val, 0);
|
||||||
|
|
||||||
// If the step has keys and/or modifiers, press them and hold for the delay
|
// If the step has keys and/or modifiers, press them and hold for the delay
|
||||||
|
@ -237,6 +277,8 @@ export default function useKeyboard() {
|
||||||
clearInterval(keepAliveTimerRef.current);
|
clearInterval(keepAliveTimerRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendKeypressKeepAliveHidRpc();
|
||||||
|
|
||||||
// Create new interval timer
|
// Create new interval timer
|
||||||
keepAliveTimerRef.current = setInterval(() => {
|
keepAliveTimerRef.current = setInterval(() => {
|
||||||
sendKeypressKeepAliveHidRpc();
|
sendKeypressKeepAliveHidRpc();
|
||||||
|
@ -295,9 +337,13 @@ export default function useKeyboard() {
|
||||||
// 1. Calculate the state
|
// 1. Calculate the state
|
||||||
// 2. Send the newly calculated state to the device
|
// 2. Send the newly calculated state to the device
|
||||||
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(
|
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(
|
||||||
|
|
||||||
keysDownState,
|
keysDownState,
|
||||||
|
|
||||||
key,
|
key,
|
||||||
|
|
||||||
press,
|
press,
|
||||||
|
,
|
||||||
);
|
);
|
||||||
|
|
||||||
handleLegacyKeyboardReport(downState.keys, downState.modifier);
|
handleLegacyKeyboardReport(downState.keys, downState.modifier);
|
||||||
|
@ -323,9 +369,13 @@ export default function useKeyboard() {
|
||||||
|
|
||||||
// IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists
|
// IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists
|
||||||
function simulateDeviceSideKeyHandlingForLegacyDevices(
|
function simulateDeviceSideKeyHandlingForLegacyDevices(
|
||||||
|
|
||||||
state: KeysDownState,
|
state: KeysDownState,
|
||||||
|
|
||||||
key: number,
|
key: number,
|
||||||
|
|
||||||
press: boolean,
|
press: boolean,
|
||||||
|
,
|
||||||
): KeysDownState {
|
): KeysDownState {
|
||||||
// IMPORTANT: This code parallels the logic in the kernel's hid-gadget driver
|
// IMPORTANT: This code parallels the logic in the kernel's hid-gadget driver
|
||||||
// for handling key presses and releases. It ensures that the USB gadget
|
// for handling key presses and releases. It ensures that the USB gadget
|
||||||
|
@ -372,14 +422,16 @@ export default function useKeyboard() {
|
||||||
if (overrun) {
|
if (overrun) {
|
||||||
if (press) {
|
if (press) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|
||||||
`keyboard buffer overflow current keys ${keys}, key: ${key} not added`,
|
`keyboard buffer overflow current keys ${keys}, key: ${key} not added`,
|
||||||
|
,
|
||||||
);
|
);
|
||||||
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
||||||
keys.length = hidKeyBufferSize;
|
keys.length = hidKeyBufferSize;
|
||||||
keys.fill(hidErrorRollOver);
|
keys.fill(hidErrorRollOver);
|
||||||
} else {
|
} else {
|
||||||
// If we are releasing a key, and we didn't find it in a slot, who cares?
|
// If we are releasing a key, and we didn't find it in a slot, who cares?
|
||||||
console.debug(`key ${key} not found in buffer, nothing to release`);
|
console.debug(`key ${key} not found in buffer, nothing to release`);;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue