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
8 changes: 8 additions & 0 deletions desktop/src/renderer/src/components/menu/mouse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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)

Expand All @@ -44,6 +48,9 @@ export const Mouse = (): ReactElement => {
if (interval) {
setScrollInterval(interval)
}
const jiggler = storage.getMouseJigglerMode()
mouseJiggler.setMode(jiggler)
setMouseJigglerMode(jiggler)
}, [])

const content = (
Expand All @@ -52,6 +59,7 @@ export const Mouse = (): ReactElement => {
<Mode />
<Direction />
<Speed />
<Jiggler />
</div>
)

Expand Down
56 changes: 56 additions & 0 deletions desktop/src/renderer/src/components/menu/mouse/jiggler.tsx
Original file line number Diff line number Diff line change
@@ -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>
)
}
3 changes: 3 additions & 0 deletions desktop/src/renderer/src/components/mouse/absolute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -77,6 +78,8 @@ export const Absolute = (): ReactElement => {
async function handleMouseMove(event: MouseEvent): Promise<void> {
disableEvent(event)
await send(event)

mouseJiggler.moveEventCallback()
}

// mouse scroll
Expand Down
3 changes: 3 additions & 0 deletions desktop/src/renderer/src/components/mouse/relative.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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> {
Expand Down
7 changes: 6 additions & 1 deletion desktop/src/renderer/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 6 additions & 1 deletion desktop/src/renderer/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ const zh = {
speed: '滚轮速度',
fast: '快',
slow: '慢',
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。'
requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。',
jiggler: {
title: '空闲晃动',
enable: '启用',
disable: '禁用'
}
},
settings: {
title: '设置',
Expand Down
5 changes: 4 additions & 1 deletion desktop/src/renderer/src/jotai/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
50 changes: 50 additions & 0 deletions desktop/src/renderer/src/libs/mouse-jiggler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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.lastMoveTime = Date.now() - 1_000
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()
10 changes: 10 additions & 0 deletions desktop/src/renderer/src/libs/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}