Skip to content

Commit 1afc28a

Browse files
authored
Merge pull request #92 from imguoguo/fix-jiggler
fix: mouse jiggler
2 parents 9750fe2 + bfe8d17 commit 1afc28a

14 files changed

Lines changed: 151 additions & 171 deletions

File tree

browser/src/components/menu/mouse/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
scrollDirectionAtom,
1111
scrollIntervalAtom
1212
} from '@/jotai/mouse.ts';
13+
import { mouseJiggler } from '@/libs/mouse-jiggler';
1314
import * as storage from '@/libs/storage';
1415

1516
import { Direction } from './direction.tsx';
@@ -23,7 +24,7 @@ export const Mouse = () => {
2324
const [mouseMode, setMouseMode] = useAtom(mouseModeAtom);
2425
const setScrollDirection = useSetAtom(scrollDirectionAtom);
2526
const setScrollInterval = useSetAtom(scrollIntervalAtom);
26-
const [mouseJigglerMode, setMouseJigglerMode] = useAtom(mouseJigglerModeAtom);
27+
const setMouseJigglerMode = useSetAtom(mouseJigglerModeAtom);
2728

2829
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
2930

@@ -53,9 +54,8 @@ export const Mouse = () => {
5354
}
5455

5556
const jiggler = storage.getMouseJigglerMode();
56-
if (mouseJigglerMode !== jiggler) {
57-
setMouseJigglerMode(jiggler);
58-
}
57+
mouseJiggler.setMode(jiggler);
58+
setMouseJigglerMode(jiggler);
5959
}
6060

6161
const content = (

browser/src/components/menu/mouse/jiggler.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
1+
import { useEffect } from 'react';
12
import { Popover } from 'antd';
23
import clsx from 'clsx';
34
import { useAtom } from 'jotai';
45
import { MousePointerIcon } from 'lucide-react';
56
import { useTranslation } from 'react-i18next';
67

78
import { mouseJigglerModeAtom } from '@/jotai/mouse.ts';
9+
import { mouseJiggler } from '@/libs/mouse-jiggler';
810
import * as storage from '@/libs/storage';
911

1012
export const Jiggler = () => {
1113
const { t } = useTranslation();
12-
const [mouseJigglerMode, setMouseJigglerMode] = useAtom(mouseJigglerModeAtom);
13-
const mouseJigglerModes = [
14+
const [jigglerMode, setJigglerMode] = useAtom(mouseJigglerModeAtom);
15+
16+
const mouseJigglerModes: { name: string; value: 'enable' | 'disable' }[] = [
1417
{ name: t('mouse.jiggler.enable'), value: 'enable' },
1518
{ name: t('mouse.jiggler.disable'), value: 'disable' }
1619
];
1720

18-
function update(mode: string) {
19-
setMouseJigglerMode(mode);
21+
function update(mode: 'enable' | 'disable'): void {
2022
storage.setMouseJigglerMode(mode);
23+
setJigglerMode(mode);
2124
}
2225

26+
useEffect(() => {
27+
mouseJiggler.setMode(jigglerMode);
28+
}, [jigglerMode]);
29+
2330
const content = (
2431
<>
2532
{mouseJigglerModes.map((mode) => (
2633
<div
2734
key={mode.value}
2835
className={clsx(
29-
'my-1 flex cursor-pointer items-center space-x-1 rounded py-1 pl-2 pr-5 hover:bg-neutral-700/50',
30-
mode.value === mouseJigglerMode ? 'text-blue-500' : 'text-neutral-300'
36+
'my-1 flex cursor-pointer items-center space-x-1 rounded py-1 pr-5 pl-2 hover:bg-neutral-700/50',
37+
mode.value === jigglerMode ? 'text-blue-500' : 'text-neutral-300'
3138
)}
3239
onClick={() => update(mode.value)}
3340
>

browser/src/components/mouse/absolute.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import { useEffect, useRef } from 'react';
2-
import { useAtomValue, useSetAtom } from 'jotai';
2+
import { useAtomValue } from 'jotai';
33

44
import { resolutionAtom } from '@/jotai/device.ts';
5-
import {
6-
mouseJigglerModeAtom,
7-
mouseLastMoveTimeAtom,
8-
scrollDirectionAtom,
9-
scrollIntervalAtom
10-
} from '@/jotai/mouse.ts';
5+
import { scrollDirectionAtom, scrollIntervalAtom } from '@/jotai/mouse.ts';
116
import { device } from '@/libs/device';
127
import { Key } from '@/libs/device/mouse.ts';
8+
import { mouseJiggler } from '@/libs/mouse-jiggler';
139

1410
export const Absolute = () => {
1511
const resolution = useAtomValue(resolutionAtom);
1612
const scrollDirection = useAtomValue(scrollDirectionAtom);
1713
const scrollInterval = useAtomValue(scrollIntervalAtom);
18-
const mouseJigglerMode = useAtomValue(mouseJigglerModeAtom);
19-
const setMouseLastMoveTime = useSetAtom(mouseLastMoveTimeAtom);
2014

2115
const keyRef = useRef<Key>(new Key());
2216
const lastScrollTimeRef = useRef(0);
@@ -82,10 +76,7 @@ export const Absolute = () => {
8276
disableEvent(event);
8377
await send(event);
8478

85-
// mouse jiggler record last move time
86-
if (mouseJigglerMode === 'enable') {
87-
setMouseLastMoveTime(Date.now());
88-
}
79+
mouseJiggler.moveEventCallback();
8980
}
9081

9182
// mouse scroll
@@ -157,7 +148,7 @@ export const Absolute = () => {
157148
canvas.removeEventListener('click', disableEvent);
158149
canvas.removeEventListener('contextmenu', disableEvent);
159150
};
160-
}, [resolution, scrollDirection, scrollInterval, mouseJigglerMode, setMouseLastMoveTime]);
151+
}, [resolution, scrollDirection, scrollInterval]);
161152

162153
// disable default events
163154
function disableEvent(event: any) {
Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,12 @@
1-
import { useEffect, useRef } from 'react';
2-
import { useAtom, useAtomValue } from 'jotai';
1+
import { useAtomValue } from 'jotai';
32

4-
import {
5-
mouseJigglerIntervalAtom,
6-
mouseJigglerModeAtom,
7-
mouseJigglerTimerAtom,
8-
mouseLastMoveTimeAtom,
9-
mouseModeAtom
10-
} from '@/jotai/mouse.ts';
11-
import { device } from '@/libs/device/index.ts';
12-
import { Key } from '@/libs/device/mouse.ts';
3+
import { mouseModeAtom } from '@/jotai/mouse.ts';
134

145
import { Absolute } from './absolute.tsx';
156
import { Relative } from './relative.tsx';
167

178
export const Mouse = () => {
189
const mouseMode = useAtomValue(mouseModeAtom);
1910

20-
// mouse jiggler
21-
const mouseJigglerMode = useAtomValue(mouseJigglerModeAtom);
22-
const [mouseJigglerTimer, setMouseJigglerTimer] = useAtom(mouseJigglerTimerAtom);
23-
const mouseJigglerInterval = useAtomValue(mouseJigglerIntervalAtom);
24-
const mouseLastMoveTime = useAtomValue(mouseLastMoveTimeAtom);
25-
const mouseLastMoveTimeRef = useRef(mouseLastMoveTime);
26-
const emptyKeyRef = useRef<Key>(new Key());
27-
useEffect(() => {
28-
// sync mouseLastMoveTime through ref
29-
mouseLastMoveTimeRef.current = mouseLastMoveTime;
30-
}, [mouseLastMoveTime]);
31-
useEffect(() => {
32-
async function jigglerTimerCallback() {
33-
if (Date.now() - mouseLastMoveTimeRef.current < mouseJigglerInterval) {
34-
return;
35-
}
36-
37-
const rect = document.getElementById('video')!.getBoundingClientRect();
38-
39-
await device.sendMouseAbsoluteData(
40-
emptyKeyRef.current,
41-
rect.width,
42-
rect.height,
43-
rect.width / 2,
44-
rect.height / 2,
45-
0
46-
);
47-
}
48-
49-
// configure interval timer
50-
if (mouseJigglerMode === 'enable') {
51-
if (mouseJigglerTimer === null) {
52-
const timer = setInterval(jigglerTimerCallback, mouseJigglerInterval);
53-
setMouseJigglerTimer(timer);
54-
}
55-
} else {
56-
if (mouseJigglerTimer) {
57-
clearInterval(mouseJigglerTimer);
58-
setMouseJigglerTimer(null);
59-
}
60-
}
61-
}, [mouseJigglerMode]);
62-
6311
return <>{mouseMode === 'relative' ? <Relative /> : <Absolute />}</>;
6412
};

browser/src/components/mouse/relative.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import { useEffect, useRef } from 'react';
22
import { message } from 'antd';
3-
import { useAtomValue, useSetAtom } from 'jotai';
3+
import { useAtomValue } from 'jotai';
44
import { useTranslation } from 'react-i18next';
55

66
import { resolutionAtom } from '@/jotai/device.ts';
7-
import {
8-
mouseJigglerModeAtom,
9-
mouseLastMoveTimeAtom,
10-
scrollDirectionAtom,
11-
scrollIntervalAtom
12-
} from '@/jotai/mouse.ts';
7+
import { scrollDirectionAtom, scrollIntervalAtom } from '@/jotai/mouse.ts';
138
import { device } from '@/libs/device';
149
import { Key } from '@/libs/device/mouse.ts';
10+
import { mouseJiggler } from '@/libs/mouse-jiggler';
1511

1612
export const Relative = () => {
1713
const { t } = useTranslation();
@@ -20,8 +16,6 @@ export const Relative = () => {
2016
const resolution = useAtomValue(resolutionAtom);
2117
const scrollDirection = useAtomValue(scrollDirectionAtom);
2218
const scrollInterval = useAtomValue(scrollIntervalAtom);
23-
const mouseJigglerMode = useAtomValue(mouseJigglerModeAtom);
24-
const setMouseLastMoveTime = useSetAtom(mouseLastMoveTimeAtom);
2519

2620
const isLockedRef = useRef(false);
2721
const keyRef = useRef<Key>(new Key());
@@ -118,10 +112,7 @@ export const Relative = () => {
118112

119113
await send(Math.abs(x) < 10 ? x * 2 : x, Math.abs(y) < 10 ? y * 2 : y, 0);
120114

121-
// mouse jiggler record last move time
122-
if (mouseJigglerMode === 'enable') {
123-
setMouseLastMoveTime(Date.now());
124-
}
115+
mouseJiggler.moveEventCallback();
125116
}
126117

127118
// mouse scroll
@@ -150,7 +141,7 @@ export const Relative = () => {
150141
canvas.removeEventListener('wheel', handleWheel);
151142
canvas.removeEventListener('contextmenu', disableEvent);
152143
};
153-
}, [resolution, scrollDirection, scrollInterval, mouseJigglerMode, setMouseLastMoveTime]);
144+
}, [resolution, scrollDirection, scrollInterval]);
154145

155146
async function send(x: number, y: number, scroll: number) {
156147
await device.sendMouseRelativeData(keyRef.current, x, y, scroll);

browser/src/i18n/locales/zh.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const zh = {
6969
slow: '慢',
7070
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。',
7171
jiggler: {
72-
title: '闲时晃动',
72+
title: '空闲晃动',
7373
enable: '启用',
7474
disable: '禁用'
7575
}

browser/src/jotai/mouse.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,9 @@ export const mouseModeAtom = atom('absolute');
99
// mouse scroll direction: 1 or -1
1010
export const scrollDirectionAtom = atom(1);
1111

12+
// mouse scroll interval (unit: ms)
1213
// mouse scroll interval (unit: ms)
1314
export const scrollIntervalAtom = atom(0);
1415

1516
// mouse jiggler mode: enable or disable
16-
export const mouseJigglerModeAtom = atom('disable');
17-
18-
// mouse jiggler timer id
19-
export const mouseJigglerTimerAtom = atom<number | null>(null);
20-
21-
// mouse jiggler interval (unit: ms)
22-
export const mouseJigglerIntervalAtom = atom(15_000);
23-
24-
// mouse jiggler last move time
25-
export const mouseLastMoveTimeAtom = atom(0);
17+
export const mouseJigglerModeAtom = atom<'enable' | 'disable'>('disable');
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { device } from '@/libs/device';
2+
import { Key } from '@/libs/device/mouse.ts';
3+
4+
const MOUSE_JIGGLER_INTERVAL = 15_000;
5+
const EMPTY_KEY: Key = new Key(false, false, false);
6+
7+
class MouseJiggler {
8+
private lastMoveTime: number;
9+
private timer: number | null;
10+
private mode: 'enable' | 'disable';
11+
12+
constructor() {
13+
this.lastMoveTime = Date.now();
14+
this.timer = null;
15+
this.mode = 'disable';
16+
}
17+
18+
// enable or disable mouse jiggler
19+
setMode(mode: 'enable' | 'disable'): void {
20+
this.mode = mode;
21+
if (mode === 'disable' && this.timer !== null) {
22+
clearInterval(this.timer);
23+
this.timer = null;
24+
} else if (mode === 'enable' && this.timer === null) {
25+
this.timer = setInterval(() => {
26+
this.timeoutCallback();
27+
}, MOUSE_JIGGLER_INTERVAL / 5);
28+
}
29+
}
30+
31+
// addEventListener to canvas on 'mousemove' event
32+
moveEventCallback(): void {
33+
if (this.mode === 'enable') {
34+
this.lastMoveTime = Date.now();
35+
}
36+
}
37+
38+
timeoutCallback(): void {
39+
if (Date.now() - this.lastMoveTime > MOUSE_JIGGLER_INTERVAL) {
40+
this.lastMoveTime = Date.now() - 1_000;
41+
this.sendJiggle();
42+
}
43+
}
44+
45+
async sendJiggle(): Promise<void> {
46+
await device.sendMouseRelativeData(EMPTY_KEY, 10, 10, 0);
47+
await device.sendMouseRelativeData(EMPTY_KEY, -10, -10, 0);
48+
}
49+
}
50+
51+
export const mouseJiggler = new MouseJiggler();

browser/src/libs/storage/index.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const MOUSE_STYLE_KEY = 'nanokvm-usb-mouse-style';
1111
const MOUSE_MODE_KEY = 'nanokvm-usb-mouse-mode';
1212
const MOUSE_SCROLL_DIRECTION_KEY = 'nanokvm-usb-mouse-scroll-direction';
1313
const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-usb-mouse-scroll-interval';
14-
const MOUSE_JIGGLER_MODE_KEY = 'nanokvm-usb-mouse-jiggler-mode';
1514
const SHORTCUTS_KEY = 'nanokvm-usb-shortcuts';
15+
const MOUSE_JIGGLER_MODE_KEY = 'nanokvm-usb-mouse-jiggler-mode';
1616

1717
export function getLanguage() {
1818
return localStorage.getItem(LANGUAGE_KEY);
@@ -124,15 +124,6 @@ export function setMouseScrollInterval(interval: number): void {
124124
localStorage.setItem(MOUSE_SCROLL_INTERVAL_KEY, String(interval));
125125
}
126126

127-
export function getMouseJigglerMode(): string {
128-
const jiggler = localStorage.getItem(MOUSE_JIGGLER_MODE_KEY);
129-
return jiggler && jiggler === 'enable' ? 'enable' : 'disable';
130-
}
131-
132-
export function setMouseJigglerMode(jiggler: string): void {
133-
localStorage.setItem(MOUSE_JIGGLER_MODE_KEY, jiggler);
134-
}
135-
136127
export function getShortcuts(): ShortcutProps[] {
137128
const shortcuts = localStorage.getItem(SHORTCUTS_KEY);
138129
if (!shortcuts) return [];
@@ -142,3 +133,12 @@ export function getShortcuts(): ShortcutProps[] {
142133
export function setShortcuts(shortcuts: ShortcutProps[]): void {
143134
localStorage.setItem(SHORTCUTS_KEY, window.JSON.stringify(shortcuts));
144135
}
136+
137+
export function getMouseJigglerMode(): 'enable' | 'disable' {
138+
const jiggler = localStorage.getItem(MOUSE_JIGGLER_MODE_KEY);
139+
return jiggler && jiggler === 'enable' ? 'enable' : 'disable';
140+
}
141+
142+
export function setMouseJigglerMode(jiggler: 'enable' | 'disable'): void {
143+
localStorage.setItem(MOUSE_JIGGLER_MODE_KEY, jiggler);
144+
}

0 commit comments

Comments
 (0)