From b76902c0f3ec8eb0213e1b6081a16f2130c8347b Mon Sep 17 00:00:00 2001
From: kevinzhangzj <710895909@qq.com>
Date: Sat, 31 May 2025 15:00:10 +0800
Subject: [PATCH 1/2] feat: add desktop version mouse jiggler function feat:
update mouse jiggler translation in English and Chinese
---
.../src/components/menu/mouse/index.tsx | 8 +++
.../src/components/menu/mouse/jiggler.tsx | 56 +++++++++++++++++++
.../src/components/mouse/absolute.tsx | 3 +
.../src/components/mouse/relative.tsx | 3 +
desktop/src/renderer/src/i18n/locales/en.ts | 7 ++-
desktop/src/renderer/src/i18n/locales/zh.ts | 7 ++-
desktop/src/renderer/src/jotai/mouse.ts | 5 +-
.../renderer/src/libs/mouse-jiggler/index.ts | 49 ++++++++++++++++
.../src/renderer/src/libs/storage/index.ts | 10 ++++
9 files changed, 145 insertions(+), 3 deletions(-)
create mode 100644 desktop/src/renderer/src/components/menu/mouse/jiggler.tsx
create mode 100644 desktop/src/renderer/src/libs/mouse-jiggler/index.ts
diff --git a/desktop/src/renderer/src/components/menu/mouse/index.tsx b/desktop/src/renderer/src/components/menu/mouse/index.tsx
index 46dc0ce..12e7329 100644
--- a/desktop/src/renderer/src/components/menu/mouse/index.tsx
+++ b/desktop/src/renderer/src/components/menu/mouse/index.tsx
@@ -4,14 +4,17 @@ import { useAtom, useSetAtom } from 'jotai'
import { MouseIcon } from 'lucide-react'
import {
+ mouseJigglerModeAtom,
mouseModeAtom,
mouseStyleAtom,
scrollDirectionAtom,
scrollIntervalAtom
} from '@renderer/jotai/mouse'
+import { mouseJiggler } from '@renderer/libs/mouse-jiggler'
import * as storage from '@renderer/libs/storage'
import { Direction } from './direction'
+import { Jiggler } from './jiggler'
import { Mode } from './mode'
import { Speed } from './speed'
import { Style } from './style'
@@ -21,6 +24,7 @@ export const Mouse = (): ReactElement => {
const setMouseMode = useSetAtom(mouseModeAtom)
const setScrollDirection = useSetAtom(scrollDirectionAtom)
const setScrollInterval = useSetAtom(scrollIntervalAtom)
+ const setMouseJigglerMode = useSetAtom(mouseJigglerModeAtom)
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
@@ -44,6 +48,9 @@ export const Mouse = (): ReactElement => {
if (interval) {
setScrollInterval(interval)
}
+ const jiggler = storage.getMouseJigglerMode()
+ mouseJiggler.setMode(jiggler)
+ setMouseJigglerMode(jiggler)
}, [])
const content = (
@@ -52,6 +59,7 @@ export const Mouse = (): ReactElement => {
+
)
diff --git a/desktop/src/renderer/src/components/menu/mouse/jiggler.tsx b/desktop/src/renderer/src/components/menu/mouse/jiggler.tsx
new file mode 100644
index 0000000..5d9bcbf
--- /dev/null
+++ b/desktop/src/renderer/src/components/menu/mouse/jiggler.tsx
@@ -0,0 +1,56 @@
+import { ReactElement, useEffect } from 'react'
+import { Popover } from 'antd'
+import clsx from 'clsx'
+import { useAtom } from 'jotai'
+import { MousePointerIcon } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
+
+import { mouseJigglerModeAtom } from '@renderer/jotai/mouse'
+import { mouseJiggler } from '@renderer/libs/mouse-jiggler'
+import * as storage from '@renderer/libs/storage'
+
+export const Jiggler = (): ReactElement => {
+ const { t } = useTranslation()
+ const [jigglerMode, setJigglerMode] = useAtom(mouseJigglerModeAtom)
+
+ const mouseJigglerModes: { name: string; value: 'enable' | 'disable' }[] = [
+ { name: t('mouse.jiggler.enable'), value: 'enable' },
+ { name: t('mouse.jiggler.disable'), value: 'disable' }
+ ]
+
+ function update(mode: 'enable' | 'disable'): void {
+ storage.setMouseJigglerMode(mode)
+ setJigglerMode(mode)
+ }
+
+ useEffect(() => {
+ mouseJiggler.setMode(jigglerMode)
+ }, [jigglerMode])
+
+ const content = (
+ <>
+ {mouseJigglerModes.map((mode) => (
+
update(mode.value)}
+ >
+ {mode.name}
+
+ ))}
+ >
+ )
+ return (
+
+
+
+
+
+
{t('mouse.jiggler.title')}
+
+
+ )
+}
diff --git a/desktop/src/renderer/src/components/mouse/absolute.tsx b/desktop/src/renderer/src/components/mouse/absolute.tsx
index 4a2c671..525035d 100644
--- a/desktop/src/renderer/src/components/mouse/absolute.tsx
+++ b/desktop/src/renderer/src/components/mouse/absolute.tsx
@@ -4,6 +4,7 @@ import { useAtomValue } from 'jotai'
import { IpcEvents } from '@common/ipc-events'
import { resolutionAtom } from '@renderer/jotai/device'
import { scrollDirectionAtom, scrollIntervalAtom } from '@renderer/jotai/mouse'
+import { mouseJiggler } from '@renderer/libs/mouse-jiggler'
import type { Mouse as MouseKey } from '@renderer/types'
export const Absolute = (): ReactElement => {
@@ -77,6 +78,8 @@ export const Absolute = (): ReactElement => {
async function handleMouseMove(event: MouseEvent): Promise {
disableEvent(event)
await send(event)
+
+ mouseJiggler.moveEventCallback()
}
// mouse scroll
diff --git a/desktop/src/renderer/src/components/mouse/relative.tsx b/desktop/src/renderer/src/components/mouse/relative.tsx
index 5bdd7b1..a270fec 100644
--- a/desktop/src/renderer/src/components/mouse/relative.tsx
+++ b/desktop/src/renderer/src/components/mouse/relative.tsx
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
import { IpcEvents } from '@common/ipc-events'
import { resolutionAtom } from '@renderer/jotai/device'
import { scrollDirectionAtom, scrollIntervalAtom } from '@renderer/jotai/mouse'
+import { mouseJiggler } from '@renderer/libs/mouse-jiggler'
import type { Mouse as MouseKey } from '@renderer/types'
export const Relative = (): ReactElement => {
@@ -110,6 +111,8 @@ export const Relative = (): ReactElement => {
if (x === 0 && y === 0) return
await send(Math.abs(x) < 10 ? x * 2 : x, Math.abs(y) < 10 ? y * 2 : y, 0)
+
+ mouseJiggler.moveEventCallback()
}
async function handleWheel(event: WheelEvent): Promise {
diff --git a/desktop/src/renderer/src/i18n/locales/en.ts b/desktop/src/renderer/src/i18n/locales/en.ts
index 707c660..83e1775 100644
--- a/desktop/src/renderer/src/i18n/locales/en.ts
+++ b/desktop/src/renderer/src/i18n/locales/en.ts
@@ -51,7 +51,12 @@ const en = {
speed: 'Wheel speed',
fast: 'Fast',
slow: 'Slow',
- requestPointer: 'Using relative mode. Please click desktop to get mouse pointer.'
+ requestPointer: 'Using relative mode. Please click desktop to get mouse pointer.',
+ jiggler: {
+ title: 'Mouse Jiggler',
+ enable: 'Enable',
+ disable: 'Disable'
+ }
},
settings: {
title: 'Settings',
diff --git a/desktop/src/renderer/src/i18n/locales/zh.ts b/desktop/src/renderer/src/i18n/locales/zh.ts
index f6ea3f1..01b5add 100644
--- a/desktop/src/renderer/src/i18n/locales/zh.ts
+++ b/desktop/src/renderer/src/i18n/locales/zh.ts
@@ -50,7 +50,12 @@ const zh = {
speed: '滚轮速度',
fast: '快',
slow: '慢',
- requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。'
+ requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。',
+ jiggler: {
+ title: '空闲晃动',
+ enable: '启用',
+ disable: '禁用'
+ }
},
settings: {
title: '设置',
diff --git a/desktop/src/renderer/src/jotai/mouse.ts b/desktop/src/renderer/src/jotai/mouse.ts
index 4a33252..01f3620 100644
--- a/desktop/src/renderer/src/jotai/mouse.ts
+++ b/desktop/src/renderer/src/jotai/mouse.ts
@@ -10,4 +10,7 @@ export const mouseModeAtom = atom('absolute')
export const scrollDirectionAtom = atom(1)
// mouse scroll interval (unit: ms)
-export const scrollIntervalAtom = atom(0);
+export const scrollIntervalAtom = atom(0)
+
+// mouse jiggler mode: enable or disable
+export const mouseJigglerModeAtom = atom<'enable' | 'disable'>('disable')
diff --git a/desktop/src/renderer/src/libs/mouse-jiggler/index.ts b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts
new file mode 100644
index 0000000..b041fc7
--- /dev/null
+++ b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts
@@ -0,0 +1,49 @@
+import { IpcEvents } from '@common/ipc-events'
+import type { Mouse as MouseKey } from '@renderer/types'
+
+const MOUSE_JIGGLER_INTERVAL = 15_000
+const EMPTY_KEY: MouseKey = { left: false, right: false, mid: false }
+
+class MouseJiggler {
+ private lastMoveTime: number
+ private timer: NodeJS.Timeout | null
+ private mode: 'enable' | 'disable'
+
+ constructor() {
+ this.lastMoveTime = Date.now()
+ this.timer = null
+ this.mode = 'disable'
+ }
+
+ // enable or disable mouse jiggler
+ setMode(mode: 'enable' | 'disable'): void {
+ this.mode = mode
+ if (mode === 'disable' && this.timer !== null) {
+ clearInterval(this.timer)
+ this.timer = null
+ } else if (mode === 'enable' && this.timer === null) {
+ this.timer = setInterval(() => {
+ this.timeoutCallback()
+ }, MOUSE_JIGGLER_INTERVAL / 5)
+ }
+ }
+
+ // addEventListener to canvas on 'mousemove' event
+ moveEventCallback(): void {
+ if (this.mode === 'enable') {
+ this.lastMoveTime = Date.now()
+ }
+ }
+
+ timeoutCallback(): void {
+ if (Date.now() - this.lastMoveTime > MOUSE_JIGGLER_INTERVAL) {
+ this.sendJiggle()
+ }
+ }
+
+ async sendJiggle(): Promise {
+ await window.electron.ipcRenderer.invoke(IpcEvents.SEND_MOUSE_RELATIVE, EMPTY_KEY, 10, 10, 0)
+ await window.electron.ipcRenderer.invoke(IpcEvents.SEND_MOUSE_RELATIVE, EMPTY_KEY, -10, -10, 0)
+ }
+}
+export const mouseJiggler = new MouseJiggler()
diff --git a/desktop/src/renderer/src/libs/storage/index.ts b/desktop/src/renderer/src/libs/storage/index.ts
index 0024a9d..4a4b701 100644
--- a/desktop/src/renderer/src/libs/storage/index.ts
+++ b/desktop/src/renderer/src/libs/storage/index.ts
@@ -13,6 +13,7 @@ const MOUSE_MODE_KEY = 'nanokvm-usb-mouse-mode'
const MOUSE_SCROLL_DIRECTION_KEY = 'nanokvm-usb-mouse-scroll-direction'
const SKIP_UPDATE_KEY = 'nano-kvm-check-update'
const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-usb-mouse-scroll-interval'
+const MOUSE_JIGGLER_MODE_KEY = 'nanokvm-usb-mouse-jiggler-mode'
export function getLanguage(): string | null {
return localStorage.getItem(LANGUAGE_KEY)
@@ -132,3 +133,12 @@ export function setSkipUpdate(skip: boolean): void {
const expiry = 3 * 24 * 60 * 60 * 1000
setWithExpiry(SKIP_UPDATE_KEY, String(skip), expiry)
}
+
+export function getMouseJigglerMode(): 'enable' | 'disable' {
+ const jiggler = localStorage.getItem(MOUSE_JIGGLER_MODE_KEY)
+ return jiggler && jiggler === 'enable' ? 'enable' : 'disable'
+}
+
+export function setMouseJigglerMode(jiggler: 'enable' | 'disable'): void {
+ localStorage.setItem(MOUSE_JIGGLER_MODE_KEY, jiggler)
+}
From dbe4251adac0bfeda57c1caeb6428495c49c00b0 Mon Sep 17 00:00:00 2001
From: kevinzhangzj <710895909@qq.com>
Date: Sat, 31 May 2025 15:00:10 +0800
Subject: [PATCH 2/2] feat: add desktop version mouse jiggler function feat:
update mouse jiggler translation in English and Chinese
---
desktop/src/renderer/src/libs/mouse-jiggler/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/desktop/src/renderer/src/libs/mouse-jiggler/index.ts b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts
index b041fc7..bfdd4b7 100644
--- a/desktop/src/renderer/src/libs/mouse-jiggler/index.ts
+++ b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts
@@ -37,6 +37,7 @@ class MouseJiggler {
timeoutCallback(): void {
if (Date.now() - this.lastMoveTime > MOUSE_JIGGLER_INTERVAL) {
+ this.lastMoveTime = Date.now() - 1_000
this.sendJiggle()
}
}