From 7361289b68d822d18ee459f9f65747920aac9fc5 Mon Sep 17 00:00:00 2001 From: Adrian Date: Thu, 23 Jan 2025 18:55:25 -0600 Subject: [PATCH] created feature branch --- config.go | 9 + display.go | 8 +- jsonrpc.go | 21 + ui/src/components/sidebar/settings.tsx | 1184 +++++++++++++----------- usb.go | 113 ++- 5 files changed, 758 insertions(+), 577 deletions(-) diff --git a/config.go b/config.go index 1636434..89c2715 100644 --- a/config.go +++ b/config.go @@ -11,6 +11,14 @@ type WakeOnLanDevice struct { MacAddress string `json:"macAddress"` } +type UsbConfig struct { + UsbVendorId string `json:"usb_vendor_id"` + UsbProductId string `json:"usb_product_id"` + UsbSerialNumber string `json:"usb_serial_number"` + UsbName string `json:"usb_name"` + UsbManufacturer string `json:"usb_manufacturer"` +} + type Config struct { CloudURL string `json:"cloud_url"` CloudToken string `json:"cloud_token"` @@ -22,6 +30,7 @@ type Config struct { LocalAuthToken string `json:"local_auth_token"` LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"` + UsbConfig UsbConfig `json:"usb_config"` } const configPath = "/userdata/kvm_config.json" diff --git a/display.go b/display.go index f312eb6..701e3c6 100644 --- a/display.go +++ b/display.go @@ -36,17 +36,17 @@ func switchToScreenIfDifferent(screenName string) { func updateDisplay() { updateLabelIfChanged("ui_Home_Content_Ip", networkState.IPv4) if usbState == "configured" { - updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected") + updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Usb_Status_Label", "state": "LV_STATE_DEFAULT"}) } else { - updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected") + updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Usb_Status_Label", "state": "LV_STATE_USER_2"}) } if lastVideoState.Ready { - updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected") + updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Hdmi_Status_Label", "state": "LV_STATE_DEFAULT"}) } else { - updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected") + updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected*") _, _ = CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": "ui_Home_Footer_Hdmi_Status_Label", "state": "LV_STATE_USER_2"}) } updateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions)) diff --git a/jsonrpc.go b/jsonrpc.go index 2ce5f18..5885d70 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -478,6 +478,26 @@ func rpcSetUsbEmulationState(enabled bool) error { } } +func rpcSetUsbConfig(usbConfig UsbConfig) error { + log.Printf("[jsonrpc.go:rpcSetUsbConfig] called") + + LoadConfig() + config.UsbConfig = usbConfig + + err2 := UpdateGadgetConfig() + if err2 != nil { + return fmt.Errorf("failed to write gadget config: %w", err2) + } + + err := SaveConfig() + if err != nil { + return fmt.Errorf("failed to save usb config: %w", err) + } + + log.Printf("[jsonrpc.go:rpcSetUsbConfig] usb config set to %s", usbConfig) + return nil +} + func rpcGetWakeOnLanDevices() ([]WakeOnLanDevice, error) { LoadConfig() if config.WakeOnLanDevices == nil { @@ -542,6 +562,7 @@ var rpcHandlers = map[string]RPCHandler{ "isUpdatePending": {Func: rpcIsUpdatePending}, "getUsbEmulationState": {Func: rpcGetUsbEmulationState}, "setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}}, + "setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}}, "checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}}, "getVirtualMediaState": {Func: rpcGetVirtualMediaState}, "getStorageSpace": {Func: rpcGetStorageSpace}, diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index ec606a6..8f5d962 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -10,6 +10,7 @@ import { Button, LinkButton } from "@components/Button"; import { TextAreaWithLabel } from "@components/TextArea"; import { SectionHeader } from "@components/SectionHeader"; import { GridCard } from "@components/Card"; +import { InputFieldWithLabel } from "@components/InputField"; import { CheckCircleIcon } from "@heroicons/react/20/solid"; import { cx } from "@/cva.config"; import React, { useCallback, useEffect, useRef, useState } from "react"; @@ -27,11 +28,11 @@ import { useRevalidator } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; export function SettingsItem({ - title, - description, - children, - className, -}: { + title, + description, + children, + className, + }: { title: string; description: string | React.ReactNode; children?: React.ReactNode; @@ -39,18 +40,18 @@ export function SettingsItem({ name?: string; }) { return ( - + ); } const defaultEdid = - "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; + "00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b"; const edids = [ { value: defaultEdid, @@ -58,17 +59,17 @@ const edids = [ }, { value: - "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", + "00FFFFFFFFFFFF00047265058A3F6101101E0104A53420783FC125A8554EA0260D5054BFEF80714F8140818081C081008B009500B300283C80A070B023403020360006442100001A000000FD00304C575716010A202020202020000000FC0042323436574C0A202020202020000000FF0054384E4545303033383532320A01F802031CF14F90020304050607011112131415161F2309070783010000011D8018711C1620582C250006442100009E011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E9600064421000018C344806E70B028401720A80406442100001E00000000000000000000000000000000000000000000000000000096", label: "Acer B246WL, 1920x1200", }, { value: - "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", + "00FFFFFFFFFFFF0006B3872401010101021F010380342078EA6DB5A7564EA0250D5054BF6F00714F8180814081C0A9409500B300D1C0283C80A070B023403020360006442100001A000000FD00314B1E5F19000A202020202020000000FC00504132343851560A2020202020000000FF004D314C4D51533035323135370A014D02032AF14B900504030201111213141F230907078301000065030C001000681A00000101314BE6E2006A023A801871382D40582C450006442100001ECD5F80B072B0374088D0360006442100001C011D007251D01E206E28550006442100001E8C0AD08A20E02D10103E960006442100001800000000000000000000000000DC", label: "ASUS PA248QV, 1920x1200", }, { value: - "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", + "00FFFFFFFFFFFF0010AC132045393639201E0103803C22782ACD25A3574B9F270D5054A54B00714F8180A9C0D1C00101010101010101023A801871382D40582C450056502100001E000000FF00335335475132330A2020202020000000FC0044454C4C204432373231480A20000000FD00384C1E5311000A202020202020018102031AB14F90050403020716010611121513141F65030C001000023A801871382D40582C450056502100001E011D8018711C1620582C250056502100009E011D007251D01E206E28550056502100001E8C0AD08A20E02D10103E960056502100001800000000000000000000000000000000000000000000000000000000004F", label: "DELL D2721H, 1920x1080", }, ]; @@ -102,6 +103,15 @@ export default function SettingsSidebar() { } | null>(null); const [usbEmulationEnabled, setUsbEmulationEnabled] = useState(false); + + const [usbConfig, setUsbConfig] = useState({ + usb_product_id: '', + usb_vendor_id: '', + usb_serial_number: '', + usb_manufacturer: '', + usb_name: '', + }) + const getUsbEmulationState = useCallback(() => { send("getUsbEmulationState", {}, resp => { if ("error" in resp) return; @@ -110,19 +120,19 @@ export default function SettingsSidebar() { }, [send]); const handleUsbEmulationToggle = useCallback( - (enabled: boolean) => { - send("setUsbEmulationState", { enabled: enabled }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to ${enabled ? "enable" : "disable"} USB emulation: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setUsbEmulationEnabled(enabled); - getUsbEmulationState(); - }); - }, - [getUsbEmulationState, send], + (enabled: boolean) => { + send("setUsbEmulationState", { enabled: enabled }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to ${enabled ? "enable" : "disable"} USB emulation: ${resp.error.data || "Unknown error"}`, + ); + return; + } + setUsbEmulationEnabled(enabled); + getUsbEmulationState(); + }); + }, + [getUsbEmulationState, send], ); const getCloudState = useCallback(() => { @@ -137,7 +147,7 @@ export default function SettingsSidebar() { send("deregisterDevice", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to de-register device: ${resp.error.data || "Unknown error"}`, + `Failed to de-register device: ${resp.error.data || "Unknown error"}`, ); return; } @@ -150,7 +160,7 @@ export default function SettingsSidebar() { send("setStreamQualityFactor", { factor: Number(factor) }, resp => { if ("error" in resp) { notifications.error( - `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, + `Failed to set stream quality: ${resp.error.data || "Unknown error"}`, ); return; } @@ -162,7 +172,7 @@ export default function SettingsSidebar() { send("setAutoUpdateState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, + `Failed to set auto-update: ${resp.error.data || "Unknown error"}`, ); return; } @@ -174,7 +184,7 @@ export default function SettingsSidebar() { send("setDevChannelState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, + `Failed to set dev channel state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -186,7 +196,7 @@ export default function SettingsSidebar() { send("setJigglerState", { enabled }, resp => { if ("error" in resp) { notifications.error( - `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, + `Failed to set jiggler state: ${resp.error.data || "Unknown error"}`, ); return; } @@ -206,33 +216,44 @@ export default function SettingsSidebar() { }); }; + const handleUsbConfigChange = useCallback((usbConfig: object) => { + send("setUsbConfig", { usbConfig }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to update USB Config: ${resp.error.data || "Unknown error"}`, + ); + return; + } + }); + }, [send, usbConfig]); + const handleSSHKeyChange = (newKey: string) => { setSSHKey(newKey); }; const handleDevModeChange = useCallback( - (developerMode: boolean) => { - send("setDevModeState", { enabled: developerMode }, resp => { - if ("error" in resp) { - notifications.error( - `Failed to set dev mode: ${resp.error.data || "Unknown error"}`, - ); - return; - } - setDeveloperMode(developerMode); - setTimeout(() => { - sidebarRef.current?.scrollTo({ top: 5000, behavior: "smooth" }); - }, 0); - }); - }, - [send, setDeveloperMode], + (developerMode: boolean) => { + send("setDevModeState", { enabled: developerMode }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to set dev mode: ${resp.error.data || "Unknown error"}`, + ); + return; + } + setDeveloperMode(developerMode); + setTimeout(() => { + sidebarRef.current?.scrollTo({ top: 5000, behavior: "smooth" }); + }, 0); + }); + }, + [send, setDeveloperMode], ); const handleUpdateSSHKey = useCallback(() => { send("setSSHKeyState", { sshKey }, resp => { if ("error" in resp) { notifications.error( - `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, + `Failed to update SSH key: ${resp.error.data || "Unknown error"}`, ); return; } @@ -240,6 +261,26 @@ export default function SettingsSidebar() { }); }, [send, sshKey]); + const handleUsbProductIdChange = (productId: string) => { + setUsbConfig({... usbConfig, usb_product_id: productId}) + }; + + const handleUsbVendorIdChange = (vendorId: string) => { + setUsbConfig({... usbConfig, usb_vendor_id: vendorId}) + }; + + const handleUsbSerialChange = (serialNumber: string) => { + setUsbConfig({... usbConfig, usb_serial_number: serialNumber}) + }; + + const handleUsbName = (name: string) => { + setUsbConfig({... usbConfig, usb_name: name}) + }; + + const handleUsbManufacturer = (manufacturer: string) => { + setUsbConfig({... usbConfig, usb_manufacturer: manufacturer}) + }; + const { setIsUpdateDialogOpen, setModalView, otaState } = useUpdateStore(); const handleCheckForUpdates = () => { if (otaState.updating) { @@ -288,7 +329,7 @@ export default function SettingsSidebar() { const receivedEdid = resp.result as string; const matchingEdid = edids.find( - x => x.value.toLowerCase() === receivedEdid.toLowerCase(), + x => x.value.toLowerCase() === receivedEdid.toLowerCase(), ); if (matchingEdid) { @@ -331,8 +372,8 @@ export default function SettingsSidebar() { const getDevice = useCallback(async () => { try { const status = await api - .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) - .then(res => res.json() as Promise); + .GET(`${import.meta.env.VITE_SIGNAL_API}/device`) + .then(res => res.json() as Promise); setLocalDevice(status); } catch (error) { notifications.error("Failed to get authentication status"); @@ -367,8 +408,8 @@ export default function SettingsSidebar() { localStorage.removeItem("theme"); // Check system preference const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; + ? "dark" + : "light"; root.classList.remove("light", "dark"); root.classList.add(systemTheme); } else { @@ -382,7 +423,7 @@ export default function SettingsSidebar() { send("resetConfig", {}, resp => { if ("error" in resp) { notifications.error( - `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, + `Failed to reset configuration: ${resp.error.data || "Unknown error"}`, ); return; } @@ -391,509 +432,558 @@ export default function SettingsSidebar() { }, [send]); return ( -
e.stopPropagation()} - onKeyUp={e => e.stopPropagation()} - > -
e.stopPropagation()} + onKeyUp={e => e.stopPropagation()} > -
-
- - App: {currentVersions.appVersion} -
- System: {currentVersions.systemVersion} - - ) : ( - "Loading current versions..." - ) - } - /> -
-
+
+
+ + +
+ + { + setHideCursor(e.target.checked); + }} + /> + + + { + handleJigglerChange(e.target.checked); + }} + /> + +
+ +
+ + +
+
- - -
- - { - setHideCursor(e.target.checked); - }} - /> - - - { - handleJigglerChange(e.target.checked); - }} - /> - +
+
- -
- - -
-
-
-
-
-
- -
- - handleStreamQualityChange(e.target.value)} - /> - - - { - if (e.target.value === "custom") { - setEdid("custom"); - setCustomEdidValue(""); - } else { - handleEDIDChange(e.target.value as string); - } - }} - options={[...edids, { value: "custom", label: "Custom" }]} - /> - - {customEdidValue !== null && ( - <> - - setCustomEdidValue(e.target.value)} - /> -
-
- - )} -
-
- {isOnDevice && ( - <> -
-
- - - -
- -
-
-

- Cloud Security -

-
-
    -
  • • End-to-end encryption using WebRTC (DTLS and SRTP)
  • -
  • • Zero Trust security model
  • -
  • • OIDC (OpenID Connect) authentication
  • -
  • • All streams encrypted in transit
  • -
-
- -
- All cloud components are open-source and available on{" "} - - GitHub - - . -
-
-
- -
- -
-
-
-
- - {!isAdopted ? ( -
- -
- ) : ( -
-
-

- Your device is adopted to JetKVM Cloud -

-
-
-
-
- )} -
- - )} -
- {isOnDevice ? ( - <> -
- - -
- - {localDevice?.authMode === "password" ? ( -
-
-
- - ) : null} -
- - -
- - { - handleAutoUpdateChange(e.target.checked); - }} - /> - - - { - handleDevChannelChange(e.target.checked); - }} - /> - -
-
-
- - - - { - setCurrentTheme(e.target.value); - handleThemeChange(e.target.value); - }} - /> - -
-
- - -
- - handleDevModeChange(e.target.checked)} - /> - - - {settings.developerMode && ( -
- handleSSHKeyChange(e.target.value)} - placeholder="Enter your SSH public key" - /> -

- The default SSH user is root. -

-
-
-
- )} - - { - settings.setDebugMode(e.target.checked); - }} - /> - - - {settings.debugMode && ( - <> - -
+ + )} +
+
+ {isOnDevice && ( + <> +
+
+ + + +
+ +
+
+

+ Cloud Security +

+
+
    +
  • • End-to-end encryption using WebRTC (DTLS and SRTP)
  • +
  • • Zero Trust security model
  • +
  • • OIDC (OpenID Connect) authentication
  • +
  • • All streams encrypted in transit
  • +
+
+ +
+ All cloud components are open-source and available on{" "} + + GitHub + + . +
+
+
+ +
+ +
+
+
+
+ + {!isAdopted ? ( +
+ +
+ ) : ( +
+
+

+ Your device is adopted to JetKVM Cloud +

+
+
+
+
+ )} +
+ + )} +
+ {isOnDevice ? ( + <> +
+ + +
+ + {localDevice?.authMode === "password" ? ( +
+
+
+ + ) : null} +
+ + +
+ + { + handleAutoUpdateChange(e.target.checked); + }} + /> + + + { + handleDevChannelChange(e.target.checked); + }} + /> + +
+
+
+ + + + { + setCurrentTheme(e.target.value); + handleThemeChange(e.target.value); + }} + /> + +
+
+ + +
+ + handleDevModeChange(e.target.checked)} + /> + + + {settings.developerMode && ( +
+ handleSSHKeyChange(e.target.value)} + placeholder="Enter your SSH public key" + /> +

+ The default SSH user is root. +

+
+
+
+ )} + {settings.developerMode && ( +
+ handleUsbVendorIdChange(e.target.value)} + placeholder="Enter USB Vendor Id" + /> + handleUsbProductIdChange(e.target.value)} + placeholder="Enter USB Product Id" + /> + handleUsbSerialChange(e.target.value)} + placeholder="Enter USB Serial Number" + /> + handleUsbName(e.target.value)} + placeholder="Enter USB Name" + /> + handleUsbManufacturer(e.target.value)} + placeholder="Enter USB Manufacturer" + /> +
+
+
+ )} + + { + settings.setDebugMode(e.target.checked); + }} + /> + + + {settings.debugMode && ( + <> + +
+ { + // Revalidate the current route to refresh the local device status and dependent UI components + revalidator.revalidate(); + setIsLocalAuthDialogOpen(x); + }} + />
- { - // Revalidate the current route to refresh the local device status and dependent UI components - revalidator.revalidate(); - setIsLocalAuthDialogOpen(x); - }} - /> -
); -} +} \ No newline at end of file diff --git a/usb.go b/usb.go index 075409a..18bf874 100644 --- a/usb.go +++ b/usb.go @@ -3,6 +3,7 @@ package kvm import ( "errors" "fmt" + gadget "github.com/openstadia/go-usb-gadget" "log" "os" "os/exec" @@ -11,8 +12,6 @@ import ( "strings" "sync" "time" - - gadget "github.com/openstadia/go-usb-gadget" ) const configFSPath = "/sys/kernel/config" @@ -20,19 +19,6 @@ const gadgetPath = "/sys/kernel/config/usb_gadget" const kvmGadgetPath = "/sys/kernel/config/usb_gadget/jetkvm" const configC1Path = "/sys/kernel/config/usb_gadget/jetkvm/configs/c.1" -func mountConfigFS() error { - _, err := os.Stat(gadgetPath) - if os.IsNotExist(err) { - err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() - if err != nil { - return fmt.Errorf("failed to mount configfs: %w", err) - } - } else { - return fmt.Errorf("unable to access usb gadget path: %w", err) - } - return nil -} - func init() { _ = os.MkdirAll(imagesFolder, 0755) udcs := gadget.GetUdcs() @@ -58,6 +44,60 @@ func init() { //TODO: read hid reports(capslock, numlock, etc) from keyboardHidFile } +func UpdateGadgetConfig() error { + LoadConfig() + gadgetAttrs := [][]string{ + {"idVendor", config.UsbConfig.UsbVendorId}, + {"idProduct", config.UsbConfig.UsbProductId}, + } + err := writeGadgetAttrs(kvmGadgetPath, gadgetAttrs) + if err != nil { + return err + } + + log.Printf("Successfully updated usb gadget attributes: %v", gadgetAttrs) + + strAttrs := [][]string{ + {"serialnumber", config.UsbConfig.UsbSerialNumber}, + {"manufacturer", config.UsbConfig.UsbManufacturer}, + {"product", config.UsbConfig.UsbName}, + } + gadgetStringsPath := filepath.Join(kvmGadgetPath, "strings", "0x409") + err = os.MkdirAll(gadgetStringsPath, 0755) + if err != nil { + return err + } + err = writeGadgetAttrs(gadgetStringsPath, strAttrs) + if err != nil { + return err + } + + log.Printf("Successfully updated string attributes: %s", strAttrs) + + err = rebindUsb() + if err != nil { + return err + } + + return nil +} + +func mountConfigFS() error { + logger.Infof("Checking for gadgetPath: %s ...", gadgetPath) + _, err := os.Stat(gadgetPath) + if os.IsNotExist(err) { + logger.Infof("running mount command...") + err = exec.Command("mount", "-t", "configfs", "none", configFSPath).Run() + if err != nil { + return fmt.Errorf("failed to mount configfs: %w", err) + } + } else { + return fmt.Errorf("unable to access usb gadget path: %w", err) + } + logger.Infof("Successfully mounted usb gadget at %s", gadgetPath) + return nil +} + func writeGadgetAttrs(basePath string, attrs [][]string) error { for _, item := range attrs { filePath := filepath.Join(basePath, item[0]) @@ -79,30 +119,35 @@ func writeGadgetConfig() error { return err } - err = writeGadgetAttrs(kvmGadgetPath, [][]string{ - {"bcdUSB", "0x0200"}, //USB 2.0 - {"idVendor", "0x1d6b"}, //The Linux Foundation - {"idProduct", "0104"}, //Multifunction Composite Gadget¬ + LoadConfig() + gadgetAttrs := [][]string{ + {"bcdUSB", "0x0200"}, //USB 2.0 + {"idVendor", config.UsbConfig.UsbVendorId}, + {"idProduct", config.UsbConfig.UsbProductId}, {"bcdDevice", "0100"}, - }) + } + err = writeGadgetAttrs(kvmGadgetPath, gadgetAttrs) if err != nil { return err } + logger.Infof("Successfully wrote gadget attributes: %s", gadgetAttrs) gadgetStringsPath := filepath.Join(kvmGadgetPath, "strings", "0x409") err = os.MkdirAll(gadgetStringsPath, 0755) if err != nil { return err } - err = writeGadgetAttrs(gadgetStringsPath, [][]string{ - {"serialnumber", GetDeviceID()}, - {"manufacturer", "JetKVM"}, - {"product", "JetKVM USB Emulation Device"}, - }) + strAttrs := [][]string{ + {"serialnumber", config.UsbConfig.UsbSerialNumber}, + {"manufacturer", config.UsbConfig.UsbManufacturer}, + {"product", config.UsbConfig.UsbName}, + } + err = writeGadgetAttrs(gadgetStringsPath, strAttrs) if err != nil { return err } + logger.Infof("Successfully wrote string attributes: %s", strAttrs) configC1StringsPath := path.Join(configC1Path, "strings", "0x409") err = os.MkdirAll(configC1StringsPath, 0755) @@ -216,11 +261,27 @@ func writeGadgetConfig() error { } func rebindUsb() error { + unbindErr := unbindUsb() + if unbindErr != nil { + return unbindErr + } + bindErr := bindUsb() + if bindErr != nil { + return bindErr + } + return nil +} + +func unbindUsb() error { err := os.WriteFile("/sys/bus/platform/drivers/dwc3/unbind", []byte(udc), 0644) if err != nil { return err } - err = os.WriteFile("/sys/bus/platform/drivers/dwc3/bind", []byte(udc), 0644) + return nil +} + +func bindUsb() error { + err := os.WriteFile("/sys/bus/platform/drivers/dwc3/bind", []byte(udc), 0644) if err != nil { return err }