From f9d9677bc09b1b06c87d3b216095205aac692e80 Mon Sep 17 00:00:00 2001 From: LingkongSky Date: Wed, 2 Apr 2025 23:02:29 +0800 Subject: [PATCH] add the function of MouseJiggler --- kvmapp/system/init.d/S50mouse-jiggler | 52 +++++++++++++ server/proto/vm.go | 4 + server/router/vm.go | 4 + server/service/vm/mouseJiggler.go | 76 +++++++++++++++++++ web/src/api/vm.ts | 15 ++++ web/src/i18n/locales/en.ts | 5 ++ web/src/i18n/locales/zh.ts | 7 ++ .../desktop/menu/settings/device/index.tsx | 2 + .../menu/settings/device/mouse-jiggler.tsx | 65 ++++++++++++++++ 9 files changed, 230 insertions(+) create mode 100644 kvmapp/system/init.d/S50mouse-jiggler create mode 100644 server/service/vm/mouseJiggler.go create mode 100644 web/src/pages/desktop/menu/settings/device/mouse-jiggler.tsx diff --git a/kvmapp/system/init.d/S50mouse-jiggler b/kvmapp/system/init.d/S50mouse-jiggler new file mode 100644 index 0000000..b905ce0 --- /dev/null +++ b/kvmapp/system/init.d/S50mouse-jiggler @@ -0,0 +1,52 @@ +#!/bin/sh +# +# mouse-jiggler Starts mouse-jiggler. +# + +PIDFILE="/var/run/mouse-jiggler.pid" + +start() { + if [ ! -f "$PIDFILE" ];then + echo "Starting Mouse Jiggler" + ( + status=true + while true;do + if [ "$status" = true ];then + echo -ne \\x00\\x1\\x00\\x00 > /dev/hidg1 + status=false + else + echo -ne \\x1\\x00\\x00\\x00 > /dev/hidg1 + status=true + fi + sleep 60 + done + ) >/dev/null 2>&1 & + echo $! > "$PIDFILE" + else + echo "Mouse Jiggler already started" + fi +} + +stop() { + echo "Stopping Mouse Jiggler" + [ -f "$PIDFILE" ] && kill $(cat "$PIDFILE") && rm -f "$PIDFILE" +} + + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + $0 stop + $0 start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 +esac + +exit 0 \ No newline at end of file diff --git a/server/proto/vm.go b/server/proto/vm.go index 9660772..dcd302d 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -88,6 +88,10 @@ type GetSwapStateRsp struct { Enabled bool `json:"enabled"` } +type GetMouseJigglerStateRsp struct { + Enabled bool `json:"enabled"` +} + type GetMdnsStateRsp struct { Enabled bool `json:"enabled"` } diff --git a/server/router/vm.go b/server/router/vm.go index 2a2954b..46d1cc5 100644 --- a/server/router/vm.go +++ b/server/router/vm.go @@ -44,6 +44,10 @@ func vmRouter(r *gin.Engine) { api.POST("/vm/swap/enable", service.EnableSwap) // enable Swap api.POST("/vm/swap/disable", service.DisableSwap) // disable Swap + api.GET("/vm/mouseJiggler", service.GetMouseJigglerState) // get MouseJiggler state + api.POST("/vm/mouseJiggler/enable", service.EnableMouseJiggler) // enable MouseJiggler + api.POST("/vm/mouseJiggler/disable", service.DisableMouseJiggler) // disable MouseJiggler + api.GET("/vm/mdns", service.GetMdnsState) // get mDNS state api.POST("/vm/mdns/enable", service.EnableMdns) // enable mDNS api.POST("/vm/mdns/disable", service.DisableMdns) // disable mDNS diff --git a/server/service/vm/mouseJiggler.go b/server/service/vm/mouseJiggler.go new file mode 100644 index 0000000..e39c29a --- /dev/null +++ b/server/service/vm/mouseJiggler.go @@ -0,0 +1,76 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + MouseJigglerStartFlag = "/var/run/mouse-jiggler.pid" + MouseJigglerScript = "/etc/init.d/S50mouse-jiggler" + MouseJigglerBackupScript = "/kvmapp/system/init.d/S50mouse-jiggler" +) + +func (s *Service) GetMouseJigglerState(c *gin.Context) { + var rsp proto.Response + + enabled := isMouseJigglerEnabled() + rsp.OkRspWithData(c, &proto.GetMouseJigglerStateRsp{ + Enabled: enabled, + }) +} + +func (s *Service) EnableMouseJiggler(c *gin.Context) { + var rsp proto.Response + + commands := []string{ + fmt.Sprintf("cp -f %s %s", MouseJigglerBackupScript, MouseJigglerScript), + fmt.Sprintf("chmod +x %s", MouseJigglerScript), + fmt.Sprintf("%s start", MouseJigglerScript), + } + + command := strings.Join(commands, " && ") + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to run MouseJiggler script: %s", err) + rsp.ErrRsp(c, -1, "operation failed") + return + } + + rsp.OkRsp(c) + log.Debugf("MouseJiggler enabled") +} + +func (s *Service) DisableMouseJiggler(c *gin.Context) { + var rsp proto.Response + + command := fmt.Sprintf("%s stop", MouseJigglerScript) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to run MouseJiggler script: %s", err) + rsp.ErrRsp(c, -1, "operation failed") + return + } + + _ = os.Remove(MouseJigglerScript) + + rsp.OkRsp(c) + log.Debugf("MouseJiggler disabled") +} + +func isMouseJigglerEnabled() bool { + _, err := os.Stat(MouseJigglerStartFlag) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return false + } + } + return true +} diff --git a/web/src/api/vm.ts b/web/src/api/vm.ts index 7a0cf43..943a7b7 100644 --- a/web/src/api/vm.ts +++ b/web/src/api/vm.ts @@ -92,6 +92,21 @@ export function disableSwap() { return http.post('/api/vm/swap/disable'); } +// get MouseJiggler state +export function getMouseJigglerState() { + return http.get('/api/vm/mouseJiggler'); +} + +// enable MouseJiggler +export function enableMouseJiggler() { + return http.post('/api/vm/mouseJiggler/enable'); +} + +// disable MouseJiggler +export function disableMouseJiggler() { + return http.post('/api/vm/mouseJiggler/disable'); +} + // get mDNS state export function getMdnsState() { return http.get('/api/vm/mdns'); diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index f86d0c5..a69f76f 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -224,6 +224,11 @@ const en = { description: 'Enable the swap partition', tip: 'Enable the default swap partition(128M)' }, + mouseJiggler: { + title: "Mouse Jiggler", + description: 'Enable the mouse jiggler', + tip: "Automatically jiggles the mouse when inactive for 60 seconds to prevent system sleep" + }, mdns: { description: 'Enable mDNS discovery service', tip: "Turning it off if it's not needed" diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index a930870..8192ebf 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -1,3 +1,5 @@ +import { t } from "i18next"; + const zh = { translation: { head: { @@ -208,6 +210,11 @@ const zh = { description: '启用交换分区', tip: '启用默认的交换分区(128M)' }, + mouseJiggler:{ + title: '鼠标抖动', + description: '启用鼠标抖动', + tip: '在鼠标离开60s未操作时自动进行抖动以防止系统睡眠' + }, mdns: { description: '启用 mDNS 发现服务', tip: '如果您未使用此功能,建议将其关闭' diff --git a/web/src/pages/desktop/menu/settings/device/index.tsx b/web/src/pages/desktop/menu/settings/device/index.tsx index a510bd5..fc41502 100644 --- a/web/src/pages/desktop/menu/settings/device/index.tsx +++ b/web/src/pages/desktop/menu/settings/device/index.tsx @@ -14,6 +14,7 @@ import { Ssh } from './ssh.tsx'; import { VirtualDevices } from './virtual-devices.tsx'; import { Wifi } from './wifi.tsx'; import { Swap } from './swap.tsx'; +import { MouseJiggler } from './mouse-jiggler.tsx'; export const Device = () => { const { t } = useTranslation(); @@ -39,6 +40,7 @@ export const Device = () => { + {hidMode === 'normal' ? : } diff --git a/web/src/pages/desktop/menu/settings/device/mouse-jiggler.tsx b/web/src/pages/desktop/menu/settings/device/mouse-jiggler.tsx new file mode 100644 index 0000000..49d3221 --- /dev/null +++ b/web/src/pages/desktop/menu/settings/device/mouse-jiggler.tsx @@ -0,0 +1,65 @@ +import { useEffect, useState } from 'react'; +import { Switch, Tooltip } from 'antd'; +import { CircleAlertIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +import * as api from '@/api/vm.ts'; + +export const MouseJiggler = () => { + const { t } = useTranslation(); + + const [isEnabled, setIsEnabled] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + setIsLoading(true); + + api + .getMouseJigglerState() + .then((rsp) => { + if (rsp.data?.enabled) { + setIsEnabled(true); + } + }) + .finally(() => { + setIsLoading(false); + }); + }, []); + + async function update() { + if (isLoading) return; + setIsLoading(true); + + const rsp = isEnabled ? await api.disableMouseJiggler() : await api.enableMouseJiggler(); + setIsLoading(false); + + if (rsp.code !== 0) { + console.log(rsp.msg); + return; + } + + setIsEnabled(!isEnabled); + } + + return ( +
+
+
+ {t('settings.device.mouseJiggler.title')} + + + + +
+ {t('settings.device.mouseJiggler.description')} +
+ + +
+ ); +};