mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-28 16:30:48 +08:00
feat(hid): 支持控制台鼠标抖动
在控制台鼠键配置菜单增加鼠标抖动开关和间隔设置,默认关闭,间隔支持 10–3600 秒。 设备端按键鼠空闲时间调度,支持配置持久化、热更新、真实输入打断和断连后重新计时。 兼容绝对及相对鼠标模式,按键或按钮按住期间暂停抖动。 验证:608 项 Rust 单测通过;暂存快照前端构建和浏览器交互测试通过;已完成 ARMv7 OpenWrt 部署验证。 Refs #316
This commit is contained in:
@@ -8,6 +8,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Slider } from '@/components/ui/slider'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@@ -67,6 +68,53 @@ watch(showCursor, (newValue, oldValue) => {
|
||||
const buttonText = computed(() => t('actionbar.hidConfig'))
|
||||
const hidSaving = ref(false)
|
||||
const hidControlsDisabled = computed(() => hidSaving.value || configStore.hidLoading || !configStore.hid)
|
||||
const jigglerEnabled = computed(() => configStore.hid?.jiggler?.enabled ?? false)
|
||||
const jigglerInterval = ref<string | number>(60)
|
||||
const jigglerIntervalInvalid = ref(false)
|
||||
const hasMouseInterface = computed(() => {
|
||||
const hid = configStore.hid
|
||||
if (!hid || hid.backend === HidBackend.None) return false
|
||||
if (hid.backend !== HidBackend.Otg) return true
|
||||
if (hid.otg_profile === OtgHidProfile.LegacyKeyboard) return false
|
||||
if (hid.otg_profile === OtgHidProfile.Custom) {
|
||||
return !!(hid.otg_functions?.mouse_relative || hid.otg_functions?.mouse_absolute)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
watch(() => [configStore.hid?.jiggler?.interval_secs, configStore.hid?.jiggler?.enabled] as const, ([interval]) => {
|
||||
jigglerInterval.value = interval ?? 60
|
||||
jigglerIntervalInvalid.value = false
|
||||
}, { immediate: true })
|
||||
|
||||
async function saveJiggler(update: { enabled?: boolean; interval_secs?: number }) {
|
||||
if (hidControlsDisabled.value) return
|
||||
hidSaving.value = true
|
||||
try {
|
||||
await configStore.updateHid({ jiggler: update })
|
||||
} catch (error) {
|
||||
toast.error(t('config.updateFailed'), {
|
||||
description: error instanceof Error ? error.message : undefined,
|
||||
})
|
||||
await configStore.refreshHid().catch(() => undefined)
|
||||
jigglerInterval.value = configStore.hid?.jiggler?.interval_secs ?? 60
|
||||
} finally {
|
||||
hidSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toggleJiggler(enabled: boolean) {
|
||||
if (enabled && !hasMouseInterface.value) return
|
||||
void saveJiggler({ enabled })
|
||||
}
|
||||
|
||||
function saveJigglerInterval() {
|
||||
if (hidControlsDisabled.value || !jigglerEnabled.value) return
|
||||
const interval = Number(jigglerInterval.value)
|
||||
jigglerIntervalInvalid.value = !Number.isInteger(interval) || interval < 10 || interval > 3600
|
||||
if (jigglerIntervalInvalid.value || interval === configStore.hid?.jiggler?.interval_secs) return
|
||||
void saveJiggler({ interval_secs: interval })
|
||||
}
|
||||
const supportsLinuxCompatibility = computed(() => usesCh9329(configStore.hid?.backend))
|
||||
const supportsMacosCompatibility = computed(() =>
|
||||
supportsLinuxCompatibility.value || configStore.hid?.backend === HidBackend.Otg
|
||||
@@ -140,6 +188,8 @@ watch(() => props.open, (open) => {
|
||||
mouseThrottle.value = loadMouseMoveSendIntervalFromStorage()
|
||||
showCursor.value = localStorage.getItem('hidShowCursor') !== 'false'
|
||||
if (!hidSaving.value) void configStore.refreshHid().catch(() => undefined)
|
||||
jigglerInterval.value = configStore.hid?.jiggler?.interval_secs ?? 60
|
||||
jigglerIntervalInvalid.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -160,7 +210,7 @@ watch(() => props.open, (open) => {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
@open-auto-focus="focusConsolePanel"
|
||||
class="console-config-panel w-[min(320px,92vw)] p-3"
|
||||
class="console-config-panel w-[min(320px,92vw)] max-h-(--reka-popover-content-available-height) overflow-y-auto p-3"
|
||||
align="start"
|
||||
:side="props.side ?? 'bottom'"
|
||||
>
|
||||
@@ -231,6 +281,45 @@ watch(() => props.open, (open) => {
|
||||
<Label class="text-xs text-muted-foreground">{{ t('actionbar.showCursor') }}</Label>
|
||||
<Switch v-model="showCursor" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-2" :aria-busy="hidSaving">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-1">
|
||||
<Label for="hid-mouse-jiggler" class="text-xs text-muted-foreground">{{ t('actionbar.mouseJiggler') }}</Label>
|
||||
<HelpTooltip :content="t('actionbar.mouseJigglerHelp')" icon-size="sm" />
|
||||
</div>
|
||||
<Switch
|
||||
id="hid-mouse-jiggler"
|
||||
:model-value="jigglerEnabled"
|
||||
:disabled="hidControlsDisabled || (!hasMouseInterface && !jigglerEnabled)"
|
||||
@update:model-value="toggleJiggler"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="jigglerEnabled" class="flex items-center justify-between gap-3">
|
||||
<Label for="hid-jiggler-interval" class="text-xs text-muted-foreground">{{ t('actionbar.jigglerInterval') }}</Label>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input
|
||||
id="hid-jiggler-interval"
|
||||
v-model="jigglerInterval"
|
||||
type="number" min="10" max="3600" step="1" size="sm"
|
||||
class="w-20 text-xs"
|
||||
:disabled="hidControlsDisabled"
|
||||
:aria-invalid="jigglerIntervalInvalid"
|
||||
:aria-describedby="jigglerIntervalInvalid ? 'hid-jiggler-interval-error' : undefined"
|
||||
@input="jigglerIntervalInvalid = false"
|
||||
@blur="saveJigglerInterval"
|
||||
@keydown.enter.prevent="saveJigglerInterval"
|
||||
/>
|
||||
<span class="text-xs text-muted-foreground">{{ t('actionbar.jigglerSeconds') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="jigglerEnabled && jigglerIntervalInvalid" id="hid-jiggler-interval-error" role="alert" class="text-xs text-destructive">{{ t('actionbar.jigglerIntervalInvalid') }}</p>
|
||||
<p v-if="!hasMouseInterface && configStore.hid" class="text-xs text-muted-foreground">{{ t('actionbar.jigglerNoMouse') }}</p>
|
||||
</div>
|
||||
<p v-if="hidSaving" role="status" class="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Loader2 class="size-3 shrink-0 animate-spin" />
|
||||
{{ t('actionbar.applying') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<template v-if="supportsMacosCompatibility">
|
||||
@@ -265,10 +354,6 @@ watch(() => props.open, (open) => {
|
||||
</div>
|
||||
|
||||
<p v-if="macosDragRequiresInterfaces" class="text-xs text-warning">{{ t('actionbar.macosDragRequiresBoth') }}</p>
|
||||
<p v-if="hidSaving" role="status" class="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Loader2 class="size-3 shrink-0 animate-spin" />
|
||||
{{ t('actionbar.applying') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -278,6 +278,12 @@ export default {
|
||||
positioningMode: 'Positioning Mode',
|
||||
sendInterval: 'Send Interval',
|
||||
showCursor: 'Show Cursor',
|
||||
mouseJiggler: 'Mouse jiggler',
|
||||
mouseJigglerHelp: 'Gently moves the mouse while idle to help prevent the target from sleeping or turning off its display. Pauses during input or while keys or buttons are held. Keeps working after closing this page and remembers settings after restart.',
|
||||
jigglerInterval: 'Jiggle interval',
|
||||
jigglerSeconds: 'sec',
|
||||
jigglerIntervalInvalid: 'Enter a whole number between 10 and 3600 seconds.',
|
||||
jigglerNoMouse: 'Enable a mouse interface in HID device settings first.',
|
||||
backend: 'Backend',
|
||||
devicePath: 'Device Path',
|
||||
baudrate: 'Baudrate',
|
||||
|
||||
@@ -278,6 +278,12 @@ export default {
|
||||
positioningMode: '定位模式',
|
||||
sendInterval: '发送间隔',
|
||||
showCursor: '显示指针',
|
||||
mouseJiggler: '鼠标抖动',
|
||||
mouseJigglerHelp: '空闲时自动小幅移动鼠标,帮助防止目标主机息屏或休眠。操作键鼠或按住按键时暂停。关闭网页后仍生效,重启后保留设置。',
|
||||
jigglerInterval: '抖动间隔',
|
||||
jigglerSeconds: '秒',
|
||||
jigglerIntervalInvalid: '请输入 10–3600 之间的整数秒数。',
|
||||
jigglerNoMouse: '请先在 HID 设备设置中启用鼠标接口。',
|
||||
backend: '后端类型',
|
||||
devicePath: '设备路径',
|
||||
baudrate: '波特率',
|
||||
|
||||
@@ -61,7 +61,13 @@ export interface Ch9329DescriptorConfig {
|
||||
serial_number?: string;
|
||||
}
|
||||
|
||||
export interface MouseJigglerConfig {
|
||||
enabled: boolean;
|
||||
interval_secs: number;
|
||||
}
|
||||
|
||||
export interface HidConfig {
|
||||
jiggler: MouseJigglerConfig;
|
||||
bluetooth: BluetoothHidConfig;
|
||||
backend: HidBackend;
|
||||
otg_udc?: string;
|
||||
@@ -503,7 +509,13 @@ export interface OtgHidFunctionsUpdate {
|
||||
consumer?: boolean;
|
||||
}
|
||||
|
||||
export interface MouseJigglerConfigUpdate {
|
||||
enabled?: boolean;
|
||||
interval_secs?: number;
|
||||
}
|
||||
|
||||
export interface HidConfigUpdate {
|
||||
jiggler?: MouseJigglerConfigUpdate;
|
||||
bluetooth_reset_pairing?: boolean;
|
||||
bluetooth?: BluetoothHidConfig;
|
||||
backend?: HidBackend;
|
||||
|
||||
Reference in New Issue
Block a user