mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
refactor: refactor virtual devices and HID
This commit is contained in:
@@ -75,7 +75,7 @@ start_usb_dev(){
|
||||
fi
|
||||
echo 2 > functions/hid.GS1/protocol
|
||||
echo 4 > functions/hid.GS1/report_length
|
||||
echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc
|
||||
echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc
|
||||
ln -s functions/hid.GS1 configs/c.1
|
||||
|
||||
# touchpad
|
||||
@@ -94,7 +94,6 @@ start_usb_dev(){
|
||||
ln -s functions/hid.GS2 configs/c.1
|
||||
fi
|
||||
|
||||
# dedicated for media
|
||||
if [ -e /boot/usb.disk0 ]
|
||||
then
|
||||
mkdir functions/mass_storage.disk0
|
||||
@@ -109,23 +108,18 @@ start_usb_dev(){
|
||||
disk=$(cat /boot/usb.disk0)
|
||||
if [ -z "${disk}" ]
|
||||
then
|
||||
echo "" > functions/mass_storage.disk0/lun.0/file
|
||||
# if [ ! -e /mnt/usbdisk.img ]
|
||||
# then
|
||||
# fallocate -l 8G /mnt/usbdisk.img
|
||||
# mkfs.vfat /mnt/usbdisk.img
|
||||
# fi
|
||||
echo /dev/mmcblk0p3 > functions/mass_storage.disk0/lun.0/file
|
||||
else
|
||||
cat /boot/usb.disk0 > functions/mass_storage.disk0/lun.0/file
|
||||
fi
|
||||
fi
|
||||
|
||||
# mount data partition
|
||||
if [ -e /boot/usb.disk1 ]
|
||||
then
|
||||
mkdir functions/mass_storage.disk1
|
||||
ln -s functions/mass_storage.disk1 configs/c.1/
|
||||
echo /dev/mmcblk0p3 > functions/mass_storage.disk1/lun.0/file
|
||||
fi
|
||||
|
||||
if [ ! -e /boot/udc.disable ]; then
|
||||
ls /sys/class/udc/ | cat > UDC
|
||||
fi
|
||||
ls /sys/class/udc/ | cat > UDC
|
||||
echo device > /proc/cviusb/otg_role
|
||||
}
|
||||
|
||||
@@ -137,9 +131,7 @@ start_usb_host(){
|
||||
restart_usb_dev(){
|
||||
echo > /sys/kernel/config/usb_gadget/g0/UDC
|
||||
sleep 1
|
||||
if [ ! -e /boot/udc.disable ]; then
|
||||
ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC
|
||||
fi
|
||||
ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC
|
||||
echo "USB Restart OK!"
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ start_usb_dev(){
|
||||
fi
|
||||
echo 2 > functions/hid.GS1/protocol
|
||||
echo 4 > functions/hid.GS1/report_length
|
||||
echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc
|
||||
echo -ne \\x5\\x1\\x9\\x2\\xa1\\x1\\x9\\x1\\xa1\\x0\\x5\\x9\\x19\\x1\\x29\\x3\\x15\\x0\\x25\\x1\\x95\\x3\\x75\\x1\\x81\\x2\\x95\\x1\\x75\\x5\\x81\\x3\\x5\\x1\\x9\\x30\\x9\\x31\\x9\\x38\\x15\\x81\\x25\\x7f\\x75\\x8\\x95\\x3\\x81\\x6\\xc0\\xc0 > functions/hid.GS1/report_desc
|
||||
ln -s functions/hid.GS1 configs/c.1
|
||||
|
||||
# touchpad
|
||||
@@ -72,9 +72,7 @@ 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
|
||||
|
||||
if [ ! -e /boot/udc.disable ]; then
|
||||
ls /sys/class/udc/ | cat > UDC
|
||||
fi
|
||||
ls /sys/class/udc/ | cat > UDC
|
||||
echo device > /proc/cviusb/otg_role
|
||||
}
|
||||
|
||||
@@ -86,9 +84,7 @@ start_usb_host(){
|
||||
restart_usb_dev(){
|
||||
echo > /sys/kernel/config/usb_gadget/g0/UDC
|
||||
sleep 1
|
||||
if [ ! -e /boot/udc.disable ]; then
|
||||
ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC
|
||||
fi
|
||||
ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC
|
||||
echo "USB Restart OK!"
|
||||
}
|
||||
|
||||
|
||||
@@ -101,10 +101,6 @@ type GetGetHdmiStateRsp struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type GetUsbStateRsp struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type GetSSHStateRsp struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
@@ -35,10 +35,6 @@ 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
|
||||
|
||||
@@ -18,11 +18,11 @@ import (
|
||||
|
||||
const (
|
||||
imageDirectory = "/data"
|
||||
imageNone = "/dev/mmcblk0p3"
|
||||
cdromFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/cdrom"
|
||||
mountDevice = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/file"
|
||||
inquiryString = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/inquiry_string"
|
||||
roFlag = "/sys/kernel/config/usb_gadget/g0/functions/mass_storage.disk0/lun.0/ro"
|
||||
usbNoRstMarker = "/boot/usb.norst"
|
||||
)
|
||||
|
||||
func (s *Service) GetImages(c *gin.Context) {
|
||||
@@ -108,20 +108,16 @@ func (s *Service) MountImage(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// mount if file provided
|
||||
// mount
|
||||
image := req.File
|
||||
if image != "" {
|
||||
if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil {
|
||||
log.Errorf("mount file %s failed: %s", image, err)
|
||||
rsp.ErrRsp(c, -2, "mount image failed")
|
||||
return
|
||||
}
|
||||
if image == "" {
|
||||
image = imageNone
|
||||
}
|
||||
|
||||
// check to see if usb gadget reset is disabled
|
||||
resetUsb := true
|
||||
if _, err := os.Stat(usbNoRstMarker); err == nil {
|
||||
resetUsb = false
|
||||
if err := os.WriteFile(mountDevice, []byte(image), 0o666); err != nil {
|
||||
log.Errorf("mount file %s failed: %s", image, err)
|
||||
rsp.ErrRsp(c, -2, "mount image failed")
|
||||
return
|
||||
}
|
||||
|
||||
h := hid.GetHid()
|
||||
@@ -133,20 +129,18 @@ func (s *Service) MountImage(c *gin.Context) {
|
||||
}()
|
||||
|
||||
// reset usb
|
||||
if resetUsb {
|
||||
commands := []string{
|
||||
"echo > /sys/kernel/config/usb_gadget/g0/UDC",
|
||||
"ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC",
|
||||
}
|
||||
commands := []string{
|
||||
"echo > /sys/kernel/config/usb_gadget/g0/UDC",
|
||||
"ls /sys/class/udc/ | cat > /sys/kernel/config/usb_gadget/g0/UDC",
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "execute command failed")
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
for _, command := range commands {
|
||||
err := exec.Command("sh", "-c", command).Run()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -2, "execute command failed")
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
@@ -163,6 +157,9 @@ func (s *Service) GetMountedImage(c *gin.Context) {
|
||||
}
|
||||
|
||||
image := strings.ReplaceAll(string(content), "\n", "")
|
||||
if image == imageNone {
|
||||
image = ""
|
||||
}
|
||||
|
||||
data := &proto.GetMountedImageRsp{
|
||||
File: image,
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
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")
|
||||
}
|
||||
@@ -14,8 +14,7 @@ import (
|
||||
|
||||
const (
|
||||
virtualNetwork = "/boot/usb.rndis0"
|
||||
virtualMedia = "/boot/usb.disk0"
|
||||
virtualDisk = "/boot/usb.disk1"
|
||||
virtualDisk = "/boot/usb.disk0"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -32,29 +31,16 @@ var (
|
||||
"/etc/init.d/S03usbdev start",
|
||||
}
|
||||
|
||||
mountMediaCommands = []string{
|
||||
"touch /boot/usb.disk0",
|
||||
"/etc/init.d/S03usbdev stop",
|
||||
"/etc/init.d/S03usbdev start",
|
||||
}
|
||||
|
||||
unmountMediaCommands = []string{
|
||||
"/etc/init.d/S03usbdev stop",
|
||||
"rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0",
|
||||
"rm /boot/usb.disk0",
|
||||
"/etc/init.d/S03usbdev start",
|
||||
}
|
||||
|
||||
mountDiskCommands = []string{
|
||||
"touch /boot/usb.disk1",
|
||||
"touch /boot/usb.disk0",
|
||||
"/etc/init.d/S03usbdev stop",
|
||||
"/etc/init.d/S03usbdev start",
|
||||
}
|
||||
|
||||
unmountDiskCommands = []string{
|
||||
"/etc/init.d/S03usbdev stop",
|
||||
"rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk1",
|
||||
"rm /boot/usb.disk1",
|
||||
"rm -rf /sys/kernel/config/usb_gadget/g0/configs/c.1/mass_storage.disk0",
|
||||
"rm /boot/usb.disk0",
|
||||
"/etc/init.d/S03usbdev start",
|
||||
}
|
||||
)
|
||||
@@ -63,12 +49,10 @@ func (s *Service) GetVirtualDevice(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
network, _ := isDeviceExist(virtualNetwork)
|
||||
media, _ := isDeviceExist(virtualMedia)
|
||||
disk, _ := isDeviceExist(virtualDisk)
|
||||
|
||||
rsp.OkRspWithData(c, &proto.GetVirtualDeviceRsp{
|
||||
Network: network,
|
||||
Media: media,
|
||||
Disk: disk,
|
||||
})
|
||||
log.Debugf("get virtual device success")
|
||||
@@ -96,15 +80,6 @@ func (s *Service) UpdateVirtualDevice(c *gin.Context) {
|
||||
} else {
|
||||
commands = unmountNetworkCommands
|
||||
}
|
||||
case "media":
|
||||
device = virtualMedia
|
||||
|
||||
exist, _ := isDeviceExist(device)
|
||||
if !exist {
|
||||
commands = mountMediaCommands
|
||||
} else {
|
||||
commands = unmountMediaCommands
|
||||
}
|
||||
case "disk":
|
||||
device = virtualDisk
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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');
|
||||
}
|
||||
@@ -230,6 +230,7 @@ const en = {
|
||||
title: 'Appearance',
|
||||
display: 'Display',
|
||||
language: 'Language',
|
||||
languageDesc: 'Select the language for the interface',
|
||||
menuBar: 'Menu Bar',
|
||||
menuBarDesc: 'Display icons in the menu bar',
|
||||
webTitle: 'Web Title',
|
||||
@@ -306,16 +307,13 @@ const en = {
|
||||
settings: 'Settings'
|
||||
},
|
||||
hidOnly: 'HID-Only Mode',
|
||||
hidOnlyDesc: 'Stop emulating virtual devices, retaining only basic HID control',
|
||||
disk: 'Virtual Disk',
|
||||
diskDesc: 'Mount SD card on the remote host',
|
||||
media: 'Virtual Image',
|
||||
mediaDesc: 'Attach virtual image device to the remote host',
|
||||
network: 'Virtual Network',
|
||||
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'
|
||||
},
|
||||
|
||||
@@ -200,6 +200,7 @@ const zh = {
|
||||
title: '外观',
|
||||
display: '显示',
|
||||
language: '语言',
|
||||
languageDesc: '选择界面语言',
|
||||
menuBar: '菜单栏',
|
||||
menuBarDesc: '是否在菜单栏中显示图标',
|
||||
webTitle: '网站标题',
|
||||
@@ -275,6 +276,7 @@ const zh = {
|
||||
settings: '设置'
|
||||
},
|
||||
hidOnly: 'HID-Only 模式',
|
||||
hidOnlyDesc: '该模式下不再挂载虚拟设备,仅保留基础的 HID 控制功能。',
|
||||
disk: '虚拟U盘',
|
||||
diskDesc: '在远程主机中挂载虚拟U盘',
|
||||
network: '虚拟网卡',
|
||||
|
||||
@@ -8,6 +8,3 @@ export const mouseModeAtom = atom('absolute');
|
||||
|
||||
// mouse scroll interval (unit: ms)
|
||||
export const scrollIntervalAtom = atom(0);
|
||||
|
||||
// hid mode: normal or hid-only
|
||||
export const hidModeAtom = atom<'normal' | 'hid-only'>('normal');
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Modal, Typography } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { useAtom } from 'jotai';
|
||||
import { PenIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/hid.ts';
|
||||
import { hidModeAtom } from '@/jotai/mouse.ts';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
export const HidMode = () => {
|
||||
const { t } = useTranslation();
|
||||
const [hidMode, setHidMode] = useAtom(hidModeAtom);
|
||||
|
||||
const [hidMode, setHidMode] = useState<'normal' | 'hid-only'>('normal');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [errMsg, setErrMsg] = useState('');
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const HidMode = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span>{t('settings.device.hidOnly')}</span>
|
||||
</div>
|
||||
|
||||
<Switch checked={true} disabled={true} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,59 +1,44 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Divider } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/hid.ts';
|
||||
import { hidModeAtom } from '@/jotai/mouse.ts';
|
||||
|
||||
import { Advanced } from './advanced';
|
||||
import { Hdmi } from './hdmi.tsx';
|
||||
import { HidMode } from './hid-mode.tsx';
|
||||
import { Mdns } from './mdns.tsx';
|
||||
import { MouseJiggler } from './mouse-jiggler.tsx';
|
||||
import { Oled } from './oled.tsx';
|
||||
import { Reboot } from './reboot.tsx';
|
||||
import { Ssh } from './ssh.tsx';
|
||||
import { Tls } from './tls.tsx';
|
||||
import { Usb } from './usb/index.tsx';
|
||||
import { VirtualDevices } from './virtual-devices.tsx';
|
||||
import { Wifi } from './wifi.tsx';
|
||||
|
||||
export const Device = () => {
|
||||
const { t } = useTranslation();
|
||||
const [hidMode, setHidMode] = useAtom(hidModeAtom);
|
||||
|
||||
useEffect(() => {
|
||||
api.getHidMode().then((rsp) => {
|
||||
if (rsp.code === 0) {
|
||||
setHidMode(rsp.data.mode);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-base font-bold">{t('settings.device.title')}</div>
|
||||
<Divider />
|
||||
<div className="text-base">{t('settings.device.title')}</div>
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<div className="flex flex-col space-y-6">
|
||||
<div className="flex flex-col space-y-8">
|
||||
<Tls />
|
||||
<Ssh />
|
||||
<Mdns />
|
||||
<Hdmi />
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
{hidMode === 'normal' ? <Usb/> : <HidMode />}
|
||||
</div>
|
||||
<Divider />
|
||||
<VirtualDevices />
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<div className="flex flex-col space-y-6">
|
||||
<Oled />
|
||||
<Wifi />
|
||||
<MouseJiggler />
|
||||
</div>
|
||||
<Divider />
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<Advanced />
|
||||
<Divider />
|
||||
<Advanced />
|
||||
</div>
|
||||
|
||||
<Divider className="opacity-50" />
|
||||
|
||||
<Reboot />
|
||||
</>
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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 (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span>{t('settings.device.usb')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.usbDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch checked={isUsbOn} loading={isLoading} onChange={() => update()} />
|
||||
</div>
|
||||
|
||||
{isUsbOn ? <VirtualDevices /> : ""}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as api from '@/api/virtual-device.ts';
|
||||
|
||||
export const VirtualDevices = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isNetworkOn, setIsNetworkOn] = useState(false);
|
||||
const [isMediaOn, setIsMediaOn] = useState(false);
|
||||
const [isDiskOn, setIsDiskOn] = useState(false);
|
||||
const [loading, setLoading] = useState(''); // '' | 'network' | 'media' | 'disk'
|
||||
|
||||
useEffect(() => {
|
||||
api.getVirtualDevice().then((rsp) => {
|
||||
if (rsp.code !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsNetworkOn(rsp.data.network);
|
||||
setIsMediaOn(rsp.data.media);
|
||||
setIsDiskOn(rsp.data.disk);
|
||||
});
|
||||
}, []);
|
||||
|
||||
function update(device: 'network' | 'media' | 'disk') {
|
||||
if (loading) return;
|
||||
setLoading(device);
|
||||
|
||||
api
|
||||
.updateVirtualDevice(device)
|
||||
.then((rsp) => {
|
||||
if (rsp.code !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (device) {
|
||||
case 'network':
|
||||
setIsNetworkOn(rsp.data.on);
|
||||
break;
|
||||
case 'media':
|
||||
setIsMediaOn(rsp.data.on);
|
||||
break;
|
||||
case 'disk':
|
||||
setIsDiskOn(rsp.data.on);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading('');
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span>{t('settings.device.media')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.mediaDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch checked={isMediaOn} loading={loading === 'media'} onChange={() => update('media')} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span>{t('settings.device.disk')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.diskDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch checked={isDiskOn} loading={loading === 'disk'} onChange={() => update('disk')} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span>{t('settings.device.network')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.networkDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isNetworkOn}
|
||||
loading={loading === 'network'}
|
||||
onChange={() => update('network')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
112
web/src/pages/desktop/menu/settings/device/virtual-devices.tsx
Normal file
112
web/src/pages/desktop/menu/settings/device/virtual-devices.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { getHidMode } from '@/api/hid.ts';
|
||||
import * as api from '@/api/virtual-device.ts';
|
||||
|
||||
export const VirtualDevices = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isHidOnlyMode, setIsHidOnlyMode] = useState(false);
|
||||
const [isDiskEnabled, setIsDiskEnabled] = useState(false);
|
||||
const [isNetworkEnabled, setIsNetworkEnabled] = useState(false);
|
||||
const [loading, setLoading] = useState<'' | 'disk' | 'network'>('');
|
||||
|
||||
useEffect(() => {
|
||||
getHidOnlyMode();
|
||||
getVirtualDevice();
|
||||
}, []);
|
||||
|
||||
async function getHidOnlyMode() {
|
||||
try {
|
||||
const rsp = await getHidMode();
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
setIsHidOnlyMode(rsp.data.mode === 'hid-only');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function getVirtualDevice() {
|
||||
try {
|
||||
const rsp = await api.getVirtualDevice();
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDiskEnabled(rsp.data.disk);
|
||||
setIsNetworkEnabled(rsp.data.network);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function update(device: 'disk' | 'network') {
|
||||
if (loading) return;
|
||||
setLoading(device);
|
||||
|
||||
try {
|
||||
const rsp = await api.updateVirtualDevice(device);
|
||||
if (rsp.code !== 0) {
|
||||
console.log(rsp.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
await getVirtualDevice();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
setLoading('');
|
||||
}
|
||||
}
|
||||
|
||||
if (isHidOnlyMode) {
|
||||
return (
|
||||
<div className="flex items-center justify-between space-x-10">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.hidOnly')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.hidOnlyDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch checked={true} disabled={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Virtual Disk */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.disk')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.diskDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isDiskEnabled}
|
||||
loading={loading === 'disk'}
|
||||
onChange={() => update('disk')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Virtual Network */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<span>{t('settings.device.network')}</span>
|
||||
<span className="text-xs text-neutral-500">{t('settings.device.networkDesc')}</span>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isNetworkEnabled}
|
||||
loading={loading === 'network'}
|
||||
onChange={() => update('network')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user