mirror of
https://github.com/sipeed/NanoKVM-USB.git
synced 2026-09-11 05:59:57 -05:00
feat: add desktop version mouse jiggler function
feat: update mouse jiggler translation in English and Chinese
This commit is contained in:
@@ -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 => {
|
||||
<Mode />
|
||||
<Direction />
|
||||
<Speed />
|
||||
<Jiggler />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
56
desktop/src/renderer/src/components/menu/mouse/jiggler.tsx
Normal file
56
desktop/src/renderer/src/components/menu/mouse/jiggler.tsx
Normal file
@@ -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) => (
|
||||
<div
|
||||
key={mode.value}
|
||||
className={clsx(
|
||||
'my-1 flex cursor-pointer items-center space-x-1 rounded py-1 pr-5 pl-2 hover:bg-neutral-700/50',
|
||||
mode.value === jigglerMode ? '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>
|
||||
)
|
||||
}
|
||||
@@ -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<void> {
|
||||
disableEvent(event)
|
||||
await send(event)
|
||||
|
||||
mouseJiggler.moveEventCallback()
|
||||
}
|
||||
|
||||
// mouse scroll
|
||||
|
||||
@@ -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<void> {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -50,7 +50,12 @@ const zh = {
|
||||
speed: '滚轮速度',
|
||||
fast: '快',
|
||||
slow: '慢',
|
||||
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。'
|
||||
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。',
|
||||
jiggler: {
|
||||
title: '空闲晃动',
|
||||
enable: '启用',
|
||||
disable: '禁用'
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
title: '设置',
|
||||
|
||||
@@ -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')
|
||||
|
||||
49
desktop/src/renderer/src/libs/mouse-jiggler/index.ts
Normal file
49
desktop/src/renderer/src/libs/mouse-jiggler/index.ts
Normal file
@@ -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<void> {
|
||||
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()
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user