From cd0b52cc2acdb200904cda8e7713a7b0b55fc210 Mon Sep 17 00:00:00 2001 From: kevinzhangzj <710895909@qq.com> Date: Fri, 30 May 2025 14:17:13 +0800 Subject: [PATCH] feat: add mouse jiggler function feat: add mouse jiggler to menu --- browser/src/components/menu/mouse/index.tsx | 9 +++ browser/src/components/menu/mouse/jiggler.tsx | 49 ++++++++++++++++ browser/src/components/mouse/absolute.tsx | 18 +++++- browser/src/components/mouse/index.tsx | 56 ++++++++++++++++++- browser/src/components/mouse/relative.tsx | 18 +++++- browser/src/i18n/locales/en.ts | 7 ++- browser/src/i18n/locales/zh.ts | 7 ++- browser/src/jotai/mouse.ts | 13 ++++- browser/src/libs/storage/index.ts | 10 ++++ 9 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 browser/src/components/menu/mouse/jiggler.tsx diff --git a/browser/src/components/menu/mouse/index.tsx b/browser/src/components/menu/mouse/index.tsx index 4c0bbf4..d5ba87a 100644 --- a/browser/src/components/menu/mouse/index.tsx +++ b/browser/src/components/menu/mouse/index.tsx @@ -4,6 +4,7 @@ import { useAtom, useSetAtom } from 'jotai'; import { MouseIcon } from 'lucide-react'; import { + mouseJigglerModeAtom, mouseModeAtom, mouseStyleAtom, scrollDirectionAtom, @@ -12,6 +13,7 @@ import { import * as storage from '@/libs/storage'; import { Direction } from './direction.tsx'; +import { Jiggler } from './jiggler.tsx'; import { Mode } from './mode.tsx'; import { Speed } from './speed.tsx'; import { Style } from './style.tsx'; @@ -21,6 +23,7 @@ export const Mouse = () => { const [mouseMode, setMouseMode] = useAtom(mouseModeAtom); const setScrollDirection = useSetAtom(scrollDirectionAtom); const setScrollInterval = useSetAtom(scrollIntervalAtom); + const [mouseJigglerMode, setMouseJigglerMode] = useAtom(mouseJigglerModeAtom); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -48,6 +51,11 @@ export const Mouse = () => { if (interval) { setScrollInterval(interval); } + + const jiggler = storage.getMouseJigglerMode(); + if (mouseJigglerMode !== jiggler) { + setMouseJigglerMode(jiggler); + } } const content = ( @@ -56,6 +64,7 @@ export const Mouse = () => { + ); diff --git a/browser/src/components/menu/mouse/jiggler.tsx b/browser/src/components/menu/mouse/jiggler.tsx new file mode 100644 index 0000000..b3c74f5 --- /dev/null +++ b/browser/src/components/menu/mouse/jiggler.tsx @@ -0,0 +1,49 @@ +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 '@/jotai/mouse.ts'; +import * as storage from '@/libs/storage'; + +export const Jiggler = () => { + const { t } = useTranslation(); + const [mouseJigglerMode, setMouseJigglerMode] = useAtom(mouseJigglerModeAtom); + const mouseJigglerModes = [ + { name: t('mouse.jiggler.enable'), value: 'enable' }, + { name: t('mouse.jiggler.disable'), value: 'disable' } + ]; + + function update(mode: string) { + setMouseJigglerMode(mode); + storage.setMouseJigglerMode(mode); + } + + const content = ( + <> + {mouseJigglerModes.map((mode) => ( +
update(mode.value)} + > + {mode.name} +
+ ))} + + ); + return ( + +
+
+ +
+ {t('mouse.jiggler.title')} +
+
+ ); +}; diff --git a/browser/src/components/mouse/absolute.tsx b/browser/src/components/mouse/absolute.tsx index d5a9bd0..7f4ab4b 100644 --- a/browser/src/components/mouse/absolute.tsx +++ b/browser/src/components/mouse/absolute.tsx @@ -1,8 +1,13 @@ import { useEffect, useRef } from 'react'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { resolutionAtom } from '@/jotai/device.ts'; -import { scrollDirectionAtom, scrollIntervalAtom } from '@/jotai/mouse.ts'; +import { + mouseJigglerModeAtom, + mouseLastMoveTimeAtom, + scrollDirectionAtom, + scrollIntervalAtom +} from '@/jotai/mouse.ts'; import { device } from '@/libs/device'; import { Key } from '@/libs/device/mouse.ts'; @@ -10,6 +15,8 @@ export const Absolute = () => { const resolution = useAtomValue(resolutionAtom); const scrollDirection = useAtomValue(scrollDirectionAtom); const scrollInterval = useAtomValue(scrollIntervalAtom); + const mouseJigglerMode = useAtomValue(mouseJigglerModeAtom); + const setMouseLastMoveTime = useSetAtom(mouseLastMoveTimeAtom); const keyRef = useRef(new Key()); const lastScrollTimeRef = useRef(0); @@ -74,6 +81,11 @@ export const Absolute = () => { async function handleMouseMove(event: any) { disableEvent(event); await send(event); + + // mouse jiggler record last move time + if (mouseJigglerMode === 'enable') { + setMouseLastMoveTime(Date.now()); + } } // mouse scroll @@ -109,7 +121,7 @@ export const Absolute = () => { canvas.removeEventListener('click', disableEvent); canvas.removeEventListener('contextmenu', disableEvent); }; - }, [resolution, scrollDirection, scrollInterval]); + }, [resolution, scrollDirection, scrollInterval, mouseJigglerMode, setMouseLastMoveTime]); // disable default events function disableEvent(event: any) { diff --git a/browser/src/components/mouse/index.tsx b/browser/src/components/mouse/index.tsx index ddbd4ae..32e558a 100644 --- a/browser/src/components/mouse/index.tsx +++ b/browser/src/components/mouse/index.tsx @@ -1,6 +1,15 @@ -import { useAtomValue } from 'jotai'; +import { useEffect, useRef } from 'react'; +import { useAtom, useAtomValue } from 'jotai'; -import { mouseModeAtom } from '@/jotai/mouse.ts'; +import { + mouseJigglerIntervalAtom, + mouseJigglerModeAtom, + mouseJigglerTimerAtom, + mouseLastMoveTimeAtom, + mouseModeAtom +} from '@/jotai/mouse.ts'; +import { device } from '@/libs/device/index.ts'; +import { Key } from '@/libs/device/mouse.ts'; import { Absolute } from './absolute.tsx'; import { Relative } from './relative.tsx'; @@ -8,5 +17,48 @@ import { Relative } from './relative.tsx'; export const Mouse = () => { const mouseMode = useAtomValue(mouseModeAtom); + // mouse jiggler + const mouseJigglerMode = useAtomValue(mouseJigglerModeAtom); + const [mouseJigglerTimer, setMouseJigglerTimer] = useAtom(mouseJigglerTimerAtom); + const mouseJigglerInterval = useAtomValue(mouseJigglerIntervalAtom); + const mouseLastMoveTime = useAtomValue(mouseLastMoveTimeAtom); + const mouseLastMoveTimeRef = useRef(mouseLastMoveTime); + const emptyKeyRef = useRef(new Key()); + useEffect(() => { + // sync mouseLastMoveTime through ref + mouseLastMoveTimeRef.current = mouseLastMoveTime; + }, [mouseLastMoveTime]); + useEffect(() => { + async function jigglerTimerCallback() { + if (Date.now() - mouseLastMoveTimeRef.current < mouseJigglerInterval) { + return; + } + + const rect = document.getElementById('video')!.getBoundingClientRect(); + + await device.sendMouseAbsoluteData( + emptyKeyRef.current, + rect.width, + rect.height, + rect.width / 2, + rect.height / 2, + 0 + ); + } + + // configure interval timer + if (mouseJigglerMode === 'enable') { + if (mouseJigglerTimer === null) { + const timer = setInterval(jigglerTimerCallback, mouseJigglerInterval); + setMouseJigglerTimer(timer); + } + } else { + if (mouseJigglerTimer) { + clearInterval(mouseJigglerTimer); + setMouseJigglerTimer(null); + } + } + }, [mouseJigglerMode]); + return <>{mouseMode === 'relative' ? : }; }; diff --git a/browser/src/components/mouse/relative.tsx b/browser/src/components/mouse/relative.tsx index 0caecba..22bf8d6 100644 --- a/browser/src/components/mouse/relative.tsx +++ b/browser/src/components/mouse/relative.tsx @@ -1,10 +1,15 @@ import { useEffect, useRef } from 'react'; import { message } from 'antd'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; import { resolutionAtom } from '@/jotai/device.ts'; -import { scrollDirectionAtom, scrollIntervalAtom } from '@/jotai/mouse.ts'; +import { + mouseJigglerModeAtom, + mouseLastMoveTimeAtom, + scrollDirectionAtom, + scrollIntervalAtom +} from '@/jotai/mouse.ts'; import { device } from '@/libs/device'; import { Key } from '@/libs/device/mouse.ts'; @@ -15,6 +20,8 @@ export const Relative = () => { const resolution = useAtomValue(resolutionAtom); const scrollDirection = useAtomValue(scrollDirectionAtom); const scrollInterval = useAtomValue(scrollIntervalAtom); + const mouseJigglerMode = useAtomValue(mouseJigglerModeAtom); + const setMouseLastMoveTime = useSetAtom(mouseLastMoveTimeAtom); const isLockedRef = useRef(false); const keyRef = useRef(new Key()); @@ -110,6 +117,11 @@ export const Relative = () => { if (x === 0 && y === 0) return; await send(Math.abs(x) < 10 ? x * 2 : x, Math.abs(y) < 10 ? y * 2 : y, 0); + + // mouse jiggler record last move time + if (mouseJigglerMode === 'enable') { + setMouseLastMoveTime(Date.now()); + } } // mouse scroll @@ -138,7 +150,7 @@ export const Relative = () => { canvas.removeEventListener('wheel', handleWheel); canvas.removeEventListener('contextmenu', disableEvent); }; - }, [resolution, scrollDirection, scrollInterval]); + }, [resolution, scrollDirection, scrollInterval, mouseJigglerMode, setMouseLastMoveTime]); async function send(x: number, y: number, scroll: number) { await device.sendMouseRelativeData(keyRef.current, x, y, scroll); diff --git a/browser/src/i18n/locales/en.ts b/browser/src/i18n/locales/en.ts index 59ea63b..18be788 100644 --- a/browser/src/i18n/locales/en.ts +++ b/browser/src/i18n/locales/en.ts @@ -56,7 +56,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: { language: 'Language', diff --git a/browser/src/i18n/locales/zh.ts b/browser/src/i18n/locales/zh.ts index e7f1f85..1e73f1d 100644 --- a/browser/src/i18n/locales/zh.ts +++ b/browser/src/i18n/locales/zh.ts @@ -54,7 +54,12 @@ const zh = { speed: '滚轮速度', fast: '快', slow: '慢', - requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。' + requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。', + jiggler: { + title: '闲时晃动', + enable: '启用', + disable: '禁用' + } }, settings: { language: '语言', diff --git a/browser/src/jotai/mouse.ts b/browser/src/jotai/mouse.ts index 7678c93..162484a 100644 --- a/browser/src/jotai/mouse.ts +++ b/browser/src/jotai/mouse.ts @@ -9,6 +9,17 @@ export const mouseModeAtom = atom('absolute'); // mouse scroll direction: 1 or -1 export const scrollDirectionAtom = atom(1); -// mouse scroll interval (unit: ms) // mouse scroll interval (unit: ms) export const scrollIntervalAtom = atom(0); + +// mouse jiggler mode: enable or disable +export const mouseJigglerModeAtom = atom('disable'); + +// mouse jiggler timer id +export const mouseJigglerTimerAtom = atom(null); + +// mouse jiggler interval (unit: ms) +export const mouseJigglerIntervalAtom = atom(15_000); + +// mouse jiggler last move time +export const mouseLastMoveTimeAtom = atom(0); diff --git a/browser/src/libs/storage/index.ts b/browser/src/libs/storage/index.ts index 9397942..6340370 100644 --- a/browser/src/libs/storage/index.ts +++ b/browser/src/libs/storage/index.ts @@ -9,6 +9,7 @@ const MOUSE_STYLE_KEY = 'nanokvm-usb-mouse-style'; const MOUSE_MODE_KEY = 'nanokvm-usb-mouse-mode'; const MOUSE_SCROLL_DIRECTION_KEY = 'nanokvm-usb-mouse-scroll-direction'; const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-usb-mouse-scroll-interval'; +const MOUSE_JIGGLER_MODE_KEY = 'nanokvm-usb-mouse-jiggler-mode'; export function getLanguage() { return localStorage.getItem(LANGUAGE_KEY); @@ -107,3 +108,12 @@ export function getMouseScrollInterval(): number | null { export function setMouseScrollInterval(interval: number): void { localStorage.setItem(MOUSE_SCROLL_INTERVAL_KEY, String(interval)); } + +export function getMouseJigglerMode(): string { + const jiggler = localStorage.getItem(MOUSE_JIGGLER_MODE_KEY); + return jiggler && jiggler === 'enable' ? 'enable' : 'disable'; +} + +export function setMouseJigglerMode(jiggler: string): void { + localStorage.setItem(MOUSE_JIGGLER_MODE_KEY, jiggler); +}