From 4c1b2f9437925b13ab509086c92b8b1c73c737fc Mon Sep 17 00:00:00 2001 From: Dmitry Reshetnikov Date: Sun, 18 May 2025 16:29:58 +0300 Subject: [PATCH 01/24] fix: update russian localization --- browser/src/i18n/locales/ru.ts | 3 +++ desktop/src/renderer/src/i18n/locales/ru.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/browser/src/i18n/locales/ru.ts b/browser/src/i18n/locales/ru.ts index c1ee4c8..08b536d 100644 --- a/browser/src/i18n/locales/ru.ts +++ b/browser/src/i18n/locales/ru.ts @@ -53,6 +53,9 @@ const ru = { direction: 'Направление прокрутки', scrollUp: 'Обычное', scrollDown: 'Инвертированное', + speed: 'Скорость прокрутки', + fast: 'Быстро', + slow: 'Медленно', requestPointer: 'Используется относительное позиционирование мыши. Чтобы захватить курсор, щелкните по видео на экране' }, settings: { diff --git a/desktop/src/renderer/src/i18n/locales/ru.ts b/desktop/src/renderer/src/i18n/locales/ru.ts index 4e50e6d..98f9ef9 100644 --- a/desktop/src/renderer/src/i18n/locales/ru.ts +++ b/desktop/src/renderer/src/i18n/locales/ru.ts @@ -31,7 +31,8 @@ const ru = { }, keyboard: { paste: 'Вставить текст', - virtualKeyboard: 'Виртуальная клавиатура' + virtualKeyboard: 'Виртуальная клавиатура', + ctrlAltDel: 'Ctrl + Alt + Delete' }, mouse: { cursor: { @@ -47,6 +48,9 @@ const ru = { direction: 'Направление прокрутки', scrollUp: 'Обычное', scrollDown: 'Инвертированное', + speed: 'Скорость прокрутки', + fast: 'Быстро', + slow: 'Медленно', requestPointer: 'Используется относительное позиционирование мыши. Чтобы захватить курсор, щелкните по видео на экране' }, From a662096acd9cc90da494b59c9a0878db42db5cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=BE=E6=B5=9A?= Date: Wed, 28 May 2025 23:11:21 +0800 Subject: [PATCH 02/24] feat: add Ctrl+D and Win+Tab keyboard functionalities with translations --- .../src/components/menu/keyboard/ctrl-d.tsx | 43 +++++++++++++++++++ .../src/components/menu/keyboard/index.tsx | 4 ++ .../src/components/menu/keyboard/win-tab.tsx | 43 +++++++++++++++++++ browser/src/i18n/locales/be.ts | 8 ++-- browser/src/i18n/locales/de.ts | 8 ++-- browser/src/i18n/locales/en.ts | 4 +- browser/src/i18n/locales/nl.ts | 8 ++-- browser/src/i18n/locales/ru.ts | 4 +- browser/src/i18n/locales/zh.ts | 4 +- 9 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 browser/src/components/menu/keyboard/ctrl-d.tsx create mode 100644 browser/src/components/menu/keyboard/win-tab.tsx diff --git a/browser/src/components/menu/keyboard/ctrl-d.tsx b/browser/src/components/menu/keyboard/ctrl-d.tsx new file mode 100644 index 0000000..6cbda05 --- /dev/null +++ b/browser/src/components/menu/keyboard/ctrl-d.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { SendHorizonal } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +import { device } from '@/libs/device'; +import { Modifiers } from '@/libs/device/keyboard.ts'; +import { KeyboardCodes } from '@/libs/keyboard'; + +export const CtrlD = () => { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); + + async function ctrlD(): Promise { + if (isLoading) return; + setIsLoading(true); + + try { + const modifiers = new Modifiers(); + modifiers.leftCtrl = true; + await send(modifiers, KeyboardCodes.get('KeyD')!); + } catch (e) { + console.log(e); + } finally { + setIsLoading(false); + } + } + + async function send(modifiers: Modifiers, code: number) { + const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00]; + await device.sendKeyboardData(modifiers, keys); + await device.sendKeyboardData(new Modifiers(), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + } + + return ( +
+ + {t('keyboard.ctrlD')} +
+ ); +} diff --git a/browser/src/components/menu/keyboard/index.tsx b/browser/src/components/menu/keyboard/index.tsx index 68a652b..223d1b8 100644 --- a/browser/src/components/menu/keyboard/index.tsx +++ b/browser/src/components/menu/keyboard/index.tsx @@ -5,6 +5,8 @@ import { KeyboardIcon } from 'lucide-react'; import { Paste } from './paste.tsx'; import { VirtualKeyboard } from './virtual-keyboard.tsx'; import { CtrlAltDel } from './ctrl-alt-del'; +import { CtrlD } from './ctrl-d.tsx'; +import { WinTab } from './win-tab.tsx'; export const Keyboard = () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -13,6 +15,8 @@ export const Keyboard = () => { <> + + ); diff --git a/browser/src/components/menu/keyboard/win-tab.tsx b/browser/src/components/menu/keyboard/win-tab.tsx new file mode 100644 index 0000000..d190fef --- /dev/null +++ b/browser/src/components/menu/keyboard/win-tab.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; +import { SendHorizonal } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; + +import { device } from '@/libs/device'; +import { Modifiers } from '@/libs/device/keyboard.ts'; +import { KeyboardCodes } from '@/libs/keyboard'; + +export const WinTab = () => { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); + + async function winTab(): Promise { + if (isLoading) return; + setIsLoading(true); + + try { + const modifiers = new Modifiers(); + modifiers.leftWindows = true; + await send(modifiers, KeyboardCodes.get('Tab')!); + } catch (e) { + console.log(e); + } finally { + setIsLoading(false); + } + } + + async function send(modifiers: Modifiers, code: number) { + const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00]; + await device.sendKeyboardData(modifiers, keys); + await device.sendKeyboardData(new Modifiers(), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + } + + return ( +
+ + {t('keyboard.winTab')} +
+ ); +} diff --git a/browser/src/i18n/locales/be.ts b/browser/src/i18n/locales/be.ts index 094db16..e842148 100644 --- a/browser/src/i18n/locales/be.ts +++ b/browser/src/i18n/locales/be.ts @@ -37,7 +37,9 @@ const be = { keyboard: { paste: 'Plakken', virtualKeyboard: 'Virtueel klavier', - ctrlAltDel: 'Ctrl + Alt + Delete' + ctrlAltDel: 'Ctrl + Alt + Delete', + ctrlD: 'Ctrl + D', + winTab: 'Win + Tab', }, mouse: { cursor: { @@ -62,5 +64,5 @@ const be = { } } }; - - export default be; \ No newline at end of file + + export default be; diff --git a/browser/src/i18n/locales/de.ts b/browser/src/i18n/locales/de.ts index 79dbb70..a245d7f 100644 --- a/browser/src/i18n/locales/de.ts +++ b/browser/src/i18n/locales/de.ts @@ -37,7 +37,9 @@ const de = { keyboard: { paste: 'Einfügen', virtualKeyboard: 'Virtuelle Tastatur', - ctrlAltDel: 'Strg + Alt + Entfernen' + ctrlAltDel: 'Strg + Alt + Entfernen', + ctrlD: 'Strg + D', + winTab: 'Win + Tab', }, mouse: { cursor: { @@ -62,5 +64,5 @@ const de = { } } }; - - export default de; \ No newline at end of file + + export default de; diff --git a/browser/src/i18n/locales/en.ts b/browser/src/i18n/locales/en.ts index 59ea63b..c50610c 100644 --- a/browser/src/i18n/locales/en.ts +++ b/browser/src/i18n/locales/en.ts @@ -37,7 +37,9 @@ const en = { keyboard: { paste: 'Paste', virtualKeyboard: 'Keyboard', - ctrlAltDel: 'Ctrl + Alt + Delete' + ctrlAltDel: 'Ctrl + Alt + Delete', + ctrlD: 'Ctrl + D', + winTab: 'Win + Tab', }, mouse: { cursor: { diff --git a/browser/src/i18n/locales/nl.ts b/browser/src/i18n/locales/nl.ts index abab0ea..0896f9b 100644 --- a/browser/src/i18n/locales/nl.ts +++ b/browser/src/i18n/locales/nl.ts @@ -37,7 +37,9 @@ const nl = { keyboard: { paste: 'Plakken', virtualKeyboard: 'Virtueel toetsenbord', - ctrlAltDel: 'Ctrl + Alt + Delete' + ctrlAltDel: 'Ctrl + Alt + Delete', + ctrlD: 'Ctrl + D', + winTab: 'Win + Tab', }, mouse: { cursor: { @@ -62,5 +64,5 @@ const nl = { } } }; - - export default nl; \ No newline at end of file + + export default nl; diff --git a/browser/src/i18n/locales/ru.ts b/browser/src/i18n/locales/ru.ts index c1ee4c8..befcf50 100644 --- a/browser/src/i18n/locales/ru.ts +++ b/browser/src/i18n/locales/ru.ts @@ -37,7 +37,9 @@ const ru = { keyboard: { paste: 'Вставить текст', virtualKeyboard: 'Виртуальная клавиатура', - ctrlAltDel: 'Ctrl + Alt + Delete' + ctrlAltDel: 'Ctrl + Alt + Delete', + ctrlD: 'Ctrl + D', + winTab: 'Win + Tab', }, mouse: { cursor: { diff --git a/browser/src/i18n/locales/zh.ts b/browser/src/i18n/locales/zh.ts index e7f1f85..1cfc9d3 100644 --- a/browser/src/i18n/locales/zh.ts +++ b/browser/src/i18n/locales/zh.ts @@ -35,7 +35,9 @@ const zh = { keyboard: { paste: '粘贴', virtualKeyboard: '虚拟键盘', - ctrlAltDel: 'Ctrl + Alt + Delete' + ctrlAltDel: 'Ctrl + Alt + Delete', + ctrlD: 'Ctrl + D', + winTab: 'Win + Tab', }, mouse: { cursor: { From 90eeec3093138453bbd247d837d5ff59baa909c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=BE=E6=B5=9A?= Date: Wed, 28 May 2025 23:36:06 +0800 Subject: [PATCH 03/24] feat: add keyboard shortcuts menu and update translations --- .../src/components/menu/keyboard/index.tsx | 8 ++--- .../menu/keyboard/shortcuts-menu.tsx | 36 +++++++++++++++++++ browser/src/i18n/locales/be.ts | 1 + browser/src/i18n/locales/de.ts | 1 + browser/src/i18n/locales/en.ts | 1 + browser/src/i18n/locales/nl.ts | 1 + browser/src/i18n/locales/ru.ts | 1 + browser/src/i18n/locales/zh.ts | 1 + 8 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 browser/src/components/menu/keyboard/shortcuts-menu.tsx diff --git a/browser/src/components/menu/keyboard/index.tsx b/browser/src/components/menu/keyboard/index.tsx index 223d1b8..ff59175 100644 --- a/browser/src/components/menu/keyboard/index.tsx +++ b/browser/src/components/menu/keyboard/index.tsx @@ -4,9 +4,7 @@ import { KeyboardIcon } from 'lucide-react'; import { Paste } from './paste.tsx'; import { VirtualKeyboard } from './virtual-keyboard.tsx'; -import { CtrlAltDel } from './ctrl-alt-del'; -import { CtrlD } from './ctrl-d.tsx'; -import { WinTab } from './win-tab.tsx'; +import { KeyboardShortcutsMenu } from './shortcuts-menu.tsx'; export const Keyboard = () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -14,10 +12,8 @@ export const Keyboard = () => { const content = ( <> - - - + ); diff --git a/browser/src/components/menu/keyboard/shortcuts-menu.tsx b/browser/src/components/menu/keyboard/shortcuts-menu.tsx new file mode 100644 index 0000000..cc88ca2 --- /dev/null +++ b/browser/src/components/menu/keyboard/shortcuts-menu.tsx @@ -0,0 +1,36 @@ +import { useState } from 'react'; +import { SendHorizonal } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Popover } from 'antd'; + +import { CtrlAltDel } from './ctrl-alt-del'; +import { CtrlD } from './ctrl-d.tsx'; +import { WinTab } from './win-tab.tsx'; + +export const KeyboardShortcutsMenu = () => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + + return ( + + + + + + } + trigger="click" + placement="rightTop" + align={{ offset: [14, 0] }} + open={open} + onOpenChange={setOpen} + arrow={false} + > +
+ + {t('keyboard.shortcuts')} +
+
+ ); +}; diff --git a/browser/src/i18n/locales/be.ts b/browser/src/i18n/locales/be.ts index e842148..3afee03 100644 --- a/browser/src/i18n/locales/be.ts +++ b/browser/src/i18n/locales/be.ts @@ -37,6 +37,7 @@ const be = { keyboard: { paste: 'Plakken', virtualKeyboard: 'Virtueel klavier', + shortcuts: 'Sneltoetsen', ctrlAltDel: 'Ctrl + Alt + Delete', ctrlD: 'Ctrl + D', winTab: 'Win + Tab', diff --git a/browser/src/i18n/locales/de.ts b/browser/src/i18n/locales/de.ts index a245d7f..03dce33 100644 --- a/browser/src/i18n/locales/de.ts +++ b/browser/src/i18n/locales/de.ts @@ -37,6 +37,7 @@ const de = { keyboard: { paste: 'Einfügen', virtualKeyboard: 'Virtuelle Tastatur', + shortcuts: 'Tastenkürzel', ctrlAltDel: 'Strg + Alt + Entfernen', ctrlD: 'Strg + D', winTab: 'Win + Tab', diff --git a/browser/src/i18n/locales/en.ts b/browser/src/i18n/locales/en.ts index c50610c..a971bd9 100644 --- a/browser/src/i18n/locales/en.ts +++ b/browser/src/i18n/locales/en.ts @@ -37,6 +37,7 @@ const en = { keyboard: { paste: 'Paste', virtualKeyboard: 'Keyboard', + shortcuts: 'Shortcuts', ctrlAltDel: 'Ctrl + Alt + Delete', ctrlD: 'Ctrl + D', winTab: 'Win + Tab', diff --git a/browser/src/i18n/locales/nl.ts b/browser/src/i18n/locales/nl.ts index 0896f9b..21934e6 100644 --- a/browser/src/i18n/locales/nl.ts +++ b/browser/src/i18n/locales/nl.ts @@ -37,6 +37,7 @@ const nl = { keyboard: { paste: 'Plakken', virtualKeyboard: 'Virtueel toetsenbord', + shortcuts: 'Sneltoetsen', ctrlAltDel: 'Ctrl + Alt + Delete', ctrlD: 'Ctrl + D', winTab: 'Win + Tab', diff --git a/browser/src/i18n/locales/ru.ts b/browser/src/i18n/locales/ru.ts index befcf50..9c70f15 100644 --- a/browser/src/i18n/locales/ru.ts +++ b/browser/src/i18n/locales/ru.ts @@ -37,6 +37,7 @@ const ru = { keyboard: { paste: 'Вставить текст', virtualKeyboard: 'Виртуальная клавиатура', + shortcuts: 'Сочетания клавиш', ctrlAltDel: 'Ctrl + Alt + Delete', ctrlD: 'Ctrl + D', winTab: 'Win + Tab', diff --git a/browser/src/i18n/locales/zh.ts b/browser/src/i18n/locales/zh.ts index 1cfc9d3..773a87e 100644 --- a/browser/src/i18n/locales/zh.ts +++ b/browser/src/i18n/locales/zh.ts @@ -35,6 +35,7 @@ const zh = { keyboard: { paste: '粘贴', virtualKeyboard: '虚拟键盘', + shortcuts: '快捷键', ctrlAltDel: 'Ctrl + Alt + Delete', ctrlD: 'Ctrl + D', winTab: 'Win + Tab', From be5a233e56a070d05debee0262bf87f65190e2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=BE=E6=B5=9A?= Date: Thu, 29 May 2025 16:28:14 +0800 Subject: [PATCH 04/24] feat: refactor keyboard shortcuts menu and consolidate shortcut components --- .../src/components/menu/keyboard/ctrl-d.tsx | 43 ------------------- .../{ctrl-alt-del.tsx => shortcut.tsx} | 28 ++++++------ .../menu/keyboard/shortcuts-menu.tsx | 31 ++++++++++--- .../src/components/menu/keyboard/win-tab.tsx | 43 ------------------- 4 files changed, 40 insertions(+), 105 deletions(-) delete mode 100644 browser/src/components/menu/keyboard/ctrl-d.tsx rename browser/src/components/menu/keyboard/{ctrl-alt-del.tsx => shortcut.tsx} (60%) delete mode 100644 browser/src/components/menu/keyboard/win-tab.tsx diff --git a/browser/src/components/menu/keyboard/ctrl-d.tsx b/browser/src/components/menu/keyboard/ctrl-d.tsx deleted file mode 100644 index 6cbda05..0000000 --- a/browser/src/components/menu/keyboard/ctrl-d.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useState } from 'react'; -import { SendHorizonal } from 'lucide-react'; -import { useTranslation } from 'react-i18next'; - -import { device } from '@/libs/device'; -import { Modifiers } from '@/libs/device/keyboard.ts'; -import { KeyboardCodes } from '@/libs/keyboard'; - -export const CtrlD = () => { - const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(false); - - async function ctrlD(): Promise { - if (isLoading) return; - setIsLoading(true); - - try { - const modifiers = new Modifiers(); - modifiers.leftCtrl = true; - await send(modifiers, KeyboardCodes.get('KeyD')!); - } catch (e) { - console.log(e); - } finally { - setIsLoading(false); - } - } - - async function send(modifiers: Modifiers, code: number) { - const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00]; - await device.sendKeyboardData(modifiers, keys); - await device.sendKeyboardData(new Modifiers(), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); - } - - return ( -
- - {t('keyboard.ctrlD')} -
- ); -} diff --git a/browser/src/components/menu/keyboard/ctrl-alt-del.tsx b/browser/src/components/menu/keyboard/shortcut.tsx similarity index 60% rename from browser/src/components/menu/keyboard/ctrl-alt-del.tsx rename to browser/src/components/menu/keyboard/shortcut.tsx index 4f1e12e..95a0f6f 100644 --- a/browser/src/components/menu/keyboard/ctrl-alt-del.tsx +++ b/browser/src/components/menu/keyboard/shortcut.tsx @@ -1,24 +1,27 @@ import { useState } from 'react'; import { SendHorizonal } from 'lucide-react'; -import { useTranslation } from 'react-i18next'; import { device } from '@/libs/device'; import { Modifiers } from '@/libs/device/keyboard.ts'; import { KeyboardCodes } from '@/libs/keyboard'; -export const CtrlAltDel = () => { - const { t } = useTranslation(); +interface ShortcutProps { + label: string; + modifiers?: Partial; + keyCode: string; +} + +export const Shortcut = ({ label, modifiers = {}, keyCode }: ShortcutProps) => { const [isLoading, setIsLoading] = useState(false); - async function ctrlAltDel(): Promise { + async function handleClick(): Promise { if (isLoading) return; setIsLoading(true); try { - const modifiers = new Modifiers(); - modifiers.leftCtrl = true; - modifiers.leftAlt = true; - await send(modifiers, KeyboardCodes.get('Delete')!); + const mods = new Modifiers(); + Object.assign(mods, modifiers); + await send(mods, KeyboardCodes.get(keyCode)!); } catch (e) { console.log(e); } finally { @@ -26,20 +29,19 @@ export const CtrlAltDel = () => { } } - async function send(modifiers: Modifiers, code: number) { + async function send(mods: Modifiers, code: number) { const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00]; - await device.sendKeyboardData(modifiers, keys); - + await device.sendKeyboardData(mods, keys); await device.sendKeyboardData(new Modifiers(), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); } return (
- {t('keyboard.ctrlAltDel')} + {label}
); }; diff --git a/browser/src/components/menu/keyboard/shortcuts-menu.tsx b/browser/src/components/menu/keyboard/shortcuts-menu.tsx index cc88ca2..6f960cf 100644 --- a/browser/src/components/menu/keyboard/shortcuts-menu.tsx +++ b/browser/src/components/menu/keyboard/shortcuts-menu.tsx @@ -3,9 +3,7 @@ import { SendHorizonal } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Popover } from 'antd'; -import { CtrlAltDel } from './ctrl-alt-del'; -import { CtrlD } from './ctrl-d.tsx'; -import { WinTab } from './win-tab.tsx'; +import { Shortcut } from './shortcut.tsx'; export const KeyboardShortcutsMenu = () => { const { t } = useTranslation(); @@ -15,9 +13,30 @@ export const KeyboardShortcutsMenu = () => { - - - + {[ + { + label: t('keyboard.ctrlAltDel'), + modifiers: { leftCtrl: true, leftAlt: true }, + keyCode: 'Delete', + }, + { + label: t('keyboard.ctrlD'), + modifiers: { leftCtrl: true }, + keyCode: 'KeyD', + }, + { + label: t('keyboard.winTab'), + modifiers: { leftWindows: true }, + keyCode: 'Tab', + }, + ].map((shortcut) => ( + + ))} } trigger="click" diff --git a/browser/src/components/menu/keyboard/win-tab.tsx b/browser/src/components/menu/keyboard/win-tab.tsx deleted file mode 100644 index d190fef..0000000 --- a/browser/src/components/menu/keyboard/win-tab.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useState } from 'react'; -import { SendHorizonal } from 'lucide-react'; -import { useTranslation } from 'react-i18next'; - -import { device } from '@/libs/device'; -import { Modifiers } from '@/libs/device/keyboard.ts'; -import { KeyboardCodes } from '@/libs/keyboard'; - -export const WinTab = () => { - const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(false); - - async function winTab(): Promise { - if (isLoading) return; - setIsLoading(true); - - try { - const modifiers = new Modifiers(); - modifiers.leftWindows = true; - await send(modifiers, KeyboardCodes.get('Tab')!); - } catch (e) { - console.log(e); - } finally { - setIsLoading(false); - } - } - - async function send(modifiers: Modifiers, code: number) { - const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00]; - await device.sendKeyboardData(modifiers, keys); - await device.sendKeyboardData(new Modifiers(), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); - } - - return ( -
- - {t('keyboard.winTab')} -
- ); -} From c3375cb595cd2fecdc5d1125180cfb84efee9810 Mon Sep 17 00:00:00 2001 From: aangert <40806022+aangert@users.noreply.github.com> Date: Fri, 30 May 2025 03:27:16 +0200 Subject: [PATCH 05/24] update german localization --- browser/src/i18n/locales/de.ts | 3 +++ desktop/src/renderer/src/i18n/locales/de.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/browser/src/i18n/locales/de.ts b/browser/src/i18n/locales/de.ts index 79dbb70..e96d9f8 100644 --- a/browser/src/i18n/locales/de.ts +++ b/browser/src/i18n/locales/de.ts @@ -53,6 +53,9 @@ const de = { direction: 'Scrollrichtung', scrollUp: 'Hochscrollen', scrollDown: 'Runterscrollen', + speed: 'Scrollgeschwindigkeit', + fast: 'Schnell', + slow: 'Langsam', requestPointer: 'Benutze relativen Modus. Bitte auf den Desktop klicken, um den Mauszeiger anzuzeigen.' }, settings: { diff --git a/desktop/src/renderer/src/i18n/locales/de.ts b/desktop/src/renderer/src/i18n/locales/de.ts index 8a1ac47..5e0fc83 100644 --- a/desktop/src/renderer/src/i18n/locales/de.ts +++ b/desktop/src/renderer/src/i18n/locales/de.ts @@ -48,6 +48,9 @@ const de = { direction: 'Scrollrichtung', scrollUp: 'Hochscrollen', scrollDown: 'Runterscrollen', + speed: 'Scrollgeschwindigkeit', + fast: 'Schnell', + slow: 'Langsam', requestPointer: 'Benutze relativen Modus. Bitte auf den Desktop klicken, um den Mauszeiger anzuzeigen.' }, settings: { 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 06/24] 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); +} From b76902c0f3ec8eb0213e1b6081a16f2130c8347b Mon Sep 17 00:00:00 2001 From: kevinzhangzj <710895909@qq.com> Date: Sat, 31 May 2025 15:00:10 +0800 Subject: [PATCH 07/24] feat: add desktop version mouse jiggler function feat: update mouse jiggler translation in English and Chinese --- .../src/components/menu/mouse/index.tsx | 8 +++ .../src/components/menu/mouse/jiggler.tsx | 56 +++++++++++++++++++ .../src/components/mouse/absolute.tsx | 3 + .../src/components/mouse/relative.tsx | 3 + desktop/src/renderer/src/i18n/locales/en.ts | 7 ++- desktop/src/renderer/src/i18n/locales/zh.ts | 7 ++- desktop/src/renderer/src/jotai/mouse.ts | 5 +- .../renderer/src/libs/mouse-jiggler/index.ts | 49 ++++++++++++++++ .../src/renderer/src/libs/storage/index.ts | 10 ++++ 9 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 desktop/src/renderer/src/components/menu/mouse/jiggler.tsx create mode 100644 desktop/src/renderer/src/libs/mouse-jiggler/index.ts diff --git a/desktop/src/renderer/src/components/menu/mouse/index.tsx b/desktop/src/renderer/src/components/menu/mouse/index.tsx index 46dc0ce..12e7329 100644 --- a/desktop/src/renderer/src/components/menu/mouse/index.tsx +++ b/desktop/src/renderer/src/components/menu/mouse/index.tsx @@ -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 => { + ) diff --git a/desktop/src/renderer/src/components/menu/mouse/jiggler.tsx b/desktop/src/renderer/src/components/menu/mouse/jiggler.tsx new file mode 100644 index 0000000..5d9bcbf --- /dev/null +++ b/desktop/src/renderer/src/components/menu/mouse/jiggler.tsx @@ -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) => ( +
update(mode.value)} + > + {mode.name} +
+ ))} + + ) + return ( + +
+
+ +
+ {t('mouse.jiggler.title')} +
+
+ ) +} diff --git a/desktop/src/renderer/src/components/mouse/absolute.tsx b/desktop/src/renderer/src/components/mouse/absolute.tsx index 4a2c671..525035d 100644 --- a/desktop/src/renderer/src/components/mouse/absolute.tsx +++ b/desktop/src/renderer/src/components/mouse/absolute.tsx @@ -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 { disableEvent(event) await send(event) + + mouseJiggler.moveEventCallback() } // mouse scroll diff --git a/desktop/src/renderer/src/components/mouse/relative.tsx b/desktop/src/renderer/src/components/mouse/relative.tsx index 5bdd7b1..a270fec 100644 --- a/desktop/src/renderer/src/components/mouse/relative.tsx +++ b/desktop/src/renderer/src/components/mouse/relative.tsx @@ -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 { diff --git a/desktop/src/renderer/src/i18n/locales/en.ts b/desktop/src/renderer/src/i18n/locales/en.ts index 707c660..83e1775 100644 --- a/desktop/src/renderer/src/i18n/locales/en.ts +++ b/desktop/src/renderer/src/i18n/locales/en.ts @@ -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', diff --git a/desktop/src/renderer/src/i18n/locales/zh.ts b/desktop/src/renderer/src/i18n/locales/zh.ts index f6ea3f1..01b5add 100644 --- a/desktop/src/renderer/src/i18n/locales/zh.ts +++ b/desktop/src/renderer/src/i18n/locales/zh.ts @@ -50,7 +50,12 @@ const zh = { speed: '滚轮速度', fast: '快', slow: '慢', - requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。' + requestPointer: '正在使用鼠标相对模式,请点击桌面获取鼠标指针。', + jiggler: { + title: '空闲晃动', + enable: '启用', + disable: '禁用' + } }, settings: { title: '设置', diff --git a/desktop/src/renderer/src/jotai/mouse.ts b/desktop/src/renderer/src/jotai/mouse.ts index 4a33252..01f3620 100644 --- a/desktop/src/renderer/src/jotai/mouse.ts +++ b/desktop/src/renderer/src/jotai/mouse.ts @@ -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') diff --git a/desktop/src/renderer/src/libs/mouse-jiggler/index.ts b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts new file mode 100644 index 0000000..b041fc7 --- /dev/null +++ b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts @@ -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 { + 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() diff --git a/desktop/src/renderer/src/libs/storage/index.ts b/desktop/src/renderer/src/libs/storage/index.ts index 0024a9d..4a4b701 100644 --- a/desktop/src/renderer/src/libs/storage/index.ts +++ b/desktop/src/renderer/src/libs/storage/index.ts @@ -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) +} From dbe4251adac0bfeda57c1caeb6428495c49c00b0 Mon Sep 17 00:00:00 2001 From: kevinzhangzj <710895909@qq.com> Date: Sat, 31 May 2025 15:00:10 +0800 Subject: [PATCH 08/24] feat: add desktop version mouse jiggler function feat: update mouse jiggler translation in English and Chinese --- desktop/src/renderer/src/libs/mouse-jiggler/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop/src/renderer/src/libs/mouse-jiggler/index.ts b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts index b041fc7..bfdd4b7 100644 --- a/desktop/src/renderer/src/libs/mouse-jiggler/index.ts +++ b/desktop/src/renderer/src/libs/mouse-jiggler/index.ts @@ -37,6 +37,7 @@ class MouseJiggler { timeoutCallback(): void { if (Date.now() - this.lastMoveTime > MOUSE_JIGGLER_INTERVAL) { + this.lastMoveTime = Date.now() - 1_000 this.sendJiggle() } } From 515e7709c640996321d14ffab23d1353dd9c15b6 Mon Sep 17 00:00:00 2001 From: Inhwan Wee Date: Mon, 2 Jun 2025 14:14:04 +0900 Subject: [PATCH 09/24] Add Language KR (Korean) --- browser/src/i18n/languages.ts | 3 +- browser/src/i18n/locales/ko.ts | 69 +++++++++++++++++ desktop/src/renderer/src/i18n/languages.ts | 3 +- desktop/src/renderer/src/i18n/locales/ko.ts | 83 +++++++++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 browser/src/i18n/locales/ko.ts create mode 100644 desktop/src/renderer/src/i18n/locales/ko.ts diff --git a/browser/src/i18n/languages.ts b/browser/src/i18n/languages.ts index 59019eb..9edb392 100644 --- a/browser/src/i18n/languages.ts +++ b/browser/src/i18n/languages.ts @@ -4,7 +4,8 @@ const languages = [ { key: 'zh', name: '中文' }, { key: 'de', name: 'Deutsch' }, { key: 'nl', name: 'Nederlands' }, - { key: 'be', name: 'België' } + { key: 'be', name: 'België' }, + { key: 'ko', name: '한국어' } ]; languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })); diff --git a/browser/src/i18n/locales/ko.ts b/browser/src/i18n/locales/ko.ts new file mode 100644 index 0000000..e07fbcc --- /dev/null +++ b/browser/src/i18n/locales/ko.ts @@ -0,0 +1,69 @@ +const ko = { + translation: { + serial: { + notSupported: + '시리얼이 지원되지 않습니다. 마우스와 키보드를 사용하려면 Chrome 브라우저를 사용하세요.', + failed: '시리얼 연결에 실패했습니다. 다시 시도해 주세요.' + }, + camera: { + tip: '권한을 기다리는 중...', + denied: '권한이 거부되었습니다.', + authorize: + 'Target PC 연결에 카메라 권한이 필요합니다. 브라우저 설정에서 카메라 권한을 허용해 주세요.', + failed: '카메라 연결에 실패했습니다. 다시 시도해 주세요.' + }, + modal: { + title: 'USB 장치 선택', + selectVideo: '비디오 입력 장치를 선택해 주세요.', + selectSerial: '시리얼 장치를 선택해 주세요.' + }, + menu: { + serial: '시리얼', + keyboard: '키보드', + mouse: '마우스' + }, + video: { + resolution: '해상도', + customResolution: '사용자 정의', + device: '장치', + custom: { + title: '사용자 정의 해상도', + width: '가로', + height: '세로', + confirm: '확인', + cancel: '취소' + } + }, + keyboard: { + paste: '붙여넣기', + virtualKeyboard: '가상 키보드', + ctrlAltDel: 'Ctrl + Alt + Delete' + }, + mouse: { + cursor: { + title: '커서 모양', + pointer: '포인터', + grab: '손', + cell: '플러스', + hide: '숨기기' + }, + mode: '마우스 모드', + absolute: '절대 모드', + relative: '상대 모드', + direction: '휠 방향', + scrollUp: '위로 스크롤', + scrollDown: '아래로 스크롤', + speed: '휠 속도', + fast: '빠르게', + slow: '느리게', + requestPointer: '상대 모드를 사용 중입니다. 마우스 포인터를 가져오려면 스크린을 클릭하세요.' + }, + settings: { + language: '언어', + document: '문서', + download: '다운로드' + } + } +}; + +export default ko; diff --git a/desktop/src/renderer/src/i18n/languages.ts b/desktop/src/renderer/src/i18n/languages.ts index eb2fcb8..035e848 100644 --- a/desktop/src/renderer/src/i18n/languages.ts +++ b/desktop/src/renderer/src/i18n/languages.ts @@ -4,7 +4,8 @@ const languages = [ { key: 'zh', name: '中文' }, { key: 'de', name: 'Deutsch' }, { key: 'nl', name: 'Nederlands' }, - { key: 'be', name: 'België' } + { key: 'be', name: 'België' }, + { key: 'ko', name: '한국어' } ] languages.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })) diff --git a/desktop/src/renderer/src/i18n/locales/ko.ts b/desktop/src/renderer/src/i18n/locales/ko.ts new file mode 100644 index 0000000..7e0b720 --- /dev/null +++ b/desktop/src/renderer/src/i18n/locales/ko.ts @@ -0,0 +1,83 @@ +const ko = { + translation: { + camera: { + tip: '권한을 기다리는 중...', + denied: '권한이 거부되었습니다.', + authorize: + 'Target PC 연결에 카메라 권한이 필요합니다. 설정에서 카메라 권한을 허용해 주세요.', + failed: '카메라 연결에 실패했습니다. 다시 시도해 주세요.' + }, + modal: { + title: 'USB 장치 선택', + selectVideo: '비디오 입력 장치를 선택해 주세요.', + selectSerial: '시리얼 장치를 선택해 주세요.' + }, + menu: { + serial: '시리얼', + keyboard: '키보드', + mouse: '마우스' + }, + video: { + resolution: '해상도', + customResolution: '사용자 정의', + device: '장치', + custom: { + title: '사용자 정의 해상도', + width: '가로', + height: '세로', + confirm: '확인', + cancel: '취소' + } + }, + keyboard: { + paste: '붙여넣기', + virtualKeyboard: '가상 키보드', + ctrlAltDel: 'Ctrl + Alt + Delete' + }, + mouse: { + cursor: { + title: '커서 모양', + pointer: '포인터', + grab: '손', + cell: '플러스', + hide: '숨기기' + }, + mode: '마우스 모드', + absolute: '절대 모드', + relative: '상대 모드', + direction: '휠 방향', + scrollUp: '위로 스크롤', + scrollDown: '아래로 스크롤', + speed: '휠 속도', + fast: '빠르게', + slow: '느리게', + requestPointer: '상대 모드를 사용 중입니다. 마우스 포인터를 가져오려면 스크린을 클릭하세요.' + }, + settings: { + title: '설정', + appearance: { + title: '화면 설정', + language: '언어', + menu: '메뉴 바', + menuTips: '시작 시 메뉴 바 열기' + }, + update: { + title: '업데이트 확인', + latest: '최신 버전을 사용 중입니다.', + outdated: '업데이트가 가능합니다. 지금 업데이트하시겠습니까?', + downloading: '다운로드 중...', + installing: '설치 중...', + failed: '업데이트에 실패했습니다. 다시 시도해 주세요.', + confirm: '확인', + cancel: '취소' + }, + about: { + title: '정보', + version: '버전', + community: '커뮤니티' + } + } + } +} + +export default ko From 3cba902de28fc0fbe91e3b7b48532e61a6e7800c Mon Sep 17 00:00:00 2001 From: Inhwan Wee Date: Mon, 2 Jun 2025 14:55:46 +0900 Subject: [PATCH 10/24] add video scale (desktop) --- desktop/src/renderer/src/App.tsx | 16 ++++- .../src/components/menu/video/index.tsx | 2 + .../src/components/menu/video/scale.tsx | 58 +++++++++++++++++++ desktop/src/renderer/src/i18n/locales/be.ts | 5 +- desktop/src/renderer/src/i18n/locales/de.ts | 5 +- desktop/src/renderer/src/i18n/locales/en.ts | 1 + desktop/src/renderer/src/i18n/locales/ko.ts | 1 + desktop/src/renderer/src/i18n/locales/nl.ts | 5 +- desktop/src/renderer/src/i18n/locales/ru.ts | 1 + desktop/src/renderer/src/i18n/locales/zh.ts | 1 + desktop/src/renderer/src/jotai/device.ts | 2 + .../src/renderer/src/libs/storage/index.ts | 13 +++++ 12 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 desktop/src/renderer/src/components/menu/video/scale.tsx diff --git a/desktop/src/renderer/src/App.tsx b/desktop/src/renderer/src/App.tsx index 97f9bb4..7ab90ac 100644 --- a/desktop/src/renderer/src/App.tsx +++ b/desktop/src/renderer/src/App.tsx @@ -11,7 +11,12 @@ import { Keyboard } from '@renderer/components/keyboard' import { Menu } from '@renderer/components/menu' import { Mouse } from '@renderer/components/mouse' import { VirtualKeyboard } from '@renderer/components/virtual-keyboard' -import { resolutionAtom, serialPortStateAtom, videoStateAtom } from '@renderer/jotai/device' +import { + resolutionAtom, + serialPortStateAtom, + videoScaleAtom, + videoStateAtom +} from '@renderer/jotai/device' import { isKeyboardEnableAtom } from '@renderer/jotai/keyboard' import { mouseStyleAtom } from '@renderer/jotai/mouse' import { camera } from '@renderer/libs/camera' @@ -24,6 +29,7 @@ const App = (): ReactElement => { const { t } = useTranslation() const isBigScreen = useMediaQuery({ minWidth: 850 }) + const videoScale = useAtomValue(videoScaleAtom) const videoState = useAtomValue(videoStateAtom) const serialPortState = useAtomValue(serialPortStateAtom) const mouseStyle = useAtomValue(mouseStyleAtom) @@ -115,7 +121,13 @@ const App = (): ReactElement => {