Use the KeysDownState for the infobar

Strong typed in the typescript realm.
This commit is contained in:
Marc Brooks 2025-08-13 18:10:31 -05:00
parent b3d8c3b77d
commit 1e57f4bf4f
No known key found for this signature in database
GPG Key ID: 583A6AF2D6AE1DC6
13 changed files with 230 additions and 208 deletions

View File

@ -209,7 +209,7 @@ func (u *UsbGadget) listenKeyboardEvents() {
} }
u.resetLogSuppressionCounter("keyboardHidFileRead") u.resetLogSuppressionCounter("keyboardHidFileRead")
l.Trace().Int("n", n).Bytes("buf", buf).Msg("got data from keyboard") l.Trace().Int("n", n).Uints8("buf", buf).Msg("got data from keyboard")
if n != 1 { if n != 1 {
l.Trace().Int("n", n).Msg("expected 1 byte, got") l.Trace().Int("n", n).Msg("expected 1 byte, got")
continue continue
@ -250,7 +250,7 @@ func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
return err return err
} }
_, err := u.keyboardHidFile.Write(append([]byte{modifier, 0x00}, keys[:]...)) _, err := u.keyboardHidFile.Write(append([]byte{modifier, 0x00}, keys[:hidKeyBufferSize]...))
if err != nil { if err != nil {
u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0") u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
u.keyboardHidFile.Close() u.keyboardHidFile.Close()
@ -289,14 +289,8 @@ const (
RightSuper = 0xE7 // Right GUI (e.g. Windows key, Apple Command key) RightSuper = 0xE7 // Right GUI (e.g. Windows key, Apple Command key)
) )
// KeyCodeMask maps a key code to its corresponding bit mask
type KeyCodeMask struct {
KeyCode byte
Mask byte
}
// KeyCodeToMaskMap is a slice of KeyCodeMask for quick lookup // KeyCodeToMaskMap is a slice of KeyCodeMask for quick lookup
var KeyCodeToMaskMap = map[uint8]uint8{ var KeyCodeToMaskMap = map[byte]byte{
LeftControl: ModifierMaskLeftControl, LeftControl: ModifierMaskLeftControl,
LeftShift: ModifierMaskLeftShift, LeftShift: ModifierMaskLeftShift,
LeftAlt: ModifierMaskLeftAlt, LeftAlt: ModifierMaskLeftAlt,
@ -314,7 +308,7 @@ func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error)
var state = u.keysDownState var state = u.keysDownState
modifier := state.Modifier modifier := state.Modifier
keys := state.Keys[:] keys := append([]byte(nil), state.Keys...)
if mask, exists := KeyCodeToMaskMap[key]; exists { if mask, exists := KeyCodeToMaskMap[key]; exists {
// If the key is a modifier key, we update the keyboardModifier state // If the key is a modifier key, we update the keyboardModifier state
@ -364,13 +358,15 @@ func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error)
} }
if err := u.keyboardWriteHidFile(modifier, keys); err != nil { if err := u.keyboardWriteHidFile(modifier, keys); err != nil {
u.log.Warn().Uint8("modifier", modifier).Bytes("keys", keys).Msg("Could not write keypress report to hidg0") u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keypress report to hidg0")
} }
state.Modifier = modifier var result = KeysDownState{
state.Keys = keys Modifier: modifier,
Keys: []byte(keys[:]),
}
u.updateKeyDownState(state) u.updateKeyDownState(result)
return state, nil return result, nil
} }

View File

@ -85,17 +85,17 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
return nil return nil
} }
func (u *UsbGadget) AbsMouseReport(x, y int, buttons uint8) error { func (u *UsbGadget) AbsMouseReport(x int, y int, buttons uint8) error {
u.absMouseLock.Lock() u.absMouseLock.Lock()
defer u.absMouseLock.Unlock() defer u.absMouseLock.Unlock()
err := u.absMouseWriteHidFile([]byte{ err := u.absMouseWriteHidFile([]byte{
1, // Report ID 1 1, // Report ID 1
buttons, // Buttons buttons, // Buttons
uint8(x), // X Low Byte byte(x), // X Low Byte
uint8(x >> 8), // X High Byte byte(x >> 8), // X High Byte
uint8(y), // Y Low Byte byte(y), // Y Low Byte
uint8(y >> 8), // Y High Byte byte(y >> 8), // Y High Byte
}) })
if err != nil { if err != nil {
return err return err

View File

@ -75,15 +75,15 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
return nil return nil
} }
func (u *UsbGadget) RelMouseReport(mx, my int8, buttons uint8) error { func (u *UsbGadget) RelMouseReport(mx int8, my int8, buttons uint8) error {
u.relMouseLock.Lock() u.relMouseLock.Lock()
defer u.relMouseLock.Unlock() defer u.relMouseLock.Unlock()
err := u.relMouseWriteHidFile([]byte{ err := u.relMouseWriteHidFile([]byte{
buttons, // Buttons buttons, // Buttons
uint8(mx), // X byte(mx), // X
uint8(my), // Y byte(my), // Y
0, // Wheel 0, // Wheel
}) })
if err != nil { if err != nil {
return err return err

View File

@ -42,8 +42,8 @@ var defaultUsbGadgetDevices = Devices{
} }
type KeysDownState struct { type KeysDownState struct {
Modifier byte `json:"modifier"` Modifier byte `json:"modifier"`
Keys []byte `json:"keys"` Keys ByteSlice `json:"keys"`
} }
// UsbGadget is a struct that represents a USB gadget. // UsbGadget is a struct that represents a USB gadget.

View File

@ -2,6 +2,7 @@ package usbgadget
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -10,6 +11,31 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type ByteSlice []byte
func (s ByteSlice) MarshalJSON() ([]byte, error) {
vals := make([]int, len(s))
for i, v := range s {
vals[i] = int(v)
}
return json.Marshal(vals)
}
func (s *ByteSlice) UnmarshalJSON(data []byte) error {
var vals []int
if err := json.Unmarshal(data, &vals); err != nil {
return err
}
*s = make([]byte, len(vals))
for i, v := range vals {
if v < 0 || v > 255 {
return fmt.Errorf("value %d out of byte range", v)
}
(*s)[i] = byte(v)
}
return nil
}
func joinPath(basePath string, paths []string) string { func joinPath(basePath string, paths []string) string {
pathArr := append([]string{basePath}, paths...) pathArr := append([]string{basePath}, paths...)
return filepath.Join(pathArr...) return filepath.Join(pathArr...)

View File

@ -566,7 +566,7 @@ func riskyCallRPCHandler(logger zerolog.Logger, handler RPCHandler, params map[s
} }
} }
logger.Trace().Interface("args", args).Msg("Calling RPC handler") logger.Trace().Msg("Calling RPC handler")
results := handlerValue.Call(args) results := handlerValue.Call(args)
if len(results) == 0 { if len(results) == 0 {

View File

@ -1,47 +1,65 @@
import { useEffect } from "react"; import { useEffect, useMemo } from "react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { import {
HidState,
KeysDownState,
MouseState,
RTCState,
SettingsState,
useHidStore, useHidStore,
useMouseStore, useMouseStore,
useRTCStore, useRTCStore,
useSettingsStore, useSettingsStore,
useVideoStore, useVideoStore,
VideoState,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
export default function InfoBar() { export default function InfoBar() {
const activeKeys = useHidStore(state => state.activeKeys); const keysDownState = useHidStore((state: HidState) => state.keysDownState);
const activeModifiers = useHidStore(state => state.activeModifiers); const mouseX = useMouseStore((state: MouseState) => state.mouseX);
const mouseX = useMouseStore(state => state.mouseX); const mouseY = useMouseStore((state: MouseState) => state.mouseY);
const mouseY = useMouseStore(state => state.mouseY); const mouseMove = useMouseStore((state: MouseState) => state.mouseMove);
const mouseMove = useMouseStore(state => state.mouseMove);
const videoClientSize = useVideoStore( const videoClientSize = useVideoStore(
state => `${Math.round(state.clientWidth)}x${Math.round(state.clientHeight)}`, (state: VideoState) => `${Math.round(state.clientWidth)}x${Math.round(state.clientHeight)}`,
); );
const videoSize = useVideoStore( const videoSize = useVideoStore(
state => `${Math.round(state.width)}x${Math.round(state.height)}`, (state: VideoState) => `${Math.round(state.width)}x${Math.round(state.height)}`,
); );
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
const settings = useSettingsStore(); const settings = useSettingsStore();
const showPressedKeys = useSettingsStore(state => state.showPressedKeys); const showPressedKeys = useSettingsStore((state: SettingsState) => state.showPressedKeys);
useEffect(() => { useEffect(() => {
if (!rpcDataChannel) return; if (!rpcDataChannel) return;
rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed"); rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
rpcDataChannel.onerror = e => rpcDataChannel.onerror = (e: Event) =>
console.log(`Error on DataChannel '${rpcDataChannel.label}': ${e}`); console.log(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
}, [rpcDataChannel]); }, [rpcDataChannel]);
const keyboardLedState = useHidStore(state => state.keyboardLedState); const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState);
const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse); const isTurnServerInUse = useRTCStore((state: RTCState) => state.isTurnServerInUse);
const usbState = useHidStore(state => state.usbState); const usbState = useHidStore((state: HidState) => state.usbState);
const hdmiState = useVideoStore(state => state.hdmiState); const hdmiState = useVideoStore((state: VideoState) => state.hdmiState);
const displayKeys = useMemo(() => {
if (!showPressedKeys || !keysDownState)
return "";
const state = keysDownState as KeysDownState;
const activeModifierMask = state.modifier || 0;
const keysDown = state.keys || [];
const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name);
const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name);
return [...modifierNames,...keyNames].join(", ");
}, [keysDownState, showPressedKeys]);
return ( return (
<div className="bg-white border-t border-t-slate-800/30 text-slate-800 dark:border-t-slate-300/20 dark:bg-slate-900 dark:text-slate-300"> <div className="bg-white border-t border-t-slate-800/30 text-slate-800 dark:border-t-slate-300/20 dark:bg-slate-900 dark:text-slate-300">
@ -99,14 +117,7 @@ export default function InfoBar() {
<div className="flex items-center gap-x-1"> <div className="flex items-center gap-x-1">
<span className="text-xs font-semibold">Keys:</span> <span className="text-xs font-semibold">Keys:</span>
<h2 className="text-xs"> <h2 className="text-xs">
{[ {displayKeys}
...activeKeys.map(
x => Object.entries(keys).filter(y => y[1] === x)[0][0],
),
activeModifiers.map(
x => Object.entries(modifiers).filter(y => y[1] === x)[0][0],
),
].join(", ")}
</h2> </h2>
</div> </div>
)} )}

View File

@ -11,10 +11,14 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { keys } from "@/keyboardMappings"; import { keys } from "@/keyboardMappings";
import { import {
MouseState,
RTCState,
SettingsState,
useMouseStore, useMouseStore,
useRTCStore, useRTCStore,
useSettingsStore, useSettingsStore,
useVideoStore, useVideoStore,
VideoState,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { import {
@ -27,15 +31,15 @@ import {
export default function WebRTCVideo() { export default function WebRTCVideo() {
// Video and stream related refs and states // Video and stream related refs and states
const videoElm = useRef<HTMLVideoElement>(null); const videoElm = useRef<HTMLVideoElement>(null);
const mediaStream = useRTCStore(state => state.mediaStream); const mediaStream = useRTCStore((state: RTCState) => state.mediaStream);
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const peerConnectionState = useRTCStore(state => state.peerConnectionState); const peerConnectionState = useRTCStore((state: RTCState) => state.peerConnectionState);
const [isPointerLockActive, setIsPointerLockActive] = useState(false); const [isPointerLockActive, setIsPointerLockActive] = useState(false);
// Store hooks // Store hooks
const settings = useSettingsStore(); const settings = useSettingsStore();
const { sendKeypressEvent, resetKeyboardState } = useKeyboard(); const { sendKeypressEvent, resetKeyboardState } = useKeyboard();
const setMousePosition = useMouseStore(state => state.setMousePosition); const setMousePosition = useMouseStore((state: MouseState) => state.setMousePosition);
const setMouseMove = useMouseStore(state => state.setMouseMove); const setMouseMove = useMouseStore((state: MouseState) => state.setMouseMove);
const { const {
setClientSize: setVideoClientSize, setClientSize: setVideoClientSize,
setSize: setVideoSize, setSize: setVideoSize,
@ -46,15 +50,15 @@ export default function WebRTCVideo() {
} = useVideoStore(); } = useVideoStore();
// Video enhancement settings // Video enhancement settings
const videoSaturation = useSettingsStore(state => state.videoSaturation); const videoSaturation = useSettingsStore((state: SettingsState) => state.videoSaturation);
const videoBrightness = useSettingsStore(state => state.videoBrightness); const videoBrightness = useSettingsStore((state: SettingsState) => state.videoBrightness);
const videoContrast = useSettingsStore(state => state.videoContrast); const videoContrast = useSettingsStore((state: SettingsState) => state.videoContrast);
// RTC related states // RTC related states
const peerConnection = useRTCStore(state => state.peerConnection); const peerConnection = useRTCStore((state: RTCState ) => state.peerConnection);
// HDMI and UI states // HDMI and UI states
const hdmiState = useVideoStore(state => state.hdmiState); const hdmiState = useVideoStore((state: VideoState) => state.hdmiState);
const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState); const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState);
const isVideoLoading = !isPlaying; const isVideoLoading = !isPlaying;

View File

@ -47,12 +47,12 @@ export interface User {
picture?: string; picture?: string;
} }
interface UserState { export interface UserState {
user: User | null; user: User | null;
setUser: (user: User | null) => void; setUser: (user: User | null) => void;
} }
interface UIState { export interface UIState {
sidebarView: AvailableSidebarViews | null; sidebarView: AvailableSidebarViews | null;
setSidebarView: (view: AvailableSidebarViews | null) => void; setSidebarView: (view: AvailableSidebarViews | null) => void;
@ -68,21 +68,21 @@ interface UIState {
setAttachedVirtualKeyboardVisibility: (enabled: boolean) => void; setAttachedVirtualKeyboardVisibility: (enabled: boolean) => void;
terminalType: AvailableTerminalTypes; terminalType: AvailableTerminalTypes;
setTerminalType: (enabled: UIState["terminalType"]) => void; setTerminalType: (type: UIState["terminalType"]) => void;
} }
export const useUiStore = create<UIState>(set => ({ export const useUiStore = create<UIState>(set => ({
terminalType: "none", terminalType: "none",
setTerminalType: type => set({ terminalType: type }), setTerminalType: (type: UIState["terminalType"]) => set({ terminalType: type }),
sidebarView: null, sidebarView: null,
setSidebarView: view => set({ sidebarView: view }), setSidebarView: (view: AvailableSidebarViews | null) => set({ sidebarView: view }),
disableVideoFocusTrap: false, disableVideoFocusTrap: false,
setDisableVideoFocusTrap: enabled => set({ disableVideoFocusTrap: enabled }), setDisableVideoFocusTrap: (enabled: boolean) => set({ disableVideoFocusTrap: enabled }),
isWakeOnLanModalVisible: false, isWakeOnLanModalVisible: false,
setWakeOnLanModalVisibility: enabled => set({ isWakeOnLanModalVisible: enabled }), setWakeOnLanModalVisibility: (enabled: boolean) => set({ isWakeOnLanModalVisible: enabled }),
toggleSidebarView: view => toggleSidebarView: view =>
set(state => { set(state => {
@ -94,11 +94,11 @@ export const useUiStore = create<UIState>(set => ({
}), }),
isAttachedVirtualKeyboardVisible: true, isAttachedVirtualKeyboardVisible: true,
setAttachedVirtualKeyboardVisibility: enabled => setAttachedVirtualKeyboardVisibility: (enabled: boolean) =>
set({ isAttachedVirtualKeyboardVisible: enabled }), set({ isAttachedVirtualKeyboardVisible: enabled }),
})); }));
interface RTCState { export interface RTCState {
peerConnection: RTCPeerConnection | null; peerConnection: RTCPeerConnection | null;
setPeerConnection: (pc: RTCState["peerConnection"]) => void; setPeerConnection: (pc: RTCState["peerConnection"]) => void;
@ -118,18 +118,18 @@ interface RTCState {
setMediaStream: (stream: MediaStream) => void; setMediaStream: (stream: MediaStream) => void;
videoStreamStats: RTCInboundRtpStreamStats | null; videoStreamStats: RTCInboundRtpStreamStats | null;
appendVideoStreamStats: (state: RTCInboundRtpStreamStats) => void; appendVideoStreamStats: (stats: RTCInboundRtpStreamStats) => void;
videoStreamStatsHistory: Map<number, RTCInboundRtpStreamStats>; videoStreamStatsHistory: Map<number, RTCInboundRtpStreamStats>;
isTurnServerInUse: boolean; isTurnServerInUse: boolean;
setTurnServerInUse: (inUse: boolean) => void; setTurnServerInUse: (inUse: boolean) => void;
inboundRtpStats: Map<number, RTCInboundRtpStreamStats>; inboundRtpStats: Map<number, RTCInboundRtpStreamStats>;
appendInboundRtpStats: (state: RTCInboundRtpStreamStats) => void; appendInboundRtpStats: (stats: RTCInboundRtpStreamStats) => void;
clearInboundRtpStats: () => void; clearInboundRtpStats: () => void;
candidatePairStats: Map<number, RTCIceCandidatePairStats>; candidatePairStats: Map<number, RTCIceCandidatePairStats>;
appendCandidatePairStats: (pair: RTCIceCandidatePairStats) => void; appendCandidatePairStats: (stats: RTCIceCandidatePairStats) => void;
clearCandidatePairStats: () => void; clearCandidatePairStats: () => void;
// Remote ICE candidates stat type doesn't exist as of today // Remote ICE candidates stat type doesn't exist as of today
@ -141,7 +141,7 @@ interface RTCState {
// Disk data channel stats type doesn't exist as of today // Disk data channel stats type doesn't exist as of today
diskDataChannelStats: Map<number, RTCDataChannelStats>; diskDataChannelStats: Map<number, RTCDataChannelStats>;
appendDiskDataChannelStats: (stat: RTCDataChannelStats) => void; appendDiskDataChannelStats: (stats: RTCDataChannelStats) => void;
terminalChannel: RTCDataChannel | null; terminalChannel: RTCDataChannel | null;
setTerminalChannel: (channel: RTCDataChannel) => void; setTerminalChannel: (channel: RTCDataChannel) => void;
@ -149,78 +149,78 @@ interface RTCState {
export const useRTCStore = create<RTCState>(set => ({ export const useRTCStore = create<RTCState>(set => ({
peerConnection: null, peerConnection: null,
setPeerConnection: pc => set({ peerConnection: pc }), setPeerConnection: (pc: RTCState["peerConnection"]) => set({ peerConnection: pc }),
rpcDataChannel: null, rpcDataChannel: null,
setRpcDataChannel: channel => set({ rpcDataChannel: channel }), setRpcDataChannel: (channel: RTCDataChannel) => set({ rpcDataChannel: channel }),
transceiver: null, transceiver: null,
setTransceiver: transceiver => set({ transceiver }), setTransceiver: (transceiver: RTCRtpTransceiver) => set({ transceiver }),
peerConnectionState: null, peerConnectionState: null,
setPeerConnectionState: state => set({ peerConnectionState: state }), setPeerConnectionState: (state: RTCPeerConnectionState) => set({ peerConnectionState: state }),
diskChannel: null, diskChannel: null,
setDiskChannel: channel => set({ diskChannel: channel }), setDiskChannel: (channel: RTCDataChannel) => set({ diskChannel: channel }),
mediaStream: null, mediaStream: null,
setMediaStream: stream => set({ mediaStream: stream }), setMediaStream: (stream: MediaStream) => set({ mediaStream: stream }),
videoStreamStats: null, videoStreamStats: null,
appendVideoStreamStats: stats => set({ videoStreamStats: stats }), appendVideoStreamStats: (stats: RTCInboundRtpStreamStats) => set({ videoStreamStats: stats }),
videoStreamStatsHistory: new Map(), videoStreamStatsHistory: new Map(),
isTurnServerInUse: false, isTurnServerInUse: false,
setTurnServerInUse: inUse => set({ isTurnServerInUse: inUse }), setTurnServerInUse: (inUse: boolean) => set({ isTurnServerInUse: inUse }),
inboundRtpStats: new Map(), inboundRtpStats: new Map(),
appendInboundRtpStats: newStat => { appendInboundRtpStats: (stats: RTCInboundRtpStreamStats) => {
set(prevState => ({ set(prevState => ({
inboundRtpStats: appendStatToMap(newStat, prevState.inboundRtpStats), inboundRtpStats: appendStatToMap(stats, prevState.inboundRtpStats),
})); }));
}, },
clearInboundRtpStats: () => set({ inboundRtpStats: new Map() }), clearInboundRtpStats: () => set({ inboundRtpStats: new Map() }),
candidatePairStats: new Map(), candidatePairStats: new Map(),
appendCandidatePairStats: newStat => { appendCandidatePairStats: (stats: RTCIceCandidatePairStats) => {
set(prevState => ({ set(prevState => ({
candidatePairStats: appendStatToMap(newStat, prevState.candidatePairStats), candidatePairStats: appendStatToMap(stats, prevState.candidatePairStats),
})); }));
}, },
clearCandidatePairStats: () => set({ candidatePairStats: new Map() }), clearCandidatePairStats: () => set({ candidatePairStats: new Map() }),
localCandidateStats: new Map(), localCandidateStats: new Map(),
appendLocalCandidateStats: newStat => { appendLocalCandidateStats: (stats: RTCIceCandidateStats) => {
set(prevState => ({ set(prevState => ({
localCandidateStats: appendStatToMap(newStat, prevState.localCandidateStats), localCandidateStats: appendStatToMap(stats, prevState.localCandidateStats),
})); }));
}, },
remoteCandidateStats: new Map(), remoteCandidateStats: new Map(),
appendRemoteCandidateStats: newStat => { appendRemoteCandidateStats: (stats: RTCIceCandidateStats) => {
set(prevState => ({ set(prevState => ({
remoteCandidateStats: appendStatToMap(newStat, prevState.remoteCandidateStats), remoteCandidateStats: appendStatToMap(stats, prevState.remoteCandidateStats),
})); }));
}, },
diskDataChannelStats: new Map(), diskDataChannelStats: new Map(),
appendDiskDataChannelStats: newStat => { appendDiskDataChannelStats: (stats: RTCDataChannelStats) => {
set(prevState => ({ set(prevState => ({
diskDataChannelStats: appendStatToMap(newStat, prevState.diskDataChannelStats), diskDataChannelStats: appendStatToMap(stats, prevState.diskDataChannelStats),
})); }));
}, },
// Add these new properties to the store implementation // Add these new properties to the store implementation
terminalChannel: null, terminalChannel: null,
setTerminalChannel: channel => set({ terminalChannel: channel }), setTerminalChannel: (channel: RTCDataChannel) => set({ terminalChannel: channel }),
})); }));
interface MouseMove { export interface MouseMove {
x: number; x: number;
y: number; y: number;
buttons: number; buttons: number;
} }
interface MouseState { export interface MouseState {
mouseX: number; mouseX: number;
mouseY: number; mouseY: number;
mouseMove?: MouseMove; mouseMove?: MouseMove;
@ -232,9 +232,14 @@ export const useMouseStore = create<MouseState>(set => ({
mouseX: 0, mouseX: 0,
mouseY: 0, mouseY: 0,
setMouseMove: (move?: MouseMove) => set({ mouseMove: move }), setMouseMove: (move?: MouseMove) => set({ mouseMove: move }),
setMousePosition: (x, y) => set({ mouseX: x, mouseY: y }), setMousePosition: (x: number, y: number) => set({ mouseX: x, mouseY: y }),
})); }));
export interface HdmiState {
ready: boolean;
error?: Extract<VideoState["hdmiState"], "no_signal" | "no_lock" | "out_of_range">;
}
export interface VideoState { export interface VideoState {
width: number; width: number;
height: number; height: number;
@ -263,13 +268,13 @@ export const useVideoStore = create<VideoState>(set => ({
clientHeight: 0, clientHeight: 0,
// The video element's client size // The video element's client size
setClientSize: (clientWidth, clientHeight) => set({ clientWidth, clientHeight }), setClientSize: (clientWidth: number, clientHeight: number) => set({ clientWidth, clientHeight }),
// Resolution // Resolution
setSize: (width, height) => set({ width, height }), setSize: (width: number, height: number) => set({ width, height }),
hdmiState: "connecting", hdmiState: "connecting",
setHdmiState: state => { setHdmiState: (state: HdmiState) => {
if (!state) return; if (!state) return;
const { ready, error } = state; const { ready, error } = state;
@ -283,7 +288,7 @@ export const useVideoStore = create<VideoState>(set => ({
}, },
})); }));
interface SettingsState { export interface SettingsState {
isCursorHidden: boolean; isCursorHidden: boolean;
setCursorVisibility: (enabled: boolean) => void; setCursorVisibility: (enabled: boolean) => void;
@ -325,17 +330,17 @@ export const useSettingsStore = create(
persist<SettingsState>( persist<SettingsState>(
set => ({ set => ({
isCursorHidden: false, isCursorHidden: false,
setCursorVisibility: enabled => set({ isCursorHidden: enabled }), setCursorVisibility: (enabled: boolean) => set({ isCursorHidden: enabled }),
mouseMode: "absolute", mouseMode: "absolute",
setMouseMode: mode => set({ mouseMode: mode }), setMouseMode: (mode: string) => set({ mouseMode: mode }),
debugMode: import.meta.env.DEV, debugMode: import.meta.env.DEV,
setDebugMode: enabled => set({ debugMode: enabled }), setDebugMode: (enabled: boolean) => set({ debugMode: enabled }),
// Add developer mode with default value // Add developer mode with default value
developerMode: false, developerMode: false,
setDeveloperMode: enabled => set({ developerMode: enabled }), setDeveloperMode: (enabled: boolean) => set({ developerMode: enabled }),
displayRotation: "270", displayRotation: "270",
setDisplayRotation: (rotation: string) => set({ displayRotation: rotation }), setDisplayRotation: (rotation: string) => set({ displayRotation: rotation }),
@ -349,21 +354,21 @@ export const useSettingsStore = create(
set({ backlightSettings: settings }), set({ backlightSettings: settings }),
keyboardLayout: "en-US", keyboardLayout: "en-US",
setKeyboardLayout: layout => set({ keyboardLayout: layout }), setKeyboardLayout: (layout: string) => set({ keyboardLayout: layout }),
scrollThrottling: 0, scrollThrottling: 0,
setScrollThrottling: value => set({ scrollThrottling: value }), setScrollThrottling: (value: number) => set({ scrollThrottling: value }),
showPressedKeys: true, showPressedKeys: true,
setShowPressedKeys: show => set({ showPressedKeys: show }), setShowPressedKeys: (show: boolean) => set({ showPressedKeys: show }),
// Video enhancement settings with default values (1.0 = normal) // Video enhancement settings with default values (1.0 = normal)
videoSaturation: 1.0, videoSaturation: 1.0,
setVideoSaturation: value => set({ videoSaturation: value }), setVideoSaturation: (value: number) => set({ videoSaturation: value }),
videoBrightness: 1.0, videoBrightness: 1.0,
setVideoBrightness: value => set({ videoBrightness: value }), setVideoBrightness: (value: number) => set({ videoBrightness: value }),
videoContrast: 1.0, videoContrast: 1.0,
setVideoContrast: value => set({ videoContrast: value }), setVideoContrast: (value: number) => set({ videoContrast: value }),
}), }),
{ {
name: "settings", name: "settings",
@ -403,23 +408,23 @@ export interface MountMediaState {
export const useMountMediaStore = create<MountMediaState>(set => ({ export const useMountMediaStore = create<MountMediaState>(set => ({
localFile: null, localFile: null,
setLocalFile: file => set({ localFile: file }), setLocalFile: (file: MountMediaState["localFile"]) => set({ localFile: file }),
remoteVirtualMediaState: null, remoteVirtualMediaState: null,
setRemoteVirtualMediaState: state => set({ remoteVirtualMediaState: state }), setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => set({ remoteVirtualMediaState: state }),
modalView: "mode", modalView: "mode",
setModalView: view => set({ modalView: view }), setModalView: (view: MountMediaState["modalView"]) => set({ modalView: view }),
isMountMediaDialogOpen: false, isMountMediaDialogOpen: false,
setIsMountMediaDialogOpen: isOpen => set({ isMountMediaDialogOpen: isOpen }), setIsMountMediaDialogOpen: (isOpen: MountMediaState["isMountMediaDialogOpen"]) => set({ isMountMediaDialogOpen: isOpen }),
uploadedFiles: [], uploadedFiles: [],
addUploadedFile: file => addUploadedFile: (file: { name: string; size: string; uploadedAt: string }) =>
set(state => ({ uploadedFiles: [...state.uploadedFiles, file] })), set(state => ({ uploadedFiles: [...state.uploadedFiles, file] })),
errorMessage: null, errorMessage: null,
setErrorMessage: message => set({ errorMessage: message }), setErrorMessage: (message: string | null) => set({ errorMessage: message }),
})); }));
export interface KeyboardLedState { export interface KeyboardLedState {
@ -437,14 +442,6 @@ export interface KeysDownState {
} }
export interface HidState { export interface HidState {
activeKeys: number[];
activeModifiers: number[];
updateActiveKeysAndModifiers: (keysAndModifiers: {
keys: number[];
modifiers: number[];
}) => void;
altGrArmed: boolean; altGrArmed: boolean;
setAltGrArmed: (armed: boolean) => void; setAltGrArmed: (armed: boolean) => void;
@ -470,38 +467,31 @@ export interface HidState {
setUsbState: (state: HidState["usbState"]) => void; setUsbState: (state: HidState["usbState"]) => void;
} }
export const useHidStore = create<HidState>((set) => ({ export const useHidStore = create<HidState>(set => ({
activeKeys: [],
activeModifiers: [],
updateActiveKeysAndModifiers: ({ keys, modifiers }) => {
return set({ activeKeys: keys, activeModifiers: modifiers });
},
altGrArmed: false, altGrArmed: false,
setAltGrArmed: armed => set({ altGrArmed: armed }), setAltGrArmed: (armed: boolean): void => set({ altGrArmed: armed }),
altGrTimer: 0, altGrTimer: 0,
setAltGrTimer: timeout => set({ altGrTimer: timeout }), setAltGrTimer: (timeout: number | null): void => set({ altGrTimer: timeout }),
altGrCtrlTime: 0, altGrCtrlTime: 0,
setAltGrCtrlTime: time => set({ altGrCtrlTime: time }), setAltGrCtrlTime: (time: number): void => set({ altGrCtrlTime: time }),
keyboardLedState: undefined, keyboardLedState: undefined,
setKeyboardLedState: ledState => set({ keyboardLedState: ledState }), setKeyboardLedState: (ledState: KeyboardLedState): void => set({ keyboardLedState: ledState }),
keysDownState: undefined, keysDownState: undefined,
setKeysDownState: state => set({ keysDownState: state }), setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }),
isVirtualKeyboardEnabled: false, isVirtualKeyboardEnabled: false,
setVirtualKeyboardEnabled: enabled => set({ isVirtualKeyboardEnabled: enabled }), setVirtualKeyboardEnabled: (enabled: boolean): void => set({ isVirtualKeyboardEnabled: enabled }),
isPasteModeEnabled: false, isPasteModeEnabled: false,
setPasteModeEnabled: enabled => set({ isPasteModeEnabled: enabled }), setPasteModeEnabled: (enabled: boolean): void => set({ isPasteModeEnabled: enabled }),
// Add these new properties for USB state // Add these new properties for USB state
usbState: "not attached", usbState: "not attached",
setUsbState: state => set({ usbState: state }), setUsbState: (state: HidState["usbState"]) => set({ usbState: state }),
})); }));
export const useUserStore = create<UserState>(set => ({ export const useUserStore = create<UserState>(set => ({
@ -559,7 +549,7 @@ export interface UpdateState {
export const useUpdateStore = create<UpdateState>(set => ({ export const useUpdateStore = create<UpdateState>(set => ({
isUpdatePending: false, isUpdatePending: false,
setIsUpdatePending: isPending => set({ isUpdatePending: isPending }), setIsUpdatePending: (isPending: boolean) => set({ isUpdatePending: isPending }),
setOtaState: state => set({ otaState: state }), setOtaState: state => set({ otaState: state }),
otaState: { otaState: {
@ -583,12 +573,12 @@ export const useUpdateStore = create<UpdateState>(set => ({
}, },
updateDialogHasBeenMinimized: false, updateDialogHasBeenMinimized: false,
setUpdateDialogHasBeenMinimized: hasBeenMinimized => setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
set({ updateDialogHasBeenMinimized: hasBeenMinimized }), set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
modalView: "loading", modalView: "loading",
setModalView: view => set({ modalView: view }), setModalView: (view: UpdateState["modalView"]) => set({ modalView: view }),
updateErrorMessage: null, updateErrorMessage: null,
setUpdateErrorMessage: errorMessage => set({ updateErrorMessage: errorMessage }), setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
})); }));
interface UsbConfigModalState { interface UsbConfigModalState {
@ -609,8 +599,8 @@ export interface UsbConfigState {
export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({ export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({
modalView: "updateUsbConfig", modalView: "updateUsbConfig",
errorMessage: null, errorMessage: null,
setModalView: view => set({ modalView: view }), setModalView: (view: UsbConfigModalState["modalView"]) => set({ modalView: view }),
setErrorMessage: message => set({ errorMessage: message }), setErrorMessage: (message: string | null) => set({ errorMessage: message }),
})); }));
interface LocalAuthModalState { interface LocalAuthModalState {
@ -626,7 +616,7 @@ interface LocalAuthModalState {
export const useLocalAuthModalStore = create<LocalAuthModalState>(set => ({ export const useLocalAuthModalStore = create<LocalAuthModalState>(set => ({
modalView: "createPassword", modalView: "createPassword",
setModalView: view => set({ modalView: view }), setModalView: (view: LocalAuthModalState["modalView"]) => set({ modalView: view }),
})); }));
export interface DeviceState { export interface DeviceState {
@ -641,8 +631,8 @@ export const useDeviceStore = create<DeviceState>(set => ({
appVersion: null, appVersion: null,
systemVersion: null, systemVersion: null,
setAppVersion: version => set({ appVersion: version }), setAppVersion: (version: string) => set({ appVersion: version }),
setSystemVersion: version => set({ systemVersion: version }), setSystemVersion: (version: string) => set({ systemVersion: version }),
})); }));
export interface DhcpLease { export interface DhcpLease {
@ -808,7 +798,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
try { try {
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
sendFn("getKeyboardMacros", {}, response => { sendFn("getKeyboardMacros", {}, (response: JsonRpcResponse) => {
if (response.error) { if (response.error) {
console.error("Error loading macros:", response.error); console.error("Error loading macros:", response.error);
reject(new Error(response.error.message)); reject(new Error(response.error.message));
@ -888,7 +878,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
sendFn( sendFn(
"setKeyboardMacros", "setKeyboardMacros",
{ params: { macros: macrosWithSortOrder } }, { params: { macros: macrosWithSortOrder } },
response => { (response: JsonRpcResponse) => {
resolve(response); resolve(response);
}, },
); );

View File

@ -1,6 +1,6 @@
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { useRTCStore } from "@/hooks/stores"; import { RTCState, useRTCStore } from "@/hooks/stores";
export interface JsonRpcRequest { export interface JsonRpcRequest {
jsonrpc: string; jsonrpc: string;
@ -33,7 +33,7 @@ const callbackStore = new Map<number | string, (resp: JsonRpcResponse) => void>(
let requestCounter = 0; let requestCounter = 0;
export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) { export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
const send = useCallback( const send = useCallback(
(method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => { (method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => {

View File

@ -1,16 +1,14 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { KeysDownState, useHidStore, useRTCStore } from "@/hooks/stores"; import { KeysDownState, HidState, useHidStore, RTCState, useRTCStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
export default function useKeyboard() { export default function useKeyboard() {
const [send] = useJsonRpc(); const [send] = useJsonRpc();
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
const updateActiveKeysAndModifiers = useHidStore( const setKeysDownState = useHidStore((state: HidState) => state.setKeysDownState);
state => state.updateActiveKeysAndModifiers,
);
const sendKeyboardEvent = useCallback( const sendKeyboardEvent = useCallback(
(keys: number[], modifiers: number[]) => { (keys: number[], modifiers: number[]) => {
@ -18,35 +16,28 @@ export default function useKeyboard() {
const accModifier = modifiers.reduce((acc, val) => acc + val, 0); const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
send("keyboardReport", { keys, modifier: accModifier }); send("keyboardReport", { keys, modifier: accModifier });
//TODO would be nice if the keyboardReport rpc call returned the current state like keypressReport does
// We do this for the info bar to display the currently pressed keys for the user // We do this for the info bar to display the currently pressed keys for the user
updateActiveKeysAndModifiers({ keys: keys, modifiers: modifiers }); setKeysDownState({ keys: keys, modifier: accModifier });
}, },
[rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers], [rpcDataChannel?.readyState, send, setKeysDownState],
); );
const modifiersFromModifierMask = (activeModifiers: number): number[] => {
return Object.values(modifiers).filter(m => (activeModifiers & m) !== 0);
}
const sendKeypressEvent = useCallback( const sendKeypressEvent = useCallback(
(key: number, press: boolean) => { (key: number, press: boolean) => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
send("keypressReport", { key, press }, resp => { send("keypressReport", { key, press }, (resp: JsonRpcResponse) => {
if ("error" in resp) { if ("error" in resp) {
console.error("Failed to send keypress:", resp.error); console.error("Failed to send keypress:", resp.error);
} else { } else {
const keyDownState = resp.result as KeysDownState; const keyDownState = resp.result as KeysDownState;
const keysDown = keyDownState.keys;
const activeModifiers = modifiersFromModifierMask(keyDownState.modifier)
// We do this for the info bar to display the currently pressed keys for the user // We do this for the info bar to display the currently pressed keys for the user
updateActiveKeysAndModifiers({ keys: keysDown, modifiers: activeModifiers }); setKeysDownState(keyDownState);
} }
}); });
}, },
[rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers], [rpcDataChannel?.readyState, send, setKeysDownState],
); );
const resetKeyboardState = useCallback(() => { const resetKeyboardState = useCallback(() => {

View File

@ -18,10 +18,14 @@ import useWebSocket from "react-use-websocket";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import { import {
DeviceState,
HidState, HidState,
KeyboardLedState, KeyboardLedState,
KeysDownState, KeysDownState,
MountMediaState,
NetworkState, NetworkState,
RTCState,
UIState,
UpdateState, UpdateState,
useDeviceStore, useDeviceStore,
useHidStore, useHidStore,
@ -38,7 +42,7 @@ import WebRTCVideo from "@components/WebRTCVideo";
import { checkAuth, isInCloud, isOnDevice } from "@/main"; import { checkAuth, isInCloud, isOnDevice } from "@/main";
import DashboardNavbar from "@components/Header"; import DashboardNavbar from "@components/Header";
import ConnectionStatsSidebar from "@/components/sidebar/connectionStats"; import ConnectionStatsSidebar from "@/components/sidebar/connectionStats";
import { JsonRpcRequest, useJsonRpc } from "@/hooks/useJsonRpc"; import { JsonRpcRequest, JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import Terminal from "@components/Terminal"; import Terminal from "@components/Terminal";
import { CLOUD_API, DEVICE_API } from "@/ui.config"; import { CLOUD_API, DEVICE_API } from "@/ui.config";
@ -128,18 +132,18 @@ export default function KvmIdRoute() {
const authMode = "authMode" in loaderResp ? loaderResp.authMode : null; const authMode = "authMode" in loaderResp ? loaderResp.authMode : null;
const params = useParams() as { id: string }; const params = useParams() as { id: string };
const sidebarView = useUiStore(state => state.sidebarView); const sidebarView = useUiStore((state: UIState) => state.sidebarView);
const [queryParams, setQueryParams] = useSearchParams(); const [queryParams, setQueryParams] = useSearchParams();
const setIsTurnServerInUse = useRTCStore(state => state.setTurnServerInUse); const setIsTurnServerInUse = useRTCStore((state: RTCState) => state.setTurnServerInUse);
const peerConnection = useRTCStore(state => state.peerConnection); const peerConnection = useRTCStore((state: RTCState) => state.peerConnection);
const setPeerConnectionState = useRTCStore(state => state.setPeerConnectionState); const setPeerConnectionState = useRTCStore((state: RTCState) => state.setPeerConnectionState);
const peerConnectionState = useRTCStore(state => state.peerConnectionState); const peerConnectionState = useRTCStore((state: RTCState) => state.peerConnectionState);
const setMediaMediaStream = useRTCStore(state => state.setMediaStream); const setMediaMediaStream = useRTCStore((state: RTCState) => state.setMediaStream);
const setPeerConnection = useRTCStore(state => state.setPeerConnection); const setPeerConnection = useRTCStore((state: RTCState) => state.setPeerConnection);
const setDiskChannel = useRTCStore(state => state.setDiskChannel); const setDiskChannel = useRTCStore((state: RTCState) => state.setDiskChannel);
const setRpcDataChannel = useRTCStore(state => state.setRpcDataChannel); const setRpcDataChannel = useRTCStore((state: RTCState) => state.setRpcDataChannel);
const setTransceiver = useRTCStore(state => state.setTransceiver); const setTransceiver = useRTCStore((state: RTCState) => state.setTransceiver);
const location = useLocation(); const location = useLocation();
const isLegacySignalingEnabled = useRef(false); const isLegacySignalingEnabled = useRef(false);
@ -513,9 +517,9 @@ export default function KvmIdRoute() {
}, [peerConnectionState, cleanupAndStopReconnecting]); }, [peerConnectionState, cleanupAndStopReconnecting]);
// Cleanup effect // Cleanup effect
const clearInboundRtpStats = useRTCStore(state => state.clearInboundRtpStats); const clearInboundRtpStats = useRTCStore((state: RTCState) => state.clearInboundRtpStats);
const clearCandidatePairStats = useRTCStore(state => state.clearCandidatePairStats); const clearCandidatePairStats = useRTCStore((state: RTCState) => state.clearCandidatePairStats);
const setSidebarView = useUiStore(state => state.setSidebarView); const setSidebarView = useUiStore((state: UIState) => state.setSidebarView);
useEffect(() => { useEffect(() => {
return () => { return () => {
@ -550,7 +554,7 @@ export default function KvmIdRoute() {
}, [peerConnectionState, setIsTurnServerInUse]); }, [peerConnectionState, setIsTurnServerInUse]);
// TURN server usage reporting // TURN server usage reporting
const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse); const isTurnServerInUse = useRTCStore((state: RTCState) => state.isTurnServerInUse);
const lastBytesReceived = useRef<number>(0); const lastBytesReceived = useRef<number>(0);
const lastBytesSent = useRef<number>(0); const lastBytesSent = useRef<number>(0);
@ -583,16 +587,16 @@ export default function KvmIdRoute() {
}); });
}, 10000); }, 10000);
const setNetworkState = useNetworkStateStore(state => state.setNetworkState); const setNetworkState = useNetworkStateStore((state: NetworkState) => state.setNetworkState);
const setUsbState = useHidStore(state => state.setUsbState); const setUsbState = useHidStore((state: HidState) => state.setUsbState);
const setHdmiState = useVideoStore(state => state.setHdmiState); const setHdmiState = useVideoStore((state: VideoState) => state.setHdmiState);
const keyboardLedState = useHidStore(state => state.keyboardLedState); const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState);
const setKeyboardLedState = useHidStore(state => state.setKeyboardLedState); const setKeyboardLedState = useHidStore((state: HidState) => state.setKeyboardLedState);
const keysDownState = useHidStore(state => state.keysDownState); const keysDownState = useHidStore((state: HidState) => state.keysDownState);
const setKeysDownState = useHidStore(state => state.setKeysDownState); const setKeysDownState = useHidStore((state: HidState) => state.setKeysDownState);
const [hasUpdated, setHasUpdated] = useState(false); const [hasUpdated, setHasUpdated] = useState(false);
const { navigateTo } = useDeviceUiNavigation(); const { navigateTo } = useDeviceUiNavigation();
@ -652,12 +656,12 @@ export default function KvmIdRoute() {
} }
} }
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
const [send] = useJsonRpc(onJsonRpcRequest); const [send] = useJsonRpc(onJsonRpcRequest);
useEffect(() => { useEffect(() => {
if (rpcDataChannel?.readyState !== "open") return; if (rpcDataChannel?.readyState !== "open") return;
send("getVideoState", {}, resp => { send("getVideoState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return; if ("error" in resp) return;
setHdmiState(resp.result as Parameters<VideoState["setHdmiState"]>[0]); setHdmiState(resp.result as Parameters<VideoState["setHdmiState"]>[0]);
}); });
@ -669,7 +673,7 @@ export default function KvmIdRoute() {
if (keyboardLedState !== undefined) return; if (keyboardLedState !== undefined) return;
console.log("Requesting keyboard led state"); console.log("Requesting keyboard led state");
send("getKeyboardLedState", {}, resp => { send("getKeyboardLedState", {}, (resp: JsonRpcResponse) => {
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;
@ -685,7 +689,7 @@ export default function KvmIdRoute() {
if (keysDownState !== undefined) return; if (keysDownState !== undefined) return;
console.log("Requesting keys down state"); console.log("Requesting keys down state");
send("getKeyDownState", {}, resp => { send("getKeyDownState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) { if ("error" in resp) {
console.error("Failed to get key down state", resp.error); console.error("Failed to get key down state", resp.error);
return; return;
@ -702,8 +706,8 @@ export default function KvmIdRoute() {
} }
}, [navigate, navigateTo, queryParams, setModalView, setQueryParams]); }, [navigate, navigateTo, queryParams, setModalView, setQueryParams]);
const diskChannel = useRTCStore(state => state.diskChannel)!; const diskChannel = useRTCStore((state: RTCState) => state.diskChannel)!;
const file = useMountMediaStore(state => state.localFile)!; const file = useMountMediaStore((state: MountMediaState) => state.localFile)!;
useEffect(() => { useEffect(() => {
if (!diskChannel || !file) return; if (!diskChannel || !file) return;
diskChannel.onmessage = async e => { diskChannel.onmessage = async e => {
@ -723,7 +727,7 @@ export default function KvmIdRoute() {
}, [diskChannel, file]); }, [diskChannel, file]);
// System update // System update
const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap); const disableVideoFocusTrap = useUiStore((state: UIState) => state.disableVideoFocusTrap);
const [kvmTerminal, setKvmTerminal] = useState<RTCDataChannel | null>(null); const [kvmTerminal, setKvmTerminal] = useState<RTCDataChannel | null>(null);
const [serialConsole, setSerialConsole] = useState<RTCDataChannel | null>(null); const [serialConsole, setSerialConsole] = useState<RTCDataChannel | null>(null);
@ -744,14 +748,14 @@ export default function KvmIdRoute() {
if (location.pathname !== "/other-session") navigateTo("/"); if (location.pathname !== "/other-session") navigateTo("/");
}, [navigateTo, location.pathname]); }, [navigateTo, location.pathname]);
const appVersion = useDeviceStore(state => state.appVersion); const appVersion = useDeviceStore((state: DeviceState) => state.appVersion);
const setAppVersion = useDeviceStore(state => state.setAppVersion); const setAppVersion = useDeviceStore((state: DeviceState) => state.setAppVersion);
const setSystemVersion = useDeviceStore(state => state.setSystemVersion); const setSystemVersion = useDeviceStore((state: DeviceState) => state.setSystemVersion);
useEffect(() => { useEffect(() => {
if (appVersion) return; if (appVersion) return;
send("getUpdateStatus", {}, async resp => { send("getUpdateStatus", {}, async (resp: JsonRpcResponse) => {
if ("error" in resp) { if ("error" in resp) {
notifications.error(`Failed to get device version: ${resp.error}`); notifications.error(`Failed to get device version: ${resp.error}`);
return return

4
usb.go
View File

@ -51,11 +51,11 @@ func rpcKeypressReport(key byte, press bool) (usbgadget.KeysDownState, error) {
return gadget.KeypressReport(key, press) return gadget.KeypressReport(key, press)
} }
func rpcAbsMouseReport(x, y int, buttons uint8) error { func rpcAbsMouseReport(x int, y int, buttons uint8) error {
return gadget.AbsMouseReport(x, y, buttons) return gadget.AbsMouseReport(x, y, buttons)
} }
func rpcRelMouseReport(dx, dy int8, buttons uint8) error { func rpcRelMouseReport(dx int8, dy int8, buttons uint8) error {
return gadget.RelMouseReport(dx, dy, buttons) return gadget.RelMouseReport(dx, dy, buttons)
} }