Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions browser/src/components/menu/mouse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAtom, useSetAtom } from 'jotai';
import { MouseIcon } from 'lucide-react';

import {
mouseJigglerModeAtom,
mouseModeAtom,
mouseStyleAtom,
scrollDirectionAtom,
Expand All @@ -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';
Expand All @@ -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);

Expand Down Expand Up @@ -48,6 +51,11 @@ export const Mouse = () => {
if (interval) {
setScrollInterval(interval);
}

const jiggler = storage.getMouseJigglerMode();
if (mouseJigglerMode !== jiggler) {
setMouseJigglerMode(jiggler);
}
}

const content = (
Expand All @@ -56,6 +64,7 @@ export const Mouse = () => {
<Mode />
<Direction />
<Speed />
<Jiggler />
</>
);

Expand Down
49 changes: 49 additions & 0 deletions browser/src/components/menu/mouse/jiggler.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<div
key={mode.value}
className={clsx(
'my-1 flex cursor-pointer items-center space-x-1 rounded py-1 pl-2 pr-5 hover:bg-neutral-700/50',
mode.value === mouseJigglerMode ? 'text-blue-500' : 'text-neutral-300'
)}
onClick={() => update(mode.value)}
>
{mode.name}
</div>
))}
</>
);
return (
<Popover content={content} placement="rightTop" arrow={false} align={{ offset: [13, 0] }}>
<div className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/50">
<div className="flex h-[14px] w-[20px] items-end">
<MousePointerIcon size={16} />
</div>
<span>{t('mouse.jiggler.title')}</span>
</div>
</Popover>
);
};
18 changes: 15 additions & 3 deletions browser/src/components/mouse/absolute.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
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';

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<Key>(new Key());
const lastScrollTimeRef = useRef(0);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
56 changes: 54 additions & 2 deletions browser/src/components/mouse/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,64 @@
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';

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<Key>(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' ? <Relative /> : <Absolute />}</>;
};
18 changes: 15 additions & 3 deletions browser/src/components/mouse/relative.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<Key>(new Key());
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion browser/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 6 additions & 1 deletion browser/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ const zh = {
speed: '滚轮速度',
fast: '快',
slow: '慢',
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。'
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。',
jiggler: {
title: '闲时晃动',
enable: '启用',
disable: '禁用'
}
},
settings: {
language: '语言',
Expand Down
13 changes: 12 additions & 1 deletion browser/src/jotai/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number | null>(null);

// mouse jiggler interval (unit: ms)
export const mouseJigglerIntervalAtom = atom(15_000);

// mouse jiggler last move time
export const mouseLastMoveTimeAtom = atom(0);
10 changes: 10 additions & 0 deletions browser/src/libs/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}