diff --git a/internal/usbgadget/hid_keyboard.go b/internal/usbgadget/hid_keyboard.go
index 7be48d4..2c4a456 100644
--- a/internal/usbgadget/hid_keyboard.go
+++ b/internal/usbgadget/hid_keyboard.go
@@ -209,7 +209,7 @@ func (u *UsbGadget) listenKeyboardEvents() {
}
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 {
l.Trace().Int("n", n).Msg("expected 1 byte, got")
continue
@@ -250,7 +250,7 @@ func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
return err
}
- _, err := u.keyboardHidFile.Write(append([]byte{modifier, 0x00}, keys[:]...))
+ _, err := u.keyboardHidFile.Write(append([]byte{modifier, 0x00}, keys[:hidKeyBufferSize]...))
if err != nil {
u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
u.keyboardHidFile.Close()
@@ -289,14 +289,8 @@ const (
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
-var KeyCodeToMaskMap = map[uint8]uint8{
+var KeyCodeToMaskMap = map[byte]byte{
LeftControl: ModifierMaskLeftControl,
LeftShift: ModifierMaskLeftShift,
LeftAlt: ModifierMaskLeftAlt,
@@ -314,7 +308,7 @@ func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error)
var state = u.keysDownState
modifier := state.Modifier
- keys := state.Keys[:]
+ keys := append([]byte(nil), state.Keys...)
if mask, exists := KeyCodeToMaskMap[key]; exists {
// 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 {
- 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
- state.Keys = keys
+ var result = KeysDownState{
+ Modifier: modifier,
+ Keys: []byte(keys[:]),
+ }
- u.updateKeyDownState(state)
+ u.updateKeyDownState(result)
- return state, nil
+ return result, nil
}
diff --git a/internal/usbgadget/hid_mouse_absolute.go b/internal/usbgadget/hid_mouse_absolute.go
index 2718f20..c083b60 100644
--- a/internal/usbgadget/hid_mouse_absolute.go
+++ b/internal/usbgadget/hid_mouse_absolute.go
@@ -85,17 +85,17 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
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()
defer u.absMouseLock.Unlock()
err := u.absMouseWriteHidFile([]byte{
- 1, // Report ID 1
- buttons, // Buttons
- uint8(x), // X Low Byte
- uint8(x >> 8), // X High Byte
- uint8(y), // Y Low Byte
- uint8(y >> 8), // Y High Byte
+ 1, // Report ID 1
+ buttons, // Buttons
+ byte(x), // X Low Byte
+ byte(x >> 8), // X High Byte
+ byte(y), // Y Low Byte
+ byte(y >> 8), // Y High Byte
})
if err != nil {
return err
diff --git a/internal/usbgadget/hid_mouse_relative.go b/internal/usbgadget/hid_mouse_relative.go
index 786f265..70cb72c 100644
--- a/internal/usbgadget/hid_mouse_relative.go
+++ b/internal/usbgadget/hid_mouse_relative.go
@@ -75,15 +75,15 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
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()
defer u.relMouseLock.Unlock()
err := u.relMouseWriteHidFile([]byte{
- buttons, // Buttons
- uint8(mx), // X
- uint8(my), // Y
- 0, // Wheel
+ buttons, // Buttons
+ byte(mx), // X
+ byte(my), // Y
+ 0, // Wheel
})
if err != nil {
return err
diff --git a/internal/usbgadget/usbgadget.go b/internal/usbgadget/usbgadget.go
index 0e0604f..3a01a44 100644
--- a/internal/usbgadget/usbgadget.go
+++ b/internal/usbgadget/usbgadget.go
@@ -42,8 +42,8 @@ var defaultUsbGadgetDevices = Devices{
}
type KeysDownState struct {
- Modifier byte `json:"modifier"`
- Keys []byte `json:"keys"`
+ Modifier byte `json:"modifier"`
+ Keys ByteSlice `json:"keys"`
}
// UsbGadget is a struct that represents a USB gadget.
diff --git a/internal/usbgadget/utils.go b/internal/usbgadget/utils.go
index f3d22ed..05fcd3a 100644
--- a/internal/usbgadget/utils.go
+++ b/internal/usbgadget/utils.go
@@ -2,6 +2,7 @@ package usbgadget
import (
"bytes"
+ "encoding/json"
"fmt"
"path/filepath"
"strconv"
@@ -10,6 +11,31 @@ import (
"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 {
pathArr := append([]string{basePath}, paths...)
return filepath.Join(pathArr...)
diff --git a/jsonrpc.go b/jsonrpc.go
index 378c6a0..7d05933 100644
--- a/jsonrpc.go
+++ b/jsonrpc.go
@@ -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)
if len(results) == 0 {
diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx
index 02ff170..8d3b40e 100644
--- a/ui/src/components/InfoBar.tsx
+++ b/ui/src/components/InfoBar.tsx
@@ -1,47 +1,65 @@
-import { useEffect } from "react";
+import { useEffect, useMemo } from "react";
import { cx } from "@/cva.config";
import {
+ HidState,
+ KeysDownState,
+ MouseState,
+ RTCState,
+ SettingsState,
useHidStore,
useMouseStore,
useRTCStore,
useSettingsStore,
useVideoStore,
+ VideoState,
} from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings";
export default function InfoBar() {
- const activeKeys = useHidStore(state => state.activeKeys);
- const activeModifiers = useHidStore(state => state.activeModifiers);
- const mouseX = useMouseStore(state => state.mouseX);
- const mouseY = useMouseStore(state => state.mouseY);
- const mouseMove = useMouseStore(state => state.mouseMove);
+ const keysDownState = useHidStore((state: HidState) => state.keysDownState);
+ const mouseX = useMouseStore((state: MouseState) => state.mouseX);
+ const mouseY = useMouseStore((state: MouseState) => state.mouseY);
+ const mouseMove = useMouseStore((state: MouseState) => state.mouseMove);
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(
- 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 showPressedKeys = useSettingsStore(state => state.showPressedKeys);
+ const showPressedKeys = useSettingsStore((state: SettingsState) => state.showPressedKeys);
useEffect(() => {
if (!rpcDataChannel) return;
rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
- rpcDataChannel.onerror = e =>
+ rpcDataChannel.onerror = (e: Event) =>
console.log(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
}, [rpcDataChannel]);
- const keyboardLedState = useHidStore(state => state.keyboardLedState);
- const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse);
+ const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState);
+ const isTurnServerInUse = useRTCStore((state: RTCState) => state.isTurnServerInUse);
- const usbState = useHidStore(state => state.usbState);
- const hdmiState = useVideoStore(state => state.hdmiState);
+ const usbState = useHidStore((state: HidState) => state.usbState);
+ 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 (
@@ -99,14 +117,7 @@ export default function InfoBar() {
Keys:
- {[
- ...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(", ")}
+ {displayKeys}
)}
diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx
index c377154..a938a6b 100644
--- a/ui/src/components/WebRTCVideo.tsx
+++ b/ui/src/components/WebRTCVideo.tsx
@@ -11,10 +11,14 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
import { cx } from "@/cva.config";
import { keys } from "@/keyboardMappings";
import {
+ MouseState,
+ RTCState,
+ SettingsState,
useMouseStore,
useRTCStore,
useSettingsStore,
useVideoStore,
+ VideoState,
} from "@/hooks/stores";
import {
@@ -27,15 +31,15 @@ import {
export default function WebRTCVideo() {
// Video and stream related refs and states
const videoElm = useRef
(null);
- const mediaStream = useRTCStore(state => state.mediaStream);
+ const mediaStream = useRTCStore((state: RTCState) => state.mediaStream);
const [isPlaying, setIsPlaying] = useState(false);
- const peerConnectionState = useRTCStore(state => state.peerConnectionState);
+ const peerConnectionState = useRTCStore((state: RTCState) => state.peerConnectionState);
const [isPointerLockActive, setIsPointerLockActive] = useState(false);
// Store hooks
const settings = useSettingsStore();
const { sendKeypressEvent, resetKeyboardState } = useKeyboard();
- const setMousePosition = useMouseStore(state => state.setMousePosition);
- const setMouseMove = useMouseStore(state => state.setMouseMove);
+ const setMousePosition = useMouseStore((state: MouseState) => state.setMousePosition);
+ const setMouseMove = useMouseStore((state: MouseState) => state.setMouseMove);
const {
setClientSize: setVideoClientSize,
setSize: setVideoSize,
@@ -46,15 +50,15 @@ export default function WebRTCVideo() {
} = useVideoStore();
// Video enhancement settings
- const videoSaturation = useSettingsStore(state => state.videoSaturation);
- const videoBrightness = useSettingsStore(state => state.videoBrightness);
- const videoContrast = useSettingsStore(state => state.videoContrast);
+ const videoSaturation = useSettingsStore((state: SettingsState) => state.videoSaturation);
+ const videoBrightness = useSettingsStore((state: SettingsState) => state.videoBrightness);
+ const videoContrast = useSettingsStore((state: SettingsState) => state.videoContrast);
// RTC related states
- const peerConnection = useRTCStore(state => state.peerConnection);
+ const peerConnection = useRTCStore((state: RTCState ) => state.peerConnection);
// 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 isVideoLoading = !isPlaying;
diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts
index e788faa..223d994 100644
--- a/ui/src/hooks/stores.ts
+++ b/ui/src/hooks/stores.ts
@@ -47,12 +47,12 @@ export interface User {
picture?: string;
}
-interface UserState {
+export interface UserState {
user: User | null;
setUser: (user: User | null) => void;
}
-interface UIState {
+export interface UIState {
sidebarView: AvailableSidebarViews | null;
setSidebarView: (view: AvailableSidebarViews | null) => void;
@@ -68,21 +68,21 @@ interface UIState {
setAttachedVirtualKeyboardVisibility: (enabled: boolean) => void;
terminalType: AvailableTerminalTypes;
- setTerminalType: (enabled: UIState["terminalType"]) => void;
+ setTerminalType: (type: UIState["terminalType"]) => void;
}
export const useUiStore = create(set => ({
terminalType: "none",
- setTerminalType: type => set({ terminalType: type }),
+ setTerminalType: (type: UIState["terminalType"]) => set({ terminalType: type }),
sidebarView: null,
- setSidebarView: view => set({ sidebarView: view }),
+ setSidebarView: (view: AvailableSidebarViews | null) => set({ sidebarView: view }),
disableVideoFocusTrap: false,
- setDisableVideoFocusTrap: enabled => set({ disableVideoFocusTrap: enabled }),
+ setDisableVideoFocusTrap: (enabled: boolean) => set({ disableVideoFocusTrap: enabled }),
isWakeOnLanModalVisible: false,
- setWakeOnLanModalVisibility: enabled => set({ isWakeOnLanModalVisible: enabled }),
+ setWakeOnLanModalVisibility: (enabled: boolean) => set({ isWakeOnLanModalVisible: enabled }),
toggleSidebarView: view =>
set(state => {
@@ -94,11 +94,11 @@ export const useUiStore = create(set => ({
}),
isAttachedVirtualKeyboardVisible: true,
- setAttachedVirtualKeyboardVisibility: enabled =>
+ setAttachedVirtualKeyboardVisibility: (enabled: boolean) =>
set({ isAttachedVirtualKeyboardVisible: enabled }),
}));
-interface RTCState {
+export interface RTCState {
peerConnection: RTCPeerConnection | null;
setPeerConnection: (pc: RTCState["peerConnection"]) => void;
@@ -118,18 +118,18 @@ interface RTCState {
setMediaStream: (stream: MediaStream) => void;
videoStreamStats: RTCInboundRtpStreamStats | null;
- appendVideoStreamStats: (state: RTCInboundRtpStreamStats) => void;
+ appendVideoStreamStats: (stats: RTCInboundRtpStreamStats) => void;
videoStreamStatsHistory: Map;
isTurnServerInUse: boolean;
setTurnServerInUse: (inUse: boolean) => void;
inboundRtpStats: Map;
- appendInboundRtpStats: (state: RTCInboundRtpStreamStats) => void;
+ appendInboundRtpStats: (stats: RTCInboundRtpStreamStats) => void;
clearInboundRtpStats: () => void;
candidatePairStats: Map;
- appendCandidatePairStats: (pair: RTCIceCandidatePairStats) => void;
+ appendCandidatePairStats: (stats: RTCIceCandidatePairStats) => void;
clearCandidatePairStats: () => void;
// 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
diskDataChannelStats: Map;
- appendDiskDataChannelStats: (stat: RTCDataChannelStats) => void;
+ appendDiskDataChannelStats: (stats: RTCDataChannelStats) => void;
terminalChannel: RTCDataChannel | null;
setTerminalChannel: (channel: RTCDataChannel) => void;
@@ -149,78 +149,78 @@ interface RTCState {
export const useRTCStore = create(set => ({
peerConnection: null,
- setPeerConnection: pc => set({ peerConnection: pc }),
+ setPeerConnection: (pc: RTCState["peerConnection"]) => set({ peerConnection: pc }),
rpcDataChannel: null,
- setRpcDataChannel: channel => set({ rpcDataChannel: channel }),
+ setRpcDataChannel: (channel: RTCDataChannel) => set({ rpcDataChannel: channel }),
transceiver: null,
- setTransceiver: transceiver => set({ transceiver }),
+ setTransceiver: (transceiver: RTCRtpTransceiver) => set({ transceiver }),
peerConnectionState: null,
- setPeerConnectionState: state => set({ peerConnectionState: state }),
+ setPeerConnectionState: (state: RTCPeerConnectionState) => set({ peerConnectionState: state }),
diskChannel: null,
- setDiskChannel: channel => set({ diskChannel: channel }),
+ setDiskChannel: (channel: RTCDataChannel) => set({ diskChannel: channel }),
mediaStream: null,
- setMediaStream: stream => set({ mediaStream: stream }),
+ setMediaStream: (stream: MediaStream) => set({ mediaStream: stream }),
videoStreamStats: null,
- appendVideoStreamStats: stats => set({ videoStreamStats: stats }),
+ appendVideoStreamStats: (stats: RTCInboundRtpStreamStats) => set({ videoStreamStats: stats }),
videoStreamStatsHistory: new Map(),
isTurnServerInUse: false,
- setTurnServerInUse: inUse => set({ isTurnServerInUse: inUse }),
+ setTurnServerInUse: (inUse: boolean) => set({ isTurnServerInUse: inUse }),
inboundRtpStats: new Map(),
- appendInboundRtpStats: newStat => {
+ appendInboundRtpStats: (stats: RTCInboundRtpStreamStats) => {
set(prevState => ({
- inboundRtpStats: appendStatToMap(newStat, prevState.inboundRtpStats),
+ inboundRtpStats: appendStatToMap(stats, prevState.inboundRtpStats),
}));
},
clearInboundRtpStats: () => set({ inboundRtpStats: new Map() }),
candidatePairStats: new Map(),
- appendCandidatePairStats: newStat => {
+ appendCandidatePairStats: (stats: RTCIceCandidatePairStats) => {
set(prevState => ({
- candidatePairStats: appendStatToMap(newStat, prevState.candidatePairStats),
+ candidatePairStats: appendStatToMap(stats, prevState.candidatePairStats),
}));
},
clearCandidatePairStats: () => set({ candidatePairStats: new Map() }),
localCandidateStats: new Map(),
- appendLocalCandidateStats: newStat => {
+ appendLocalCandidateStats: (stats: RTCIceCandidateStats) => {
set(prevState => ({
- localCandidateStats: appendStatToMap(newStat, prevState.localCandidateStats),
+ localCandidateStats: appendStatToMap(stats, prevState.localCandidateStats),
}));
},
remoteCandidateStats: new Map(),
- appendRemoteCandidateStats: newStat => {
+ appendRemoteCandidateStats: (stats: RTCIceCandidateStats) => {
set(prevState => ({
- remoteCandidateStats: appendStatToMap(newStat, prevState.remoteCandidateStats),
+ remoteCandidateStats: appendStatToMap(stats, prevState.remoteCandidateStats),
}));
},
diskDataChannelStats: new Map(),
- appendDiskDataChannelStats: newStat => {
+ appendDiskDataChannelStats: (stats: RTCDataChannelStats) => {
set(prevState => ({
- diskDataChannelStats: appendStatToMap(newStat, prevState.diskDataChannelStats),
+ diskDataChannelStats: appendStatToMap(stats, prevState.diskDataChannelStats),
}));
},
// Add these new properties to the store implementation
terminalChannel: null,
- setTerminalChannel: channel => set({ terminalChannel: channel }),
+ setTerminalChannel: (channel: RTCDataChannel) => set({ terminalChannel: channel }),
}));
-interface MouseMove {
+export interface MouseMove {
x: number;
y: number;
buttons: number;
}
-interface MouseState {
+export interface MouseState {
mouseX: number;
mouseY: number;
mouseMove?: MouseMove;
@@ -232,9 +232,14 @@ export const useMouseStore = create(set => ({
mouseX: 0,
mouseY: 0,
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;
+}
+
export interface VideoState {
width: number;
height: number;
@@ -263,13 +268,13 @@ export const useVideoStore = create(set => ({
clientHeight: 0,
// The video element's client size
- setClientSize: (clientWidth, clientHeight) => set({ clientWidth, clientHeight }),
+ setClientSize: (clientWidth: number, clientHeight: number) => set({ clientWidth, clientHeight }),
// Resolution
- setSize: (width, height) => set({ width, height }),
+ setSize: (width: number, height: number) => set({ width, height }),
hdmiState: "connecting",
- setHdmiState: state => {
+ setHdmiState: (state: HdmiState) => {
if (!state) return;
const { ready, error } = state;
@@ -283,7 +288,7 @@ export const useVideoStore = create(set => ({
},
}));
-interface SettingsState {
+export interface SettingsState {
isCursorHidden: boolean;
setCursorVisibility: (enabled: boolean) => void;
@@ -325,17 +330,17 @@ export const useSettingsStore = create(
persist(
set => ({
isCursorHidden: false,
- setCursorVisibility: enabled => set({ isCursorHidden: enabled }),
+ setCursorVisibility: (enabled: boolean) => set({ isCursorHidden: enabled }),
mouseMode: "absolute",
- setMouseMode: mode => set({ mouseMode: mode }),
+ setMouseMode: (mode: string) => set({ mouseMode: mode }),
debugMode: import.meta.env.DEV,
- setDebugMode: enabled => set({ debugMode: enabled }),
+ setDebugMode: (enabled: boolean) => set({ debugMode: enabled }),
// Add developer mode with default value
developerMode: false,
- setDeveloperMode: enabled => set({ developerMode: enabled }),
+ setDeveloperMode: (enabled: boolean) => set({ developerMode: enabled }),
displayRotation: "270",
setDisplayRotation: (rotation: string) => set({ displayRotation: rotation }),
@@ -349,21 +354,21 @@ export const useSettingsStore = create(
set({ backlightSettings: settings }),
keyboardLayout: "en-US",
- setKeyboardLayout: layout => set({ keyboardLayout: layout }),
+ setKeyboardLayout: (layout: string) => set({ keyboardLayout: layout }),
scrollThrottling: 0,
- setScrollThrottling: value => set({ scrollThrottling: value }),
+ setScrollThrottling: (value: number) => set({ scrollThrottling: value }),
showPressedKeys: true,
- setShowPressedKeys: show => set({ showPressedKeys: show }),
+ setShowPressedKeys: (show: boolean) => set({ showPressedKeys: show }),
// Video enhancement settings with default values (1.0 = normal)
videoSaturation: 1.0,
- setVideoSaturation: value => set({ videoSaturation: value }),
+ setVideoSaturation: (value: number) => set({ videoSaturation: value }),
videoBrightness: 1.0,
- setVideoBrightness: value => set({ videoBrightness: value }),
+ setVideoBrightness: (value: number) => set({ videoBrightness: value }),
videoContrast: 1.0,
- setVideoContrast: value => set({ videoContrast: value }),
+ setVideoContrast: (value: number) => set({ videoContrast: value }),
}),
{
name: "settings",
@@ -403,23 +408,23 @@ export interface MountMediaState {
export const useMountMediaStore = create(set => ({
localFile: null,
- setLocalFile: file => set({ localFile: file }),
+ setLocalFile: (file: MountMediaState["localFile"]) => set({ localFile: file }),
remoteVirtualMediaState: null,
- setRemoteVirtualMediaState: state => set({ remoteVirtualMediaState: state }),
+ setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => set({ remoteVirtualMediaState: state }),
modalView: "mode",
- setModalView: view => set({ modalView: view }),
+ setModalView: (view: MountMediaState["modalView"]) => set({ modalView: view }),
isMountMediaDialogOpen: false,
- setIsMountMediaDialogOpen: isOpen => set({ isMountMediaDialogOpen: isOpen }),
+ setIsMountMediaDialogOpen: (isOpen: MountMediaState["isMountMediaDialogOpen"]) => set({ isMountMediaDialogOpen: isOpen }),
uploadedFiles: [],
- addUploadedFile: file =>
+ addUploadedFile: (file: { name: string; size: string; uploadedAt: string }) =>
set(state => ({ uploadedFiles: [...state.uploadedFiles, file] })),
errorMessage: null,
- setErrorMessage: message => set({ errorMessage: message }),
+ setErrorMessage: (message: string | null) => set({ errorMessage: message }),
}));
export interface KeyboardLedState {
@@ -437,14 +442,6 @@ export interface KeysDownState {
}
export interface HidState {
- activeKeys: number[];
- activeModifiers: number[];
-
- updateActiveKeysAndModifiers: (keysAndModifiers: {
- keys: number[];
- modifiers: number[];
- }) => void;
-
altGrArmed: boolean;
setAltGrArmed: (armed: boolean) => void;
@@ -470,38 +467,31 @@ export interface HidState {
setUsbState: (state: HidState["usbState"]) => void;
}
-export const useHidStore = create((set) => ({
- activeKeys: [],
- activeModifiers: [],
-
- updateActiveKeysAndModifiers: ({ keys, modifiers }) => {
- return set({ activeKeys: keys, activeModifiers: modifiers });
- },
-
+export const useHidStore = create(set => ({
altGrArmed: false,
- setAltGrArmed: armed => set({ altGrArmed: armed }),
+ setAltGrArmed: (armed: boolean): void => set({ altGrArmed: armed }),
altGrTimer: 0,
- setAltGrTimer: timeout => set({ altGrTimer: timeout }),
+ setAltGrTimer: (timeout: number | null): void => set({ altGrTimer: timeout }),
altGrCtrlTime: 0,
- setAltGrCtrlTime: time => set({ altGrCtrlTime: time }),
+ setAltGrCtrlTime: (time: number): void => set({ altGrCtrlTime: time }),
keyboardLedState: undefined,
- setKeyboardLedState: ledState => set({ keyboardLedState: ledState }),
+ setKeyboardLedState: (ledState: KeyboardLedState): void => set({ keyboardLedState: ledState }),
keysDownState: undefined,
- setKeysDownState: state => set({ keysDownState: state }),
+ setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }),
isVirtualKeyboardEnabled: false,
- setVirtualKeyboardEnabled: enabled => set({ isVirtualKeyboardEnabled: enabled }),
+ setVirtualKeyboardEnabled: (enabled: boolean): void => set({ isVirtualKeyboardEnabled: enabled }),
isPasteModeEnabled: false,
- setPasteModeEnabled: enabled => set({ isPasteModeEnabled: enabled }),
+ setPasteModeEnabled: (enabled: boolean): void => set({ isPasteModeEnabled: enabled }),
// Add these new properties for USB state
usbState: "not attached",
- setUsbState: state => set({ usbState: state }),
+ setUsbState: (state: HidState["usbState"]) => set({ usbState: state }),
}));
export const useUserStore = create(set => ({
@@ -559,7 +549,7 @@ export interface UpdateState {
export const useUpdateStore = create(set => ({
isUpdatePending: false,
- setIsUpdatePending: isPending => set({ isUpdatePending: isPending }),
+ setIsUpdatePending: (isPending: boolean) => set({ isUpdatePending: isPending }),
setOtaState: state => set({ otaState: state }),
otaState: {
@@ -583,12 +573,12 @@ export const useUpdateStore = create(set => ({
},
updateDialogHasBeenMinimized: false,
- setUpdateDialogHasBeenMinimized: hasBeenMinimized =>
+ setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
modalView: "loading",
- setModalView: view => set({ modalView: view }),
+ setModalView: (view: UpdateState["modalView"]) => set({ modalView: view }),
updateErrorMessage: null,
- setUpdateErrorMessage: errorMessage => set({ updateErrorMessage: errorMessage }),
+ setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
}));
interface UsbConfigModalState {
@@ -609,8 +599,8 @@ export interface UsbConfigState {
export const useUsbConfigModalStore = create(set => ({
modalView: "updateUsbConfig",
errorMessage: null,
- setModalView: view => set({ modalView: view }),
- setErrorMessage: message => set({ errorMessage: message }),
+ setModalView: (view: UsbConfigModalState["modalView"]) => set({ modalView: view }),
+ setErrorMessage: (message: string | null) => set({ errorMessage: message }),
}));
interface LocalAuthModalState {
@@ -626,7 +616,7 @@ interface LocalAuthModalState {
export const useLocalAuthModalStore = create(set => ({
modalView: "createPassword",
- setModalView: view => set({ modalView: view }),
+ setModalView: (view: LocalAuthModalState["modalView"]) => set({ modalView: view }),
}));
export interface DeviceState {
@@ -641,8 +631,8 @@ export const useDeviceStore = create(set => ({
appVersion: null,
systemVersion: null,
- setAppVersion: version => set({ appVersion: version }),
- setSystemVersion: version => set({ systemVersion: version }),
+ setAppVersion: (version: string) => set({ appVersion: version }),
+ setSystemVersion: (version: string) => set({ systemVersion: version }),
}));
export interface DhcpLease {
@@ -808,7 +798,7 @@ export const useMacrosStore = create((set, get) => ({
try {
await new Promise((resolve, reject) => {
- sendFn("getKeyboardMacros", {}, response => {
+ sendFn("getKeyboardMacros", {}, (response: JsonRpcResponse) => {
if (response.error) {
console.error("Error loading macros:", response.error);
reject(new Error(response.error.message));
@@ -888,7 +878,7 @@ export const useMacrosStore = create((set, get) => ({
sendFn(
"setKeyboardMacros",
{ params: { macros: macrosWithSortOrder } },
- response => {
+ (response: JsonRpcResponse) => {
resolve(response);
},
);
diff --git a/ui/src/hooks/useJsonRpc.ts b/ui/src/hooks/useJsonRpc.ts
index 92b56ff..5f088dc 100644
--- a/ui/src/hooks/useJsonRpc.ts
+++ b/ui/src/hooks/useJsonRpc.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect } from "react";
-import { useRTCStore } from "@/hooks/stores";
+import { RTCState, useRTCStore } from "@/hooks/stores";
export interface JsonRpcRequest {
jsonrpc: string;
@@ -33,7 +33,7 @@ const callbackStore = new Map void>(
let requestCounter = 0;
export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
- const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
+ const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
const send = useCallback(
(method: string, params: unknown, callback?: (resp: JsonRpcResponse) => void) => {
diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts
index 7861dd9..a77946c 100644
--- a/ui/src/hooks/useKeyboard.ts
+++ b/ui/src/hooks/useKeyboard.ts
@@ -1,16 +1,14 @@
import { useCallback } from "react";
-import { KeysDownState, useHidStore, useRTCStore } from "@/hooks/stores";
-import { useJsonRpc } from "@/hooks/useJsonRpc";
+import { KeysDownState, HidState, useHidStore, RTCState, useRTCStore } from "@/hooks/stores";
+import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { keys, modifiers } from "@/keyboardMappings";
export default function useKeyboard() {
const [send] = useJsonRpc();
- const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
- const updateActiveKeysAndModifiers = useHidStore(
- state => state.updateActiveKeysAndModifiers,
- );
+ const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
+ const setKeysDownState = useHidStore((state: HidState) => state.setKeysDownState);
const sendKeyboardEvent = useCallback(
(keys: number[], modifiers: number[]) => {
@@ -18,35 +16,28 @@ export default function useKeyboard() {
const accModifier = modifiers.reduce((acc, val) => acc + val, 0);
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
- 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(
(key: number, press: boolean) => {
if (rpcDataChannel?.readyState !== "open") return;
- send("keypressReport", { key, press }, resp => {
+ send("keypressReport", { key, press }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to send keypress:", resp.error);
} else {
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
- updateActiveKeysAndModifiers({ keys: keysDown, modifiers: activeModifiers });
+ setKeysDownState(keyDownState);
}
});
},
- [rpcDataChannel?.readyState, send, updateActiveKeysAndModifiers],
+ [rpcDataChannel?.readyState, send, setKeysDownState],
);
const resetKeyboardState = useCallback(() => {
diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx
index d5753ab..53177c5 100644
--- a/ui/src/routes/devices.$id.tsx
+++ b/ui/src/routes/devices.$id.tsx
@@ -18,10 +18,14 @@ import useWebSocket from "react-use-websocket";
import { cx } from "@/cva.config";
import {
+ DeviceState,
HidState,
KeyboardLedState,
KeysDownState,
+ MountMediaState,
NetworkState,
+ RTCState,
+ UIState,
UpdateState,
useDeviceStore,
useHidStore,
@@ -38,7 +42,7 @@ import WebRTCVideo from "@components/WebRTCVideo";
import { checkAuth, isInCloud, isOnDevice } from "@/main";
import DashboardNavbar from "@components/Header";
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 { CLOUD_API, DEVICE_API } from "@/ui.config";
@@ -128,18 +132,18 @@ export default function KvmIdRoute() {
const authMode = "authMode" in loaderResp ? loaderResp.authMode : null;
const params = useParams() as { id: string };
- const sidebarView = useUiStore(state => state.sidebarView);
+ const sidebarView = useUiStore((state: UIState) => state.sidebarView);
const [queryParams, setQueryParams] = useSearchParams();
- const setIsTurnServerInUse = useRTCStore(state => state.setTurnServerInUse);
- const peerConnection = useRTCStore(state => state.peerConnection);
- const setPeerConnectionState = useRTCStore(state => state.setPeerConnectionState);
- const peerConnectionState = useRTCStore(state => state.peerConnectionState);
- const setMediaMediaStream = useRTCStore(state => state.setMediaStream);
- const setPeerConnection = useRTCStore(state => state.setPeerConnection);
- const setDiskChannel = useRTCStore(state => state.setDiskChannel);
- const setRpcDataChannel = useRTCStore(state => state.setRpcDataChannel);
- const setTransceiver = useRTCStore(state => state.setTransceiver);
+ const setIsTurnServerInUse = useRTCStore((state: RTCState) => state.setTurnServerInUse);
+ const peerConnection = useRTCStore((state: RTCState) => state.peerConnection);
+ const setPeerConnectionState = useRTCStore((state: RTCState) => state.setPeerConnectionState);
+ const peerConnectionState = useRTCStore((state: RTCState) => state.peerConnectionState);
+ const setMediaMediaStream = useRTCStore((state: RTCState) => state.setMediaStream);
+ const setPeerConnection = useRTCStore((state: RTCState) => state.setPeerConnection);
+ const setDiskChannel = useRTCStore((state: RTCState) => state.setDiskChannel);
+ const setRpcDataChannel = useRTCStore((state: RTCState) => state.setRpcDataChannel);
+ const setTransceiver = useRTCStore((state: RTCState) => state.setTransceiver);
const location = useLocation();
const isLegacySignalingEnabled = useRef(false);
@@ -513,9 +517,9 @@ export default function KvmIdRoute() {
}, [peerConnectionState, cleanupAndStopReconnecting]);
// Cleanup effect
- const clearInboundRtpStats = useRTCStore(state => state.clearInboundRtpStats);
- const clearCandidatePairStats = useRTCStore(state => state.clearCandidatePairStats);
- const setSidebarView = useUiStore(state => state.setSidebarView);
+ const clearInboundRtpStats = useRTCStore((state: RTCState) => state.clearInboundRtpStats);
+ const clearCandidatePairStats = useRTCStore((state: RTCState) => state.clearCandidatePairStats);
+ const setSidebarView = useUiStore((state: UIState) => state.setSidebarView);
useEffect(() => {
return () => {
@@ -550,7 +554,7 @@ export default function KvmIdRoute() {
}, [peerConnectionState, setIsTurnServerInUse]);
// TURN server usage reporting
- const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse);
+ const isTurnServerInUse = useRTCStore((state: RTCState) => state.isTurnServerInUse);
const lastBytesReceived = useRef(0);
const lastBytesSent = useRef(0);
@@ -583,16 +587,16 @@ export default function KvmIdRoute() {
});
}, 10000);
- const setNetworkState = useNetworkStateStore(state => state.setNetworkState);
+ const setNetworkState = useNetworkStateStore((state: NetworkState) => state.setNetworkState);
- const setUsbState = useHidStore(state => state.setUsbState);
- const setHdmiState = useVideoStore(state => state.setHdmiState);
+ const setUsbState = useHidStore((state: HidState) => state.setUsbState);
+ const setHdmiState = useVideoStore((state: VideoState) => state.setHdmiState);
- const keyboardLedState = useHidStore(state => state.keyboardLedState);
- const setKeyboardLedState = useHidStore(state => state.setKeyboardLedState);
+ const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState);
+ const setKeyboardLedState = useHidStore((state: HidState) => state.setKeyboardLedState);
- const keysDownState = useHidStore(state => state.keysDownState);
- const setKeysDownState = useHidStore(state => state.setKeysDownState);
+ const keysDownState = useHidStore((state: HidState) => state.keysDownState);
+ const setKeysDownState = useHidStore((state: HidState) => state.setKeysDownState);
const [hasUpdated, setHasUpdated] = useState(false);
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);
useEffect(() => {
if (rpcDataChannel?.readyState !== "open") return;
- send("getVideoState", {}, resp => {
+ send("getVideoState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setHdmiState(resp.result as Parameters[0]);
});
@@ -669,7 +673,7 @@ export default function KvmIdRoute() {
if (keyboardLedState !== undefined) return;
console.log("Requesting keyboard led state");
- send("getKeyboardLedState", {}, resp => {
+ send("getKeyboardLedState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to get keyboard led state", resp.error);
return;
@@ -685,7 +689,7 @@ export default function KvmIdRoute() {
if (keysDownState !== undefined) return;
console.log("Requesting keys down state");
- send("getKeyDownState", {}, resp => {
+ send("getKeyDownState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to get key down state", resp.error);
return;
@@ -702,8 +706,8 @@ export default function KvmIdRoute() {
}
}, [navigate, navigateTo, queryParams, setModalView, setQueryParams]);
- const diskChannel = useRTCStore(state => state.diskChannel)!;
- const file = useMountMediaStore(state => state.localFile)!;
+ const diskChannel = useRTCStore((state: RTCState) => state.diskChannel)!;
+ const file = useMountMediaStore((state: MountMediaState) => state.localFile)!;
useEffect(() => {
if (!diskChannel || !file) return;
diskChannel.onmessage = async e => {
@@ -723,7 +727,7 @@ export default function KvmIdRoute() {
}, [diskChannel, file]);
// System update
- const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap);
+ const disableVideoFocusTrap = useUiStore((state: UIState) => state.disableVideoFocusTrap);
const [kvmTerminal, setKvmTerminal] = useState(null);
const [serialConsole, setSerialConsole] = useState(null);
@@ -744,14 +748,14 @@ export default function KvmIdRoute() {
if (location.pathname !== "/other-session") navigateTo("/");
}, [navigateTo, location.pathname]);
- const appVersion = useDeviceStore(state => state.appVersion);
- const setAppVersion = useDeviceStore(state => state.setAppVersion);
- const setSystemVersion = useDeviceStore(state => state.setSystemVersion);
+ const appVersion = useDeviceStore((state: DeviceState) => state.appVersion);
+ const setAppVersion = useDeviceStore((state: DeviceState) => state.setAppVersion);
+ const setSystemVersion = useDeviceStore((state: DeviceState) => state.setSystemVersion);
useEffect(() => {
if (appVersion) return;
- send("getUpdateStatus", {}, async resp => {
+ send("getUpdateStatus", {}, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Failed to get device version: ${resp.error}`);
return
diff --git a/usb.go b/usb.go
index 3cf6908..5c8036c 100644
--- a/usb.go
+++ b/usb.go
@@ -51,11 +51,11 @@ func rpcKeypressReport(key byte, press bool) (usbgadget.KeysDownState, error) {
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)
}
-func rpcRelMouseReport(dx, dy int8, buttons uint8) error {
+func rpcRelMouseReport(dx int8, dy int8, buttons uint8) error {
return gadget.RelMouseReport(dx, dy, buttons)
}