Compare commits

..

No commits in common. "113091ae1f55aeb2eda8032c5c1e3ad463bec358" and "867ed88c6e8534aa2ede1ad1b1d5e118615830da" have entirely different histories.

6 changed files with 69 additions and 24 deletions

View File

@ -225,8 +225,11 @@ export default function WebRTCVideo() {
); );
const relMouseMoveHandler = useMemo( const relMouseMoveHandler = useMemo(
() => getRelMouseMoveHandler(), () => getRelMouseMoveHandler({
[getRelMouseMoveHandler], isPointerLockActive,
isPointerLockPossible,
}),
[getRelMouseMoveHandler, isPointerLockActive, isPointerLockPossible],
); );
const mouseWheelHandler = useMemo( const mouseWheelHandler = useMemo(

View File

@ -461,6 +461,9 @@ export interface HidState {
keysDownState: KeysDownState; keysDownState: KeysDownState;
setKeysDownState: (state: KeysDownState) => void; setKeysDownState: (state: KeysDownState) => void;
keyPressReportApiAvailable: boolean;
setkeyPressReportApiAvailable: (available: boolean) => void;
isVirtualKeyboardEnabled: boolean; isVirtualKeyboardEnabled: boolean;
setVirtualKeyboardEnabled: (enabled: boolean) => void; setVirtualKeyboardEnabled: (enabled: boolean) => void;
@ -478,6 +481,9 @@ export const useHidStore = create<HidState>(set => ({
keysDownState: { modifier: 0, keys: [0,0,0,0,0,0] } as KeysDownState, keysDownState: { modifier: 0, keys: [0,0,0,0,0,0] } as KeysDownState,
setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }), setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }),
keyPressReportApiAvailable: true,
setkeyPressReportApiAvailable: (available: boolean) => set({ keyPressReportApiAvailable: available }),
isVirtualKeyboardEnabled: false, isVirtualKeyboardEnabled: false,
setVirtualKeyboardEnabled: (enabled: boolean): void => set({ isVirtualKeyboardEnabled: enabled }), setVirtualKeyboardEnabled: (enabled: boolean): void => set({ isVirtualKeyboardEnabled: enabled }),

View File

@ -81,11 +81,8 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
return; return;
} }
if (message.version > HID_RPC_VERSION) { if (message.version < HID_RPC_VERSION) {
// we assume that the UI is always using the latest version of the HID RPC protocol console.error("Server is using an older HID RPC version than the client", message);
// so we can't support this
// TODO: use capabilities to determine rather than version number
console.error("Server is using a newer HID RPC version than the client", message);
return; return;
} }

View File

@ -21,19 +21,23 @@ export default function useKeyboard() {
// 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.
const { keyPressReportApiAvailable, setkeyPressReportApiAvailable } = useHidStore();
const enableKeyPressReport = useCallback((reason: string) => {
if (keyPressReportApiAvailable) return;
console.debug(`Enable keyPressReport API because ${reason}`);
setkeyPressReportApiAvailable(true);
}, [setkeyPressReportApiAvailable, keyPressReportApiAvailable]);
// HidRPC is a binary format for exchanging keyboard and mouse events // HidRPC is a binary format for exchanging keyboard and mouse events
const { const { reportKeyboardEvent, reportKeypressEvent, rpcHidReady } = useHidRpc((message) => {
reportKeyboardEvent: sendKeyboardEventHidRpc,
reportKeypressEvent: sendKeypressEventHidRpc,
rpcHidReady,
} = useHidRpc((message) => {
switch (message.constructor) { switch (message.constructor) {
case KeysDownStateMessage: case KeysDownStateMessage:
setKeysDownState((message as KeysDownStateMessage).keysDownState); setKeysDownState((message as KeysDownStateMessage).keysDownState);
enableKeyPressReport("HidRPC:KeysDownStateMessage received");
break; break;
case KeyboardLedStateMessage: case KeyboardLedStateMessage:
setKeyboardLedState((message as KeyboardLedStateMessage).keyboardLedState); setKeyboardLedState((message as KeyboardLedStateMessage).keyboardLedState);
enableKeyPressReport("HidRPC:KeyboardLedStateMessage received");
break; break;
default: default:
break; break;
@ -52,7 +56,8 @@ export default function useKeyboard() {
if (rpcHidReady) { if (rpcHidReady) {
console.debug("Sending keyboard report via HidRPC"); console.debug("Sending keyboard report via HidRPC");
sendKeyboardEventHidRpc(state.keys, state.modifier); reportKeyboardEvent(state.keys, state.modifier);
enableKeyPressReport("HidRPC:KeyboardReport received");
return; return;
} }
@ -66,7 +71,28 @@ export default function useKeyboard() {
rpcDataChannel?.readyState, rpcDataChannel?.readyState,
rpcHidReady, rpcHidReady,
send, send,
sendKeyboardEventHidRpc, reportKeyboardEvent,
enableKeyPressReport,
],
);
// sendKeypressEvent is used to send a single key press/release event to the device.
// It sends the key and whether it is pressed or released.
// Older device version will not understand this request and will respond with
// an error with code -32601, which means that the RPC method name was not recognized.
// In that case we will switch to local key handling and update the keysDownState
// in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices.
const sendKeypressEvent = useCallback(
async (key: number, press: boolean) => {
console.debug(`Send keypressEvent key: ${key}, press: ${press}`);
if (!rpcHidReady) return;
reportKeypressEvent(key, press);
},
[
rpcHidReady,
reportKeypressEvent,
], ],
); );
@ -123,13 +149,14 @@ export default function useKeyboard() {
if ((key || 0) === 0) return; // ignore zero key presses (they are bad mappings) if ((key || 0) === 0) return; // ignore zero key presses (they are bad mappings)
if (rpcHidReady) { if (rpcHidReady) {
console.debug("Sending keypress event via HidRPC");
reportKeypressEvent(key, press);
return;
}
if (keyPressReportApiAvailable) {
// if the keyPress api is available, we can just send the key press event // if the keyPress api is available, we can just send the key press event
// sendKeypressEvent is used to send a single key press/release event to the device. sendKeypressEvent(key, press);
// It sends the key and whether it is pressed or released.
// Older device version doesn't support this API, so we will switch to local key handling
// In that case we will switch to local key handling and update the keysDownState
// in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices.
sendKeypressEventHidRpc(key, press);
} else { } else {
// if the keyPress api is not available, we need to handle the key locally // if the keyPress api is not available, we need to handle the key locally
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(keysDownState, key, press); const downState = simulateDeviceSideKeyHandlingForLegacyDevices(keysDownState, key, press);
@ -142,12 +169,14 @@ export default function useKeyboard() {
} }
}, },
[ [
rpcHidReady, keyPressReportApiAvailable,
keysDownState, keysDownState,
resetKeyboardState, resetKeyboardState,
rpcDataChannel?.readyState, rpcDataChannel?.readyState,
rpcHidReady,
sendKeyboardEvent, sendKeyboardEvent,
sendKeypressEventHidRpc, sendKeypressEvent,
reportKeypressEvent,
], ],
); );

View File

@ -13,6 +13,11 @@ export interface AbsMouseMoveHandlerProps {
videoHeight: number; videoHeight: number;
} }
export interface RelMouseMoveHandlerProps {
isPointerLockActive: boolean;
isPointerLockPossible: boolean;
}
export default function useMouse() { export default function useMouse() {
// states // states
const { setMousePosition, setMouseMove } = useMouseStore(); const { setMousePosition, setMouseMove } = useMouseStore();
@ -50,8 +55,9 @@ export default function useMouse() {
); );
const getRelMouseMoveHandler = useCallback( const getRelMouseMoveHandler = useCallback(
() => (e: MouseEvent) => { ({ isPointerLockActive, isPointerLockPossible }: RelMouseMoveHandlerProps) => (e: MouseEvent) => {
if (mouseMode !== "relative") return; if (mouseMode !== "relative") return;
if (isPointerLockActive === false && isPointerLockPossible) return;
// Send mouse movement // Send mouse movement
const { buttons } = e; const { buttons } = e;

View File

@ -583,6 +583,7 @@ export default function KvmIdRoute() {
const { const {
keyboardLedState, setKeyboardLedState, keyboardLedState, setKeyboardLedState,
keysDownState, setKeysDownState, setUsbState, keysDownState, setKeysDownState, setUsbState,
setkeyPressReportApiAvailable
} = useHidStore(); } = useHidStore();
const [hasUpdated, setHasUpdated] = useState(false); const [hasUpdated, setHasUpdated] = useState(false);
@ -620,6 +621,7 @@ export default function KvmIdRoute() {
const downState = resp.params as KeysDownState; const downState = resp.params as KeysDownState;
console.debug("Setting key down state:", downState); console.debug("Setting key down state:", downState);
setKeysDownState(downState); setKeysDownState(downState);
setkeyPressReportApiAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
} }
if (resp.method === "otaState") { if (resp.method === "otaState") {
@ -696,6 +698,7 @@ export default function KvmIdRoute() {
if (resp.error.code === -32601) { if (resp.error.code === -32601) {
// if we don't support key down state, we know key press is also not available // if we don't support key down state, we know key press is also not available
console.warn("Failed to get key down state, switching to old-school", resp.error); console.warn("Failed to get key down state, switching to old-school", resp.error);
setkeyPressReportApiAvailable(false);
} else { } else {
console.error("Failed to get key down state", resp.error); console.error("Failed to get key down state", resp.error);
} }
@ -703,10 +706,11 @@ export default function KvmIdRoute() {
const downState = resp.result as KeysDownState; const downState = resp.result as KeysDownState;
console.debug("Keyboard key down state", downState); console.debug("Keyboard key down state", downState);
setKeysDownState(downState); setKeysDownState(downState);
setkeyPressReportApiAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
} }
setNeedKeyDownState(false); setNeedKeyDownState(false);
}); });
}, [keysDownState, needKeyDownState, rpcDataChannel?.readyState, send, setKeysDownState]); }, [keysDownState, needKeyDownState, rpcDataChannel?.readyState, send, setkeyPressReportApiAvailable, setKeysDownState]);
// When the update is successful, we need to refresh the client javascript and show a success modal // When the update is successful, we need to refresh the client javascript and show a success modal
useEffect(() => { useEffect(() => {