mirror of https://github.com/jetkvm/kvm.git
Use the KeysDownState for the infobar
Strong typed in the typescript realm.
This commit is contained in:
parent
b3d8c3b77d
commit
1e57f4bf4f
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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...)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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
4
usb.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue