Skip to content

Commit c088534

Browse files
committed
feat(usb): dynamic usb devices config
1 parent 5c7acca commit c088534

File tree

5 files changed

+420
-139
lines changed

5 files changed

+420
-139
lines changed

config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ type UsbConfig struct {
2020
Product string `json:"product"`
2121
}
2222

23+
type UsbDevicesConfig struct {
24+
AbsoluteMouse bool `json:"absolute_mouse"`
25+
RelativeMouse bool `json:"relative_mouse"`
26+
Keyboard bool `json:"keyboard"`
27+
MassStorage bool `json:"mass_storage"`
28+
}
29+
2330
type Config struct {
2431
CloudURL string `json:"cloud_url"`
2532
CloudAppURL string `json:"cloud_app_url"`
@@ -38,6 +45,7 @@ type Config struct {
3845
DisplayDimAfterSec int `json:"display_dim_after_sec"`
3946
DisplayOffAfterSec int `json:"display_off_after_sec"`
4047
UsbConfig *UsbConfig `json:"usb_config"`
48+
UsbDevices *UsbDevicesConfig `json:"usb_devices"`
4149
}
4250

4351
const configPath = "/userdata/kvm_config.json"
@@ -57,6 +65,12 @@ var defaultConfig = &Config{
5765
Manufacturer: "JetKVM",
5866
Product: "USB Emulation Device",
5967
},
68+
UsbDevices: &UsbDevicesConfig{
69+
AbsoluteMouse: true,
70+
RelativeMouse: true,
71+
Keyboard: true,
72+
MassStorage: true,
73+
},
6074
}
6175

6276
var (
@@ -95,6 +109,10 @@ func LoadConfig() {
95109
loadedConfig.UsbConfig = defaultConfig.UsbConfig
96110
}
97111

112+
if loadedConfig.UsbDevices == nil {
113+
loadedConfig.UsbDevices = defaultConfig.UsbDevices
114+
}
115+
98116
config = &loadedConfig
99117
}
100118

jsonrpc.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -544,19 +544,7 @@ func rpcGetUsbConfig() (UsbConfig, error) {
544544
func rpcSetUsbConfig(usbConfig UsbConfig) error {
545545
LoadConfig()
546546
config.UsbConfig = &usbConfig
547-
548-
err := UpdateGadgetConfig()
549-
if err != nil {
550-
return fmt.Errorf("failed to write gadget config: %w", err)
551-
}
552-
553-
err = SaveConfig()
554-
if err != nil {
555-
return fmt.Errorf("failed to save usb config: %w", err)
556-
}
557-
558-
log.Printf("[jsonrpc.go:rpcSetUsbConfig] usb config set to %s", usbConfig)
559-
return nil
547+
return updateUsbRelatedConfig()
560548
}
561549

562550
func rpcGetWakeOnLanDevices() ([]WakeOnLanDevice, error) {
@@ -751,6 +739,41 @@ func rpcSetSerialSettings(settings SerialSettings) error {
751739
return nil
752740
}
753741

742+
func rpcGetUsbDevices() (UsbDevicesConfig, error) {
743+
return *config.UsbDevices, nil
744+
}
745+
746+
func updateUsbRelatedConfig() error {
747+
if err := UpdateGadgetConfig(); err != nil {
748+
return fmt.Errorf("failed to write gadget config: %w", err)
749+
}
750+
if err := SaveConfig(); err != nil {
751+
return fmt.Errorf("failed to save config: %w", err)
752+
}
753+
return nil
754+
}
755+
756+
func rpcSetUsbDevices(usbDevices UsbDevicesConfig) error {
757+
config.UsbDevices = &usbDevices
758+
return updateUsbRelatedConfig()
759+
}
760+
761+
func rpcSetUsbDeviceState(device string, enabled bool) error {
762+
switch device {
763+
case "absoluteMouse":
764+
config.UsbDevices.AbsoluteMouse = enabled
765+
case "relativeMouse":
766+
config.UsbDevices.RelativeMouse = enabled
767+
case "keyboard":
768+
config.UsbDevices.Keyboard = enabled
769+
case "massStorage":
770+
config.UsbDevices.MassStorage = enabled
771+
default:
772+
return fmt.Errorf("invalid device: %s", device)
773+
}
774+
return updateUsbRelatedConfig()
775+
}
776+
754777
func rpcSetCloudUrl(apiUrl string, appUrl string) error {
755778
config.CloudURL = apiUrl
756779
config.CloudAppURL = appUrl
@@ -831,6 +854,9 @@ var rpcHandlers = map[string]RPCHandler{
831854
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
832855
"getSerialSettings": {Func: rpcGetSerialSettings},
833856
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
857+
"getUsbDevices": {Func: rpcGetUsbDevices},
858+
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
859+
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
834860
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
835861
"getScrollSensitivity": {Func: rpcGetScrollSensitivity},
836862
"setScrollSensitivity": {Func: rpcSetScrollSensitivity, Params: []string{"sensitivity"}},
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { useCallback } from "react";
2+
3+
import { useEffect, useState } from "react";
4+
import { useJsonRpc } from "../hooks/useJsonRpc";
5+
import notifications from "../notifications";
6+
import { SettingsItem } from "../routes/devices.$id.settings";
7+
import Checkbox from "./Checkbox";
8+
9+
export interface USBConfig {
10+
vendor_id: string;
11+
product_id: string;
12+
serial_number: string;
13+
manufacturer: string;
14+
product: string;
15+
}
16+
17+
export interface UsbDeviceConfig {
18+
keyboard: boolean;
19+
absolute_mouse: boolean;
20+
relative_mouse: boolean;
21+
mass_storage: boolean;
22+
}
23+
24+
const defaultUsbDeviceConfig: UsbDeviceConfig = {
25+
keyboard: true,
26+
absolute_mouse: true,
27+
relative_mouse: true,
28+
mass_storage: true,
29+
}
30+
31+
export function UsbDeviceSetting() {
32+
const [send] = useJsonRpc();
33+
34+
const [usbDeviceConfig, setUsbDeviceConfig] = useState<UsbDeviceConfig>(defaultUsbDeviceConfig);
35+
const syncUsbDeviceConfig = useCallback(() => {
36+
send("getUsbDevices", {}, resp => {
37+
if ("error" in resp) {
38+
console.error("Failed to load USB devices:", resp.error);
39+
notifications.error(
40+
`Failed to load USB devices: ${resp.error.data || "Unknown error"}`,
41+
);
42+
} else {
43+
console.log("syncUsbDeviceConfig#getUsbDevices result:", resp.result);
44+
const usbConfigState = resp.result as UsbDeviceConfig;
45+
setUsbDeviceConfig(usbConfigState);
46+
}
47+
});
48+
}, [send]);
49+
50+
const handleUsbConfigChange = useCallback(
51+
(devices: UsbDeviceConfig) => {
52+
send("setUsbDevices", { devices }, resp => {
53+
if ("error" in resp) {
54+
notifications.error(
55+
`Failed to set usb devices: ${resp.error.data || "Unknown error"}`,
56+
);
57+
return;
58+
}
59+
// setUsbConfigProduct(usbConfig.product);
60+
notifications.success(
61+
`USB Devices updated`
62+
);
63+
syncUsbDeviceConfig();
64+
});
65+
},
66+
[send, syncUsbDeviceConfig],
67+
);
68+
69+
const onUsbConfigItemChange = useCallback((key: keyof UsbDeviceConfig) => (e: React.ChangeEvent<HTMLInputElement>) => {
70+
setUsbDeviceConfig((val) => {
71+
val[key] = e.target.checked;
72+
handleUsbConfigChange(val);
73+
return val;
74+
});
75+
}, [handleUsbConfigChange]);
76+
77+
useEffect(() => {
78+
syncUsbDeviceConfig();
79+
}, [syncUsbDeviceConfig]);
80+
81+
return (
82+
<>
83+
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
84+
<div className="space-y-4">
85+
<SettingsItem
86+
title="Enable Keyboard"
87+
description="Enable Keyboard"
88+
>
89+
<Checkbox
90+
checked={usbDeviceConfig.keyboard}
91+
onChange={onUsbConfigItemChange("keyboard")}
92+
/>
93+
</SettingsItem>
94+
</div>
95+
<div className="space-y-4">
96+
<SettingsItem
97+
title="Enable Absolute Mouse (Pointer)"
98+
description="Enable Absolute Mouse (Pointer)"
99+
>
100+
<Checkbox
101+
checked={usbDeviceConfig.absolute_mouse}
102+
onChange={onUsbConfigItemChange("absolute_mouse")}
103+
/>
104+
</SettingsItem>
105+
</div>
106+
<div className="space-y-4">
107+
<SettingsItem
108+
title="Enable Relative Mouse"
109+
description="Enable Relative Mouse"
110+
>
111+
<Checkbox
112+
checked={usbDeviceConfig.relative_mouse}
113+
onChange={onUsbConfigItemChange("relative_mouse")}
114+
/>
115+
</SettingsItem>
116+
</div>
117+
<div className="space-y-4">
118+
<SettingsItem
119+
title="Enable USB Mass Storage"
120+
description="Sometimes it might need to be disabled to prevent issues with certain devices"
121+
>
122+
<Checkbox
123+
checked={usbDeviceConfig.mass_storage}
124+
onChange={onUsbConfigItemChange("mass_storage")}
125+
/>
126+
</SettingsItem>
127+
</div>
128+
</>
129+
);
130+
}

ui/src/routes/devices.$id.settings.hardware.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
77
import notifications from "../notifications";
88
import { SelectMenuBasic } from "@components/SelectMenuBasic";
99
import { UsbConfigSetting } from "../components/UsbConfigSetting";
10+
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
1011
import { FeatureFlag } from "../components/FeatureFlag";
1112

1213
export default function SettingsHardwareRoute() {
@@ -132,6 +133,10 @@ export default function SettingsHardwareRoute() {
132133
<FeatureFlag minAppVersion="0.3.8">
133134
<UsbConfigSetting />
134135
</FeatureFlag>
136+
137+
<FeatureFlag minAppVersion="0.3.8">
138+
<UsbDeviceSetting />
139+
</FeatureFlag>
135140
</div>
136141
);
137142
}

0 commit comments

Comments
 (0)