Compare commits

..

2 Commits

Author SHA1 Message Date
Marc Brooks 5e6da5b782
Merge 94f6bbd27a into 608f69db13 2025-08-15 21:23:20 +00:00
Marc Brooks 94f6bbd27a
Add ability to track modifier state on the device
Remove LED sync source and add keypress reporting
We return the modifiers as the valid bitmask so that the VirtualKeyboard can represent the correct keys as down. This is important when we have strokes like Left-Control + Right-Control + Keypad-1 (used in switching KVMs and such)
Fix handling of meta keys in client
Ran go modernize
Morphs Interface{} to any
Ranges over SplitSeq and FieldSeq for iterating splits
Used min for end calculation remote_mount.Read
Used range 16 in wol.createMagicPacket
DID NOT apply the Omitempty cleanup.
Use the KeysDownState for the infobar
Strong typed in the typescript realm.
Enable still working with devices that haven't been upgraded
Return the KeysDownState from keyboardReport
Clear out the hidErrorRollOver once sent to reset the keyboard to nothing down.
Handles the returned KeysDownState from keyboardReport
Now passes all logic through handleKeyPress.
If we get a state back from a keyboardReport, use it and also enable keypressReport because we now know it's an upgraded device.
Add documentation on the legacy support.
Cleanup react state management to enable upgrading Zustand
2025-08-15 16:21:43 -05:00
5 changed files with 96 additions and 87 deletions

26
ui/package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "kvm-ui", "name": "kvm-ui",
"version": "2025.08.15.001", "version": "2025.08.15.2119",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "kvm-ui", "name": "kvm-ui",
"version": "2025.08.15.001", "version": "2025.08.15.2119",
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.7", "@headlessui/react": "^2.2.7",
"@headlessui/tailwindcss": "^0.2.2", "@headlessui/tailwindcss": "^0.2.2",
@ -28,10 +28,10 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-animate-height": "^3.2.3", "react-animate-height": "^3.2.3",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-simple-keyboard": "^3.8.109", "react-simple-keyboard": "^3.8.111",
"react-use-websocket": "^4.13.0", "react-use-websocket": "^4.13.0",
"react-xtermjs": "^1.0.10", "react-xtermjs": "^1.0.10",
"recharts": "^2.15.3", "recharts": "^2.15.3",
@ -3156,9 +3156,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.201", "version": "1.5.202",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.201.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.202.tgz",
"integrity": "sha512-ZG65vsrLClodGqywuigc+7m0gr4ISoTQttfVh7nfpLv0M7SIwF4WbFNEOywcqTiujs12AUeeXbFyQieDICAIxg==", "integrity": "sha512-NxbYjRmiHcHXV1Ws3fWUW+SLb62isauajk45LUJ/HgIOkUA7jLZu/X2Iif+X9FBNK8QkF9Zb4Q2mcwXCcY30mg==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -5784,9 +5784,9 @@
} }
}, },
"node_modules/react-hot-toast": { "node_modules/react-hot-toast": {
"version": "2.5.2", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
"integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.1.3", "csstype": "^3.1.3",
@ -5848,9 +5848,9 @@
} }
}, },
"node_modules/react-simple-keyboard": { "node_modules/react-simple-keyboard": {
"version": "3.8.109", "version": "3.8.111",
"resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.8.109.tgz", "resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.8.111.tgz",
"integrity": "sha512-FLlivKL4tb5G2cWOo2slOrMEkzzFX0Yg8P7k5qzisN8+TnqUPq+8G7N8D2+0oVkSmfeqZn6PyLCurGSitK4QIQ==", "integrity": "sha512-zR6qeGeH1bhaP8GDMLwRBqpyU98jGUOmuNKZT6Z0056kjR4EVRo99Z/eVCafN0ySKpweQ6x0gVAjxkegy6EDFg==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",

View File

@ -1,7 +1,7 @@
{ {
"name": "kvm-ui", "name": "kvm-ui",
"private": true, "private": true,
"version": "2025.08.15.001", "version": "2025.08.15.2119",
"type": "module", "type": "module",
"engines": { "engines": {
"node": "22.15.0" "node": "22.15.0"
@ -39,10 +39,10 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-animate-height": "^3.2.3", "react-animate-height": "^3.2.3",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-simple-keyboard": "^3.8.109", "react-simple-keyboard": "^3.8.111",
"react-use-websocket": "^4.13.0", "react-use-websocket": "^4.13.0",
"react-xtermjs": "^1.0.10", "react-xtermjs": "^1.0.10",
"recharts": "^2.15.3", "recharts": "^2.15.3",

View File

@ -36,7 +36,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
const { rpcDataChannel } = useRTCStore(); const { rpcDataChannel } = useRTCStore();
const send = useCallback( const send = useCallback(
(method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => { async (method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
requestCounter++; requestCounter++;
const payload = { jsonrpc: "2.0", method, params, id: requestCounter }; const payload = { jsonrpc: "2.0", method, params, id: requestCounter };
@ -61,7 +61,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
return; return;
} }
if ("error" in payload) console.error(payload.error); if ("error" in payload) console.error("RPC error", payload);
if (!payload.id) return; if (!payload.id) return;
const callback = callbackStore.get(payload.id); const callback = callbackStore.get(payload.id);

View File

@ -28,7 +28,7 @@ export default function useKeyboard() {
// The device will respond with the keysDownState if it supports the keyPressReport API // 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) // or just accept the state if it does not support (returning no result)
const sendKeyboardEvent = useCallback( const sendKeyboardEvent = useCallback(
(state: KeysDownState) => { async (state: KeysDownState) => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
console.debug(`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`); console.debug(`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`);
@ -45,13 +45,14 @@ export default function useKeyboard() {
setkeyPressReportApiAvailable(true); // if they returned a keysDownState, we ALSO know they also support keyPressReport setkeyPressReportApiAvailable(true); // if they returned a keysDownState, we ALSO know they also support keyPressReport
} else { } else {
// older devices versions do not return the keyDownState // older devices versions do not return the keyDownState
setKeysDownState(state); // we just pretend they accepted what we sent // so we just pretend they accepted what we sent
setKeysDownState(state);
setkeyPressReportApiAvailable(false); // we ALSO know they do not support keyPressReport setkeyPressReportApiAvailable(false); // we ALSO know they do not support keyPressReport
} }
} }
}); });
}, },
[rpcDataChannel?.readyState, send, setkeyPressReportApiAvailable, setKeysDownState], [rpcDataChannel?.readyState, send, setKeysDownState, setkeyPressReportApiAvailable],
); );
// sendKeypressEvent is used to send a single key press/release event to the device. // sendKeypressEvent is used to send a single key press/release event to the device.
@ -61,7 +62,7 @@ export default function useKeyboard() {
// In that case we will switch to local key handling and update the keysDownState // In that case we will switch to local key handling and update the keysDownState
// in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices. // in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices.
const sendKeypressEvent = useCallback( const sendKeypressEvent = useCallback(
(key: number, press: boolean) => { async (key: number, press: boolean) => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
console.debug(`Send keypressEvent key: ${key}, press: ${press}`); console.debug(`Send keypressEvent key: ${key}, press: ${press}`);
@ -90,12 +91,15 @@ export default function useKeyboard() {
// resetKeyboardState is used to reset the keyboard state to no keys pressed and no modifiers. // resetKeyboardState is used to reset the keyboard state to no keys pressed and no modifiers.
// This is useful for macros and when the browser loses focus to ensure that the keyboard state // This is useful for macros and when the browser loses focus to ensure that the keyboard state
// is clean. // is clean.
const resetKeyboardState = useCallback(() => { const resetKeyboardState = useCallback(
console.debug("Resetting keyboard state"); async () => {
keysDownState.keys.fill(0); // Reset the keys buffer to zeros console.debug("Resetting keyboard state");
keysDownState.modifier = 0; // Reset the modifier state to zero // Reset the keys buffer to zeros and the modifier state to zero
sendKeyboardEvent(keysDownState); keysDownState.keys.length = hidKeyBufferSize;
}, [keysDownState, sendKeyboardEvent]); keysDownState.keys.fill(0);
keysDownState.modifier = 0;
sendKeyboardEvent(keysDownState);
}, [keysDownState, sendKeyboardEvent]);
// 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.
@ -133,7 +137,7 @@ export default function useKeyboard() {
// handling for legacy devices and updates the keysDownState accordingly. // handling for legacy devices and updates the keysDownState accordingly.
// It then sends the full keyboard state to the device. // It then sends the full keyboard state to the device.
const handleKeyPress = useCallback( const handleKeyPress = useCallback(
(key: number, press: boolean) => { async (key: number, press: boolean) => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
if (keyPressReportApiAvailable) { if (keyPressReportApiAvailable) {
@ -143,9 +147,14 @@ export default function useKeyboard() {
// 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);
sendKeyboardEvent(downState); // then we send the full state sendKeyboardEvent(downState); // then we send the full state
// if we just sent ErrorRollOver, reset to empty state
if (downState.keys[0] === hidErrorRollOver) {
resetKeyboardState();
}
} }
}, },
[keyPressReportApiAvailable, keysDownState, rpcDataChannel?.readyState, sendKeyboardEvent, sendKeypressEvent], [keyPressReportApiAvailable, keysDownState, resetKeyboardState, rpcDataChannel?.readyState, sendKeyboardEvent, sendKeypressEvent],
); );
// IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists // IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists
@ -154,8 +163,8 @@ export default function useKeyboard() {
// for handling key presses and releases. It ensures that the USB gadget // for handling key presses and releases. It ensures that the USB gadget
// behaves similarly to a real USB HID keyboard. This logic is paralleled // behaves similarly to a real USB HID keyboard. This logic is paralleled
// in the device-side code in hid_keyboard.go so make sure to keep them in sync. // in the device-side code in hid_keyboard.go so make sure to keep them in sync.
const keys = state.keys;
let modifiers = state.modifier; let modifiers = state.modifier;
const keys = state.keys;
const modifierMask = hidKeyToModifierMask[key] || 0; const modifierMask = hidKeyToModifierMask[key] || 0;
if (modifierMask !== 0) { if (modifierMask !== 0) {
@ -163,7 +172,6 @@ export default function useKeyboard() {
// by setting or clearing the corresponding bit in the modifier byte. // by setting or clearing the corresponding bit in the modifier byte.
// This allows us to track the state of dynamic modifier keys like // This allows us to track the state of dynamic modifier keys like
// Shift, Control, Alt, and Super. // Shift, Control, Alt, and Super.
console.debug(`Handling modifier key: ${key}, press: ${press}, current modifiers: ${modifiers}, modifier mask: ${modifierMask}`);
if (press) { if (press) {
modifiers |= modifierMask; modifiers |= modifierMask;
} else { } else {
@ -173,34 +181,35 @@ export default function useKeyboard() {
// handle other keys that are not modifier keys by placing or removing them // handle other keys that are not modifier keys by placing or removing them
// from the key buffer since the buffer tracks currently pressed keys // from the key buffer since the buffer tracks currently pressed keys
let overrun = true; let overrun = true;
for (let i = 0; i < hidKeyBufferSize && overrun; i++) { for (let i = 0; i < hidKeyBufferSize; i++) {
// If we find the key in the buffer the buffer, we either remove it (if press is false) // If we find the key in the buffer the buffer, we either remove it (if press is false)
// or do nothing (if down is true) because the buffer tracks currently pressed keys // or do nothing (if down is true) because the buffer tracks currently pressed keys
// and if we find a zero byte, we can place the key there (if press is true) // and if we find a zero byte, we can place the key there (if press is true)
if (keys[i] == key || keys[i] == 0) { if (keys[i] === key || keys[i] === 0) {
if (press) { if (press) {
keys[i] = key // overwrites the zero byte or the same key if already pressed keys[i] = key // overwrites the zero byte or the same key if already pressed
} else { } else {
// we are releasing the key, remove it from the buffer // we are releasing the key, remove it from the buffer
if (keys[i] != 0) { if (keys[i] !== 0) {
keys.splice(i, 1); keys.splice(i, 1);
keys.push(0); // add a zero at the end keys.push(0); // add a zero at the end
} }
} }
overrun = false // We found a slot for the key overrun = false; // We found a slot for the key
break;
} }
}
// If we reach here it means we didn't find an empty slot or the key in the buffer // If we reach here it means we didn't find an empty slot or the key in the buffer
if (overrun) { if (overrun) {
if (press) { if (press) {
console.warn(`keyboard buffer overflow, key: ${key} not added`); console.warn(`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 = 6; 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`)
}
} }
} }
} }

View File

@ -248,27 +248,27 @@ export default function KvmIdRoute() {
reconnectAttempts: 15, reconnectAttempts: 15,
reconnectInterval: 1000, reconnectInterval: 1000,
onReconnectStop: () => { onReconnectStop: () => {
console.log("Reconnect stopped"); console.debug("Reconnect stopped");
cleanupAndStopReconnecting(); cleanupAndStopReconnecting();
}, },
shouldReconnect(event) { shouldReconnect(event) {
console.log("[Websocket] shouldReconnect", event); console.debug("[Websocket] shouldReconnect", event);
// TODO: Why true? // TODO: Why true?
return true; return true;
}, },
onClose(event) { onClose(event) {
console.log("[Websocket] onClose", event); console.debug("[Websocket] onClose", event);
// We don't want to close everything down, we wait for the reconnect to stop instead // We don't want to close everything down, we wait for the reconnect to stop instead
}, },
onError(event) { onError(event) {
console.log("[Websocket] onError", event); console.error("[Websocket] onError", event);
// We don't want to close everything down, we wait for the reconnect to stop instead // We don't want to close everything down, we wait for the reconnect to stop instead
}, },
onOpen() { onOpen() {
console.log("[Websocket] onOpen"); console.debug("[Websocket] onOpen");
}, },
onMessage: message => { onMessage: message => {
@ -290,8 +290,8 @@ export default function KvmIdRoute() {
const parsedMessage = JSON.parse(message.data); const parsedMessage = JSON.parse(message.data);
if (parsedMessage.type === "device-metadata") { if (parsedMessage.type === "device-metadata") {
const { deviceVersion } = parsedMessage.data; const { deviceVersion } = parsedMessage.data;
console.log("[Websocket] Received device-metadata message"); console.debug("[Websocket] Received device-metadata message");
console.log("[Websocket] Device version", deviceVersion); console.debug("[Websocket] Device version", deviceVersion);
// If the device version is not set, we can assume the device is using the legacy signaling // If the device version is not set, we can assume the device is using the legacy signaling
if (!deviceVersion) { if (!deviceVersion) {
console.log("[Websocket] Device is using legacy signaling"); console.log("[Websocket] Device is using legacy signaling");
@ -309,7 +309,7 @@ export default function KvmIdRoute() {
if (!peerConnection) return; if (!peerConnection) return;
if (parsedMessage.type === "answer") { if (parsedMessage.type === "answer") {
console.log("[Websocket] Received answer"); console.debug("[Websocket] Received answer");
const readyForOffer = const readyForOffer =
// If we're making an offer, we don't want to accept an answer // If we're making an offer, we don't want to accept an answer
!makingOffer && !makingOffer &&
@ -323,7 +323,7 @@ export default function KvmIdRoute() {
// Set so we don't accept an answer while we're setting the remote description // Set so we don't accept an answer while we're setting the remote description
isSettingRemoteAnswerPending.current = parsedMessage.type === "answer"; isSettingRemoteAnswerPending.current = parsedMessage.type === "answer";
console.log( console.debug(
"[Websocket] Setting remote answer pending", "[Websocket] Setting remote answer pending",
isSettingRemoteAnswerPending.current, isSettingRemoteAnswerPending.current,
); );
@ -339,7 +339,7 @@ export default function KvmIdRoute() {
// Reset the remote answer pending flag // Reset the remote answer pending flag
isSettingRemoteAnswerPending.current = false; isSettingRemoteAnswerPending.current = false;
} else if (parsedMessage.type === "new-ice-candidate") { } else if (parsedMessage.type === "new-ice-candidate") {
console.log("[Websocket] Received new-ice-candidate"); console.debug("[Websocket] Received new-ice-candidate");
const candidate = parsedMessage.data; const candidate = parsedMessage.data;
peerConnection.addIceCandidate(candidate); peerConnection.addIceCandidate(candidate);
} }
@ -385,7 +385,7 @@ export default function KvmIdRoute() {
return; return;
} }
console.log("Successfully got Remote Session Description. Setting."); console.debug("Successfully got Remote Session Description. Setting.");
setLoadingMessage("Setting remote session description..."); setLoadingMessage("Setting remote session description...");
const decodedSd = atob(json.sd); const decodedSd = atob(json.sd);
@ -396,13 +396,13 @@ export default function KvmIdRoute() {
); );
const setupPeerConnection = useCallback(async () => { const setupPeerConnection = useCallback(async () => {
console.log("[setupPeerConnection] Setting up peer connection"); console.debug("[setupPeerConnection] Setting up peer connection");
setConnectionFailed(false); setConnectionFailed(false);
setLoadingMessage("Connecting to device..."); setLoadingMessage("Connecting to device...");
let pc: RTCPeerConnection; let pc: RTCPeerConnection;
try { try {
console.log("[setupPeerConnection] Creating peer connection"); console.debug("[setupPeerConnection] Creating peer connection");
setLoadingMessage("Creating peer connection..."); setLoadingMessage("Creating peer connection...");
pc = new RTCPeerConnection({ pc = new RTCPeerConnection({
// We only use STUN or TURN servers if we're in the cloud // We only use STUN or TURN servers if we're in the cloud
@ -412,7 +412,7 @@ export default function KvmIdRoute() {
}); });
setPeerConnectionState(pc.connectionState); setPeerConnectionState(pc.connectionState);
console.log("[setupPeerConnection] Peer connection created", pc); console.debug("[setupPeerConnection] Peer connection created", pc);
setLoadingMessage("Setting up connection to device..."); setLoadingMessage("Setting up connection to device...");
} catch (e) { } catch (e) {
console.error(`[setupPeerConnection] Error creating peer connection: ${e}`); console.error(`[setupPeerConnection] Error creating peer connection: ${e}`);
@ -424,13 +424,13 @@ export default function KvmIdRoute() {
// Set up event listeners and data channels // Set up event listeners and data channels
pc.onconnectionstatechange = () => { pc.onconnectionstatechange = () => {
console.log("[setupPeerConnection] Connection state changed", pc.connectionState); console.debug("[setupPeerConnection] Connection state changed", pc.connectionState);
setPeerConnectionState(pc.connectionState); setPeerConnectionState(pc.connectionState);
}; };
pc.onnegotiationneeded = async () => { pc.onnegotiationneeded = async () => {
try { try {
console.log("[setupPeerConnection] Creating offer"); console.debug("[setupPeerConnection] Creating offer");
makingOffer.current = true; makingOffer.current = true;
const offer = await pc.createOffer(); const offer = await pc.createOffer();
@ -462,7 +462,7 @@ export default function KvmIdRoute() {
pc.onicegatheringstatechange = event => { pc.onicegatheringstatechange = event => {
const pc = event.currentTarget as RTCPeerConnection; const pc = event.currentTarget as RTCPeerConnection;
if (pc.iceGatheringState === "complete") { if (pc.iceGatheringState === "complete") {
console.log("ICE Gathering completed"); console.debug("ICE Gathering completed");
setLoadingMessage("ICE Gathering completed"); setLoadingMessage("ICE Gathering completed");
if (isLegacySignalingEnabled.current) { if (isLegacySignalingEnabled.current) {
@ -470,7 +470,7 @@ export default function KvmIdRoute() {
legacyHTTPSignaling(pc); legacyHTTPSignaling(pc);
} }
} else if (pc.iceGatheringState === "gathering") { } else if (pc.iceGatheringState === "gathering") {
console.log("ICE Gathering Started"); console.debug("ICE Gathering Started");
setLoadingMessage("Gathering ICE candidates..."); setLoadingMessage("Gathering ICE candidates...");
} }
}; };
@ -597,11 +597,15 @@ export default function KvmIdRoute() {
} }
if (resp.method === "usbState") { if (resp.method === "usbState") {
setUsbState(resp.params as unknown as USBStates); const usbState = resp.params as unknown as USBStates;
console.debug("Setting USB state", usbState);
setUsbState(usbState);
} }
if (resp.method === "videoInputState") { if (resp.method === "videoInputState") {
setHdmiState(resp.params as Parameters<VideoState["setHdmiState"]>[0]); const hdmiState = resp.params as Parameters<VideoState["setHdmiState"]>[0];
console.debug("Setting HDMI state", hdmiState);
setHdmiState(hdmiState);
} }
if (resp.method === "networkState") { if (resp.method === "networkState") {
@ -617,16 +621,14 @@ export default function KvmIdRoute() {
if (resp.method === "keysDownState") { if (resp.method === "keysDownState") {
const downState = resp.params as KeysDownState; const downState = resp.params as KeysDownState;
console.debug("Setting key down state:", downState);
if (downState) { setKeysDownState(downState);
console.debug("Setting key down state:", downState); setkeyPressReportApiAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
setKeysDownState(downState);
setkeyPressReportApiAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
}
} }
if (resp.method === "otaState") { if (resp.method === "otaState") {
const otaState = resp.params as OtaState; const otaState = resp.params as OtaState;
console.debug("Setting OTA state", otaState);
setOtaState(otaState); setOtaState(otaState);
if (otaState.updating === true) { if (otaState.updating === true) {
@ -654,9 +656,12 @@ export default function KvmIdRoute() {
useEffect(() => { useEffect(() => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
console.log("Requesting video state");
send("getVideoState", {}, (resp: JsonRpcResponse) => { send("getVideoState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return; if ("error" in resp) return;
setHdmiState(resp.result as Parameters<VideoState["setHdmiState"]>[0]); const hdmiState = resp.result as Parameters<VideoState["setHdmiState"]>[0];
console.debug("Setting HDMI state", hdmiState);
setHdmiState(hdmiState);
}); });
}, [rpcDataChannel?.readyState, send, setHdmiState]); }, [rpcDataChannel?.readyState, send, setHdmiState]);
@ -672,12 +677,10 @@ export default function KvmIdRoute() {
if ("error" in resp) { if ("error" in resp) {
console.error("Failed to get keyboard led state", resp.error); console.error("Failed to get keyboard led state", resp.error);
return; return;
} } else {
const ledState = resp.result as KeyboardLedState; const ledState = resp.result as KeyboardLedState;
console.debug("Keyboard led state: ", ledState);
if (ledState) { setKeyboardLedState(ledState);
console.debug("Keyboard led state: ", resp.result);
setKeyboardLedState(resp.result as KeyboardLedState);
} }
setNeedLedState(false); setNeedLedState(false);
}); });
@ -696,19 +699,16 @@ export default function KvmIdRoute() {
// -32601 means the method is not supported // -32601 means the method is not supported
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.error("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); setkeyPressReportApiAvailable(false);
} else { } else {
console.error("Failed to get key down state", resp.error); console.error("Failed to get key down state", resp.error);
} }
} else { } else {
const downState = resp.result as KeysDownState; const downState = resp.result as KeysDownState;
console.debug("Keyboard key down state", downState);
if (downState) { setKeysDownState(downState);
console.debug("Keyboard key down state", downState); setkeyPressReportApiAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
setKeysDownState(downState);
setkeyPressReportApiAvailable(true); // if they returned a keyDownState, we know they also support keyPressReport
}
} }
setNeedKeyDownState(false); setNeedKeyDownState(false);
}); });