diff --git a/kvmapp/system/init.d/S03usbdev b/kvmapp/system/init.d/S03usbdev index c650e6b..7195179 100755 --- a/kvmapp/system/init.d/S03usbdev +++ b/kvmapp/system/init.d/S03usbdev @@ -106,7 +106,9 @@ start_usb_dev(){ fi fi - ls /sys/class/udc/ | cat > UDC + if [ ! -e /boot/udc.disable ]; then + ls /sys/class/udc/ | cat > UDC + fi echo device > /proc/cviusb/otg_role } @@ -118,7 +120,9 @@ start_usb_host(){ restart_usb_dev(){ echo > /sys/kernel/config/usb_gadget/g0/UDC sleep 1 - ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC + if [ ! -e /boot/udc.disable ]; then + ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC + fi echo "USB Restart OK!" } diff --git a/kvmapp/system/init.d/S03usbhid b/kvmapp/system/init.d/S03usbhid index 6d5d79f..437b26f 100755 --- a/kvmapp/system/init.d/S03usbhid +++ b/kvmapp/system/init.d/S03usbhid @@ -60,7 +60,9 @@ start_usb_dev(){ echo -ne \\x05\\x01\\x09\\x02\\xa1\\x01\\x09\\x01\\xa1\\x00\\x05\\x09\\x19\\x01\\x29\\x03\\x15\\x00\\x25\\x01\\x95\\x03\\x75\\x01\\x81\\x02\\x95\\x01\\x75\\x05\\x81\\x01\\x05\\x01\\x09\\x30\\x09\\x31\\x15\\x00\\x26\\xff\\x7f\\x35\\x00\\x46\\xff\\x7f\\x75\\x10\\x95\\x02\\x81\\x02\\x05\\x01\\x09\\x38\\x15\\x81\\x25\\x7f\\x35\\x00\\x45\\x00\\x75\\x08\\x95\\x01\\x81\\x06\\xc0\\xc0 > functions/hid.GS2/report_desc ln -s functions/hid.GS2 configs/c.1 - ls /sys/class/udc/ | cat > UDC + if [ ! -e /boot/udc.disable ]; then + ls /sys/class/udc/ | cat > UDC + fi echo device > /proc/cviusb/otg_role } @@ -72,7 +74,9 @@ start_usb_host(){ restart_usb_dev(){ echo > /sys/kernel/config/usb_gadget/g0/UDC sleep 1 - ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC + if [ ! -e /boot/udc.disable ]; then + ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC + fi echo "USB Restart OK!" } diff --git a/server/proto/vm.go b/server/proto/vm.go index 21d3844..573fcad 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -91,6 +91,10 @@ type GetGetHdmiStateRsp struct { Enabled bool `json:"enabled"` } +type GetUsbStateRsp struct { + Enabled bool `json:"enabled"` +} + type GetSSHStateRsp struct { Enabled bool `json:"enabled"` } diff --git a/server/router/vm.go b/server/router/vm.go index f5a78a4..05dcda7 100644 --- a/server/router/vm.go +++ b/server/router/vm.go @@ -35,6 +35,10 @@ func vmRouter(r *gin.Engine) { api.GET("/vm/oled", service.GetOLED) // get OLED configuration api.POST("/vm/oled", service.SetOLED) // set OLED configuration + api.GET("/vm/usb", service.GetUsbState) // get USB gadget state + api.POST("/vm/usb/enable", service.EnableUsb) // enable USB + api.POST("/vm/usb/disable", service.DisableUsb) // disable USB + // Only supported by PCIe version api.GET("/vm/hdmi", service.GetHdmiState) // get HDMI state api.POST("/vm/hdmi/reset", service.ResetHdmi) // reset hdmi diff --git a/server/service/vm/usb.go b/server/service/vm/usb.go new file mode 100644 index 0000000..2c12603 --- /dev/null +++ b/server/service/vm/usb.go @@ -0,0 +1,86 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "NanoKVM-Server/service/hid" + "os" + "os/exec" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + udcDisableFile = "/boot/udc.disable" +) + +var ( + udcEnableCommands = []string{ + "rm /boot/udc.disable", + "/etc/init.d/S03usbdev restart", + } + + udcDisableCommands = []string{ + "touch /boot/udc.disable", + "/etc/init.d/S03usbdev restart", + } +) + +func runCommands(commands []string) error { + h := hid.GetHid() + h.Lock() + h.CloseNoLock() + defer func() { + h.OpenNoLock() + h.Unlock() + }() + + for _, command := range commands { + if err := exec.Command("sh", "-c", command).Run(); err != nil { + log.Errorf("failed to run command '%s': %v", command, err) + return err + } + } + return nil +} + +func (s *Service) EnableUsb(c *gin.Context) { + var rsp proto.Response + + err := runCommands(udcEnableCommands) + if err != nil { + rsp.ErrRsp(c, -1, "failed to enable USB") + return + } + + rsp.OkRsp(c) + log.Debug("enable usb") +} + +func (s *Service) DisableUsb(c *gin.Context) { + var rsp proto.Response + + err := runCommands(udcDisableCommands) + if err != nil { + rsp.ErrRsp(c, -1, "failed to disable USB") + return + } + + rsp.OkRsp(c) + log.Debug("disable usb") +} + +func (s *Service) GetUsbState(c *gin.Context) { + var rsp proto.Response + + usbEnabled := false + if _, err := os.Stat(udcDisableFile); err != nil { + usbEnabled = true + } + + rsp.OkRspWithData(c, &proto.GetUsbStateRsp{ + Enabled: usbEnabled, + }) + + log.Debug("get usb state") +} diff --git a/web/src/api/usb-device.ts b/web/src/api/usb-device.ts new file mode 100644 index 0000000..b06171a --- /dev/null +++ b/web/src/api/usb-device.ts @@ -0,0 +1,16 @@ +import { http } from '@/lib/http.ts'; + +// get virtual devices status +export function getUsbState() { + return http.get('/api/vm/usb'); +} + +// enable usb +export function enableUsb() { + return http.post('/api/vm/usb/enable'); +} + +// disable usb +export function disableUsb() { + return http.post('/api/vm/usb/disable'); +} \ No newline at end of file diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 534d4f0..4c875c6 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -271,6 +271,8 @@ const en = { networkDesc: 'Mount virtual network card on the remote host', reboot: 'Reboot', rebootDesc: 'Are you sure you want to reboot NanoKVM?', + usb: 'USB Interface', + usbDesc: 'Enable USB to host interface', okBtn: 'Yes', cancelBtn: 'No' }, diff --git a/web/src/pages/desktop/menu/settings/device/index.tsx b/web/src/pages/desktop/menu/settings/device/index.tsx index cb818d9..af18da3 100644 --- a/web/src/pages/desktop/menu/settings/device/index.tsx +++ b/web/src/pages/desktop/menu/settings/device/index.tsx @@ -15,7 +15,7 @@ import { Oled } from './oled.tsx'; import { Reboot } from './reboot.tsx'; import { Ssh } from './ssh.tsx'; import { Tls } from './tls.tsx'; -import { VirtualDevices } from './virtual-devices.tsx'; +import { Usb } from './usb/index.tsx'; import { Wifi } from './wifi.tsx'; export const Device = () => { @@ -42,7 +42,7 @@ export const Device = () => { - {hidMode === 'normal' ? : } + {hidMode === 'normal' ? : } diff --git a/web/src/pages/desktop/menu/settings/device/usb/index.tsx b/web/src/pages/desktop/menu/settings/device/usb/index.tsx new file mode 100644 index 0000000..988a699 --- /dev/null +++ b/web/src/pages/desktop/menu/settings/device/usb/index.tsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { Switch } from 'antd'; +import { useTranslation } from 'react-i18next'; + +import { VirtualDevices } from './virtual-devices.tsx'; +import * as api from '@/api/usb-device.ts'; + + +export const Usb = () => { + const { t } = useTranslation(); + const [isUsbOn, setIsUsbOn] = useState(false); + const [isLoading, setLoading] = useState(false); + + useEffect(() => { + api.getUsbState().then((rsp) => { + if (rsp.code !== 0) { + return; + } + + setIsUsbOn(rsp.data.enabled); + }); + }, []); + + function update() { + if (isLoading) return; + setLoading(true); + + const func = isUsbOn ? api.disableUsb() : api.enableUsb(); + + func.then((rsp) => { + if (rsp.code !== 0) { + return; + } + setIsUsbOn(!isUsbOn); + }) + .finally(() => { + setLoading(false); + }); + } + + return ( + <> +
+
+ {t('settings.device.usb')} + {t('settings.device.usbDesc')} +
+ + update()} /> +
+ + {isUsbOn ? : ""} + + ); +}; \ No newline at end of file diff --git a/web/src/pages/desktop/menu/settings/device/virtual-devices.tsx b/web/src/pages/desktop/menu/settings/device/usb/virtual-devices.tsx similarity index 100% rename from web/src/pages/desktop/menu/settings/device/virtual-devices.tsx rename to web/src/pages/desktop/menu/settings/device/usb/virtual-devices.tsx