|
| 1 | +import { useMemo } from "react"; |
| 2 | + |
| 3 | +import { useCallback } from "react"; |
| 4 | + |
| 5 | +import { useEffect, useState } from "react"; |
| 6 | +import { UsbConfigState } from "../hooks/stores"; |
| 7 | +import { useJsonRpc } from "../hooks/useJsonRpc"; |
| 8 | +import notifications from "../notifications"; |
| 9 | +import { SettingsItem } from "../routes/devices.$id.settings"; |
| 10 | +import { SelectMenuBasic } from "./SelectMenuBasic"; |
| 11 | +import USBConfigDialog from "./USBConfigDialog"; |
| 12 | + |
| 13 | +const generatedSerialNumber = [generateNumber(1, 9), generateHex(7, 7), 0, 1].join("&"); |
| 14 | + |
| 15 | +function generateNumber(min: number, max: number) { |
| 16 | + return Math.floor(Math.random() * (max - min + 1) + min); |
| 17 | +} |
| 18 | + |
| 19 | +function generateHex(min: number, max: number) { |
| 20 | + const len = generateNumber(min, max); |
| 21 | + const n = (Math.random() * 0xfffff * 1000000).toString(16); |
| 22 | + return n.slice(0, len); |
| 23 | +} |
| 24 | + |
| 25 | +export interface USBConfig { |
| 26 | + vendor_id: string; |
| 27 | + product_id: string; |
| 28 | + serial_number: string; |
| 29 | + manufacturer: string; |
| 30 | + product: string; |
| 31 | +} |
| 32 | + |
| 33 | +const usbConfigs = [ |
| 34 | + { |
| 35 | + label: "JetKVM Default", |
| 36 | + value: "USB Emulation Device", |
| 37 | + }, |
| 38 | + { |
| 39 | + label: "Logitech Universal Adapter", |
| 40 | + value: "Logitech USB Input Device", |
| 41 | + }, |
| 42 | + { |
| 43 | + label: "Microsoft Wireless MultiMedia Keyboard", |
| 44 | + value: "Wireless MultiMedia Keyboard", |
| 45 | + }, |
| 46 | + { |
| 47 | + label: "Dell Multimedia Pro Keyboard", |
| 48 | + value: "Multimedia Pro Keyboard", |
| 49 | + }, |
| 50 | +]; |
| 51 | + |
| 52 | +type UsbConfigMap = Record<string, USBConfig>; |
| 53 | + |
| 54 | +export function UsbConfigSetting() { |
| 55 | + const [send] = useJsonRpc(); |
| 56 | + |
| 57 | + const [usbConfigProduct, setUsbConfigProduct] = useState(""); |
| 58 | + const [deviceId, setDeviceId] = useState(""); |
| 59 | + const usbConfigData: UsbConfigMap = useMemo( |
| 60 | + () => ({ |
| 61 | + "USB Emulation Device": { |
| 62 | + vendor_id: "0x1d6b", |
| 63 | + product_id: "0x0104", |
| 64 | + serial_number: deviceId, |
| 65 | + manufacturer: "JetKVM", |
| 66 | + product: "USB Emulation Device", |
| 67 | + }, |
| 68 | + "Logitech USB Input Device": { |
| 69 | + vendor_id: "0x046d", |
| 70 | + product_id: "0xc52b", |
| 71 | + serial_number: generatedSerialNumber, |
| 72 | + manufacturer: "Logitech (x64)", |
| 73 | + product: "Logitech USB Input Device", |
| 74 | + }, |
| 75 | + "Wireless MultiMedia Keyboard": { |
| 76 | + vendor_id: "0x045e", |
| 77 | + product_id: "0x005f", |
| 78 | + serial_number: generatedSerialNumber, |
| 79 | + manufacturer: "Microsoft", |
| 80 | + product: "Wireless MultiMedia Keyboard", |
| 81 | + }, |
| 82 | + "Multimedia Pro Keyboard": { |
| 83 | + vendor_id: "0x413c", |
| 84 | + product_id: "0x2011", |
| 85 | + serial_number: generatedSerialNumber, |
| 86 | + manufacturer: "Dell Inc.", |
| 87 | + product: "Multimedia Pro Keyboard", |
| 88 | + }, |
| 89 | + }), |
| 90 | + [deviceId], |
| 91 | + ); |
| 92 | + |
| 93 | + const syncUsbConfigProduct = useCallback(() => { |
| 94 | + send("getUsbConfig", {}, resp => { |
| 95 | + if ("error" in resp) { |
| 96 | + console.error("Failed to load USB Config:", resp.error); |
| 97 | + notifications.error( |
| 98 | + `Failed to load USB Config: ${resp.error.data || "Unknown error"}`, |
| 99 | + ); |
| 100 | + } else { |
| 101 | + console.log("syncUsbConfigProduct#getUsbConfig result:", resp.result); |
| 102 | + const usbConfigState = resp.result as UsbConfigState; |
| 103 | + const product = usbConfigs.map(u => u.value).includes(usbConfigState.product) |
| 104 | + ? usbConfigState.product |
| 105 | + : "custom"; |
| 106 | + setUsbConfigProduct(product); |
| 107 | + } |
| 108 | + }); |
| 109 | + }, [send]); |
| 110 | + |
| 111 | + const handleUsbConfigChange = useCallback( |
| 112 | + (usbConfig: USBConfig) => { |
| 113 | + send("setUsbConfig", { usbConfig }, resp => { |
| 114 | + if ("error" in resp) { |
| 115 | + notifications.error( |
| 116 | + `Failed to set usb config: ${resp.error.data || "Unknown error"}`, |
| 117 | + ); |
| 118 | + return; |
| 119 | + } |
| 120 | + // setUsbConfigProduct(usbConfig.product); |
| 121 | + notifications.success( |
| 122 | + `USB Config set to ${usbConfig.manufacturer} ${usbConfig.product}`, |
| 123 | + ); |
| 124 | + syncUsbConfigProduct(); |
| 125 | + }); |
| 126 | + }, |
| 127 | + [send, syncUsbConfigProduct], |
| 128 | + ); |
| 129 | + |
| 130 | + useEffect(() => { |
| 131 | + send("getDeviceID", {}, async resp => { |
| 132 | + if ("error" in resp) { |
| 133 | + return notifications.error( |
| 134 | + `Failed to get device ID: ${resp.error.data || "Unknown error"}`, |
| 135 | + ); |
| 136 | + } |
| 137 | + setDeviceId(resp.result as string); |
| 138 | + }); |
| 139 | + |
| 140 | + syncUsbConfigProduct(); |
| 141 | + }, [send, syncUsbConfigProduct]); |
| 142 | + |
| 143 | + return ( |
| 144 | + <> |
| 145 | + <div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" /> |
| 146 | + |
| 147 | + <SettingsItem |
| 148 | + title="USB Device Emulation" |
| 149 | + description="Set a Preconfigured USB Device" |
| 150 | + > |
| 151 | + <SelectMenuBasic |
| 152 | + size="SM" |
| 153 | + label="" |
| 154 | + className="max-w-[192px]" |
| 155 | + value={usbConfigProduct} |
| 156 | + onChange={e => { |
| 157 | + if (e.target.value === "custom") { |
| 158 | + setUsbConfigProduct(e.target.value); |
| 159 | + } else { |
| 160 | + const usbConfig = usbConfigData[e.target.value]; |
| 161 | + handleUsbConfigChange(usbConfig); |
| 162 | + } |
| 163 | + }} |
| 164 | + options={[...usbConfigs, { value: "custom", label: "Custom" }]} |
| 165 | + /> |
| 166 | + </SettingsItem> |
| 167 | + {usbConfigProduct === "custom" && ( |
| 168 | + <USBConfigDialog |
| 169 | + onSetUsbConfig={usbConfig => handleUsbConfigChange(usbConfig)} |
| 170 | + onRestoreToDefault={() => |
| 171 | + handleUsbConfigChange(usbConfigData[usbConfigs[0].value]) |
| 172 | + } |
| 173 | + /> |
| 174 | + )} |
| 175 | + </> |
| 176 | + ); |
| 177 | +} |
0 commit comments