mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-10-27 16:00:49 +08:00
fix(web): 优化 HID 兼容配置入口并调整引导布局
This commit is contained in:
@@ -13,10 +13,10 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
import { MousePointer, Move } from 'lucide-vue-next'
|
||||
import { MousePointer, Move, Loader2 } from 'lucide-vue-next'
|
||||
import HelpTooltip from '@/components/HelpTooltip.vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { HidBackend } from '@/types/generated'
|
||||
import { HidBackend, OtgHidProfile } from '@/types/generated'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
@@ -64,18 +64,63 @@ watch(showCursor, (newValue, oldValue) => {
|
||||
})
|
||||
|
||||
const buttonText = computed(() => t('actionbar.hidConfig'))
|
||||
const hidSaving = ref(false)
|
||||
const hidControlsDisabled = computed(() => hidSaving.value || configStore.hidLoading || !configStore.hid)
|
||||
const supportsLinuxCompatibility = computed(() => configStore.hid?.backend === HidBackend.Ch9329)
|
||||
const supportsMacosCompatibility = computed(() =>
|
||||
supportsLinuxCompatibility.value || configStore.hid?.backend === HidBackend.Otg
|
||||
)
|
||||
const macosDragRequiresInterfaces = computed(() => {
|
||||
const hid = configStore.hid
|
||||
if (hid?.backend !== HidBackend.Otg) return false
|
||||
switch (hid.otg_profile) {
|
||||
case OtgHidProfile.LegacyKeyboard:
|
||||
case OtgHidProfile.LegacyMouseRelative:
|
||||
return true
|
||||
case OtgHidProfile.Custom:
|
||||
return hid.otg_functions?.mouse_relative === false || hid.otg_functions?.mouse_absolute === false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
type MouseCompatibilityOption = 'ch9329_hybrid_mouse' | 'mouse_macos_drag'
|
||||
|
||||
async function updateMouseCompatibility(option: MouseCompatibilityOption, enabled: boolean) {
|
||||
if (hidControlsDisabled.value || !supportsMacosCompatibility.value) return
|
||||
if (option === 'ch9329_hybrid_mouse' && !supportsLinuxCompatibility.value) return
|
||||
if (option === 'mouse_macos_drag' && enabled && macosDragRequiresInterfaces.value) return
|
||||
if ((configStore.hid?.[option] ?? false) === enabled) return
|
||||
|
||||
hidSaving.value = true
|
||||
try {
|
||||
// Keep both preferences: the backend gives macOS priority when both are enabled.
|
||||
await configStore.updateHid({ [option]: enabled })
|
||||
} catch (error) {
|
||||
toast.error(t('config.updateFailed'), {
|
||||
description: error instanceof Error ? error.message : undefined,
|
||||
})
|
||||
// Read back persisted state, including when the response was lost after applying.
|
||||
await configStore.refreshHid().catch(() => undefined)
|
||||
} finally {
|
||||
hidSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMouseMode() {
|
||||
if (configStore.hid?.backend === HidBackend.Bluetooth) return
|
||||
if (hidControlsDisabled.value || configStore.hid?.backend === HidBackend.Bluetooth) return
|
||||
const newMode = props.mouseMode === 'absolute' ? 'relative' : 'absolute'
|
||||
emit('update:mouseMode', newMode)
|
||||
|
||||
// Update backend config
|
||||
hidSaving.value = true
|
||||
configStore.updateHid({
|
||||
mouse_absolute: newMode === 'absolute',
|
||||
}).catch(_e => {
|
||||
console.info('[HidConfig] Failed to update mouse mode')
|
||||
toast.error(t('config.updateFailed'))
|
||||
}).finally(() => {
|
||||
hidSaving.value = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,7 +138,7 @@ watch(() => props.open, (open) => {
|
||||
if (!open) return
|
||||
mouseThrottle.value = loadMouseMoveSendIntervalFromStorage()
|
||||
showCursor.value = localStorage.getItem('hidShowCursor') !== 'false'
|
||||
void configStore.refreshHid().catch(() => undefined)
|
||||
if (!hidSaving.value) void configStore.refreshHid().catch(() => undefined)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -136,7 +181,7 @@ watch(() => props.open, (open) => {
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
:variant="mouseMode === 'absolute' ? 'default' : 'outline'"
|
||||
:disabled="configStore.hid?.backend === HidBackend.Bluetooth"
|
||||
:disabled="hidControlsDisabled || configStore.hid?.backend === HidBackend.Bluetooth"
|
||||
size="sm"
|
||||
class="flex-1 h-8 text-xs"
|
||||
@click="toggleMouseMode"
|
||||
@@ -146,6 +191,7 @@ watch(() => props.open, (open) => {
|
||||
</Button>
|
||||
<Button
|
||||
:variant="mouseMode === 'relative' ? 'default' : 'outline'"
|
||||
:disabled="hidControlsDisabled || configStore.hid?.backend === HidBackend.Bluetooth"
|
||||
size="sm"
|
||||
class="flex-1 h-8 text-xs"
|
||||
@click="toggleMouseMode"
|
||||
@@ -185,6 +231,45 @@ watch(() => props.open, (open) => {
|
||||
<Switch v-model="showCursor" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="supportsMacosCompatibility">
|
||||
<Separator />
|
||||
<div class="space-y-3" :aria-busy="hidSaving">
|
||||
<h5 class="text-xs font-medium text-muted-foreground">{{ t('actionbar.mouseCompatibility') }}</h5>
|
||||
|
||||
<div v-if="supportsLinuxCompatibility" class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-1">
|
||||
<Label for="hid-linux-compatibility" class="text-xs">{{ t('settings.ch9329HybridMouse') }}</Label>
|
||||
<HelpTooltip :content="t('settings.ch9329HybridMouseDesc')" icon-size="sm" />
|
||||
</div>
|
||||
<Switch
|
||||
id="hid-linux-compatibility"
|
||||
:model-value="configStore.hid?.ch9329_hybrid_mouse ?? false"
|
||||
:disabled="hidControlsDisabled"
|
||||
@update:model-value="updateMouseCompatibility('ch9329_hybrid_mouse', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-1">
|
||||
<Label for="hid-macos-compatibility" class="text-xs">{{ t('settings.mouseMacosDrag') }}</Label>
|
||||
<HelpTooltip :content="t('settings.mouseMacosDragDesc')" icon-size="sm" />
|
||||
</div>
|
||||
<Switch
|
||||
id="hid-macos-compatibility"
|
||||
:model-value="configStore.hid?.mouse_macos_drag ?? false"
|
||||
:disabled="hidControlsDisabled || (macosDragRequiresInterfaces && !configStore.hid?.mouse_macos_drag)"
|
||||
@update:model-value="updateMouseCompatibility('mouse_macos_drag', $event)"
|
||||
/>
|
||||
</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>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, ref, watch, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||
import { Cable, RefreshCw } from 'lucide-vue-next'
|
||||
import { configApi } from '@/api'
|
||||
@@ -13,6 +14,7 @@ import HidWiringDiagram from './HidWiringDiagram.vue'
|
||||
const props = defineProps<{ modelValue: HidSelection; locked?: boolean }>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [HidSelection]; valid: [boolean] }>()
|
||||
const { t } = useI18n()
|
||||
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||
const options = ref<Array<{ value: string; label: string }>>([])
|
||||
const loading = ref(false), error = ref(''), missing = ref('')
|
||||
let generation = 0
|
||||
@@ -83,23 +85,54 @@ onUnmounted(() => generation++)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<select id="hid-driver" class="w-full rounded-md border bg-background px-3 py-2" :value="modelValue.backend" @change="emit('update:modelValue', { ...modelValue, backend: ($event.target as HTMLSelectElement).value as Driver })">
|
||||
<option v-for="driver in drivers" :key="driver" :value="driver">{{ t(`hidGuide.driver_${driver}`) }}</option>
|
||||
</select>
|
||||
<Select
|
||||
:model-value="modelValue.backend"
|
||||
@update:model-value="value => emit('update:modelValue', { ...modelValue, backend: String(value) as Driver })"
|
||||
>
|
||||
<SelectTrigger id="hid-driver" class="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="driver in drivers" :key="driver" :value="driver">
|
||||
{{ t(`hidGuide.driver_${driver}`) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<p v-if="modelValue.backend === 'none'" class="text-sm text-muted-foreground">{{ t('hidGuide.disabledHelp') }}</p>
|
||||
<label v-if="modelValue.backend !== 'none'" class="block space-y-1 text-sm">
|
||||
<span>{{ t(`hidGuide.device_${modelValue.backend}`) }}</span>
|
||||
<select class="w-full rounded-md border bg-background px-3 py-2" :value="selected" :disabled="loading" @change="device(($event.target as HTMLSelectElement).value)">
|
||||
<option value="" disabled>{{ t('hidGuide.selectDevice') }}</option>
|
||||
<option v-for="option in options" :key="option.value" :value="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
<Select
|
||||
:model-value="selected || EMPTY_SELECT_VALUE"
|
||||
:disabled="loading"
|
||||
@update:model-value="value => device(value === EMPTY_SELECT_VALUE ? '' : String(value))"
|
||||
>
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue :placeholder="t('hidGuide.selectDevice')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="EMPTY_SELECT_VALUE" disabled>{{ t('hidGuide.selectDevice') }}</SelectItem>
|
||||
<SelectItem v-for="option in options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</label>
|
||||
<label v-if="modelValue.backend === 'ch9329'" class="block space-y-1 text-sm">
|
||||
<span>{{ t('actionbar.baudrate') }}</span>
|
||||
<select class="w-full rounded-md border bg-background px-3 py-2" :value="modelValue.ch9329_baudrate" @change="emit('update:modelValue', { ...modelValue, ch9329_baudrate: Number(($event.target as HTMLSelectElement).value) })">
|
||||
<option v-for="rate in [9600, 19200, 38400, 57600, 115200]" :key="rate">{{ rate }}</option>
|
||||
</select>
|
||||
<Select
|
||||
:model-value="String(modelValue.ch9329_baudrate)"
|
||||
@update:model-value="value => emit('update:modelValue', { ...modelValue, ch9329_baudrate: Number(value) })"
|
||||
>
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="rate in [9600, 19200, 38400, 57600, 115200]" :key="rate" :value="String(rate)">
|
||||
{{ rate }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</label>
|
||||
<label v-if="modelValue.backend === 'bluetooth'" class="block space-y-1 text-sm">
|
||||
<span>{{ t('bluetoothHid.name') }}</span>
|
||||
|
||||
@@ -16,6 +16,7 @@ const props = defineProps<{
|
||||
resolution: string
|
||||
fps: number | null
|
||||
compact?: boolean
|
||||
resolutionFpsInline?: boolean
|
||||
refreshing?: boolean
|
||||
}>()
|
||||
|
||||
@@ -98,32 +99,34 @@ const { t } = useI18n()
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label :class="compact ? 'text-xs text-muted-foreground' : undefined">{{ t('videoInput.resolution') }}</Label>
|
||||
<Select :model-value="resolution" @update:model-value="value => emit('update:resolution', String(value))">
|
||||
<SelectTrigger :size="compact ? 'sm' : 'default'" class="w-full" :class="compact ? 'text-xs' : undefined">
|
||||
<SelectValue :placeholder="t('videoInput.selectResolution')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in resolutions" :key="`${item.width}x${item.height}`" :value="`${item.width}x${item.height}`">
|
||||
{{ item.width }}x{{ item.height }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div :class="resolutionFpsInline ? 'grid grid-cols-2 gap-3' : 'space-y-4'">
|
||||
<div class="space-y-2">
|
||||
<Label :class="compact ? 'text-xs text-muted-foreground' : undefined">{{ t('videoInput.resolution') }}</Label>
|
||||
<Select :model-value="resolution" @update:model-value="value => emit('update:resolution', String(value))">
|
||||
<SelectTrigger :size="compact ? 'sm' : 'default'" class="w-full" :class="compact ? 'text-xs' : undefined">
|
||||
<SelectValue :placeholder="t('videoInput.selectResolution')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in resolutions" :key="`${item.width}x${item.height}`" :value="`${item.width}x${item.height}`">
|
||||
{{ item.width }}x{{ item.height }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label :class="compact ? 'text-xs text-muted-foreground' : undefined">{{ t('videoInput.frameRate') }}</Label>
|
||||
<Select :model-value="fps === null ? '' : String(fps)" @update:model-value="value => emit('update:fps', Number(value))">
|
||||
<SelectTrigger :size="compact ? 'sm' : 'default'" class="w-full" :class="compact ? 'text-xs' : undefined">
|
||||
<SelectValue :placeholder="t('videoInput.selectFps')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in fpsOptions" :key="item" :value="String(item)">
|
||||
{{ formatFpsLabel(item) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="space-y-2">
|
||||
<Label :class="compact ? 'text-xs text-muted-foreground' : undefined">{{ t('videoInput.frameRate') }}</Label>
|
||||
<Select :model-value="fps === null ? '' : String(fps)" @update:model-value="value => emit('update:fps', Number(value))">
|
||||
<SelectTrigger :size="compact ? 'sm' : 'default'" class="w-full" :class="compact ? 'text-xs' : undefined">
|
||||
<SelectValue :placeholder="t('videoInput.selectFps')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="item in fpsOptions" :key="item" :value="String(item)">
|
||||
{{ formatFpsLabel(item) }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -259,6 +259,8 @@ export default {
|
||||
multiSourceVideoParamsWarning: '{sources} are enabled. Changing video device and input parameters will interrupt the stream.',
|
||||
hidConfig: 'HID',
|
||||
mouseSettings: 'Mouse Settings',
|
||||
mouseCompatibility: 'Mouse Compatibility',
|
||||
macosDragRequiresBoth: 'Enable and save both relative and absolute mouse interfaces in Settings → HID Settings first.',
|
||||
hidDeviceSettings: 'HID Device Settings',
|
||||
positioningMode: 'Positioning Mode',
|
||||
sendInterval: 'Send Interval',
|
||||
@@ -821,8 +823,8 @@ export default {
|
||||
ch9329HybridMouse: 'Linux Absolute Mouse Compatibility',
|
||||
ch9329HybridMouseDesc: 'Keep absolute movement on absolute packets, but send buttons and wheel through relative packets',
|
||||
mouseMacosDrag: 'macOS Drag Compatibility',
|
||||
mouseMacosDragRequiresBoth: 'Enable both relative and absolute mouse interfaces before saving.',
|
||||
mouseMacosDragDesc: 'Experimental workaround for interrupted drags in absolute mouse mode (OTG / CH9329). OTG requires both mouse interfaces. Tracking speed and drop position may vary with macOS settings; this version has not been tested on a Mac.',
|
||||
mouseMacosDragRequiresBoth: 'macOS drag compatibility requires both relative and absolute mouse interfaces. Keep both enabled, or turn off this mode in the console HID menu first.',
|
||||
mouseMacosDragDesc: 'Experimental workaround for interrupted drags in macOS absolute mouse mode',
|
||||
ch9329Descriptor: 'CH9329 USB Device Descriptor',
|
||||
ch9329DescriptorDesc: 'Read USB identification fields from the CH9329 chip before editing',
|
||||
ch9329DescriptorLoading: 'Reading CH9329 descriptor...',
|
||||
|
||||
@@ -259,6 +259,8 @@ export default {
|
||||
multiSourceVideoParamsWarning: '{sources} 已启用,修改视频设备和输入参数将导致流中断',
|
||||
hidConfig: '鼠键配置',
|
||||
mouseSettings: '鼠标设置',
|
||||
mouseCompatibility: '鼠标兼容性',
|
||||
macosDragRequiresBoth: '请先在系统设置 → HID 设置中启用并保存相对鼠标和绝对鼠标接口。',
|
||||
hidDeviceSettings: 'HID 设备设置',
|
||||
positioningMode: '定位模式',
|
||||
sendInterval: '发送间隔',
|
||||
@@ -820,8 +822,8 @@ export default {
|
||||
ch9329HybridMouse: 'Linux 绝对鼠标兼容模式',
|
||||
ch9329HybridMouseDesc: '绝对移动仍使用绝对鼠标包,点击和滚轮改用相对鼠标包发送',
|
||||
mouseMacosDrag: 'macOS 拖拽兼容模式',
|
||||
mouseMacosDragRequiresBoth: '请同时启用相对鼠标和绝对鼠标接口后保存。',
|
||||
mouseMacosDragDesc: '实验性缓解绝对鼠标模式下的拖拽中断,适用于 OTG / CH9329。OTG 需同时启用两种鼠标接口。移动速度和落点可能受 macOS 设置影响;本版本尚未进行 Mac 实机验证。',
|
||||
mouseMacosDragRequiresBoth: 'macOS 拖拽兼容模式需要同时启用相对鼠标和绝对鼠标接口。请保留两个接口,或先在控制台鼠键配置中关闭该模式。',
|
||||
mouseMacosDragDesc: '实验性缓解 macOS 绝对鼠标模式下的拖拽中断',
|
||||
ch9329Descriptor: 'CH9329 USB 设备描述符',
|
||||
ch9329DescriptorDesc: '先从 CH9329 芯片读取 USB 标识信息,读取成功后再修改',
|
||||
ch9329DescriptorLoading: '正在读取 CH9329 描述符...',
|
||||
|
||||
@@ -691,8 +691,6 @@ const config = ref({
|
||||
consumer: true,
|
||||
} as OtgHidFunctions,
|
||||
hid_otg_keyboard_leds: false,
|
||||
hid_ch9329_hybrid_mouse: false,
|
||||
hid_mouse_macos_drag: false,
|
||||
msd_enabled: false,
|
||||
msd_dir: '',
|
||||
msd_flash_inquiry_string: 'One-KVM Virtual Flash',
|
||||
@@ -1175,10 +1173,14 @@ const isCh9329DescriptorDirty = computed(() => {
|
||||
|| current.serialNumber !== baseline.serialNumber
|
||||
})
|
||||
|
||||
const hasMacosDragInterfaceConflict = computed(() =>
|
||||
config.value.hid_backend === 'otg' && configStore.hid?.mouse_macos_drag
|
||||
&& (!effectiveOtgFunctions.value.mouse_relative || !effectiveOtgFunctions.value.mouse_absolute)
|
||||
)
|
||||
|
||||
const isHidSettingsValid = computed(() =>
|
||||
isHidFunctionSelectionValid.value
|
||||
&& !(config.value.hid_backend === 'otg' && config.value.hid_mouse_macos_drag
|
||||
&& (!effectiveOtgFunctions.value.mouse_relative || !effectiveOtgFunctions.value.mouse_absolute))
|
||||
&& !hasMacosDragInterfaceConflict.value
|
||||
&& isCh9329DescriptorValid.value
|
||||
&& areMsdInquiryStringsValid.value
|
||||
)
|
||||
@@ -1456,13 +1458,7 @@ async function saveConfig() {
|
||||
if (!isHidSettingsValid.value) {
|
||||
return
|
||||
}
|
||||
const hidUpdate: HidConfigUpdate = configStore.hid?.backend === 'ch9329'
|
||||
? {
|
||||
ch9329_hybrid_mouse: config.value.hid_ch9329_hybrid_mouse,
|
||||
} : {}
|
||||
if (['otg', 'ch9329'].includes(config.value.hid_backend)) {
|
||||
hidUpdate.mouse_macos_drag = config.value.hid_mouse_macos_drag
|
||||
}
|
||||
const hidUpdate: HidConfigUpdate = {}
|
||||
if (config.value.hid_backend === 'ch9329' && isCh9329DescriptorDirty.value) {
|
||||
hidUpdate.ch9329_descriptor = {
|
||||
vendor_id: parseInt(ch9329VendorIdHex.value, 16) || 0x1a86,
|
||||
@@ -1532,7 +1528,7 @@ async function saveConfig() {
|
||||
const hidFeatureBaseline = ref('')
|
||||
function hidFeatureSnapshot() {
|
||||
return JSON.stringify({
|
||||
fields: Object.fromEntries(Object.entries(config.value).filter(([key]) => key.startsWith('msd_') || key.startsWith('otg_network_') || key.startsWith('uac_') || ['hid_otg_functions', 'hid_otg_keyboard_leds', 'hid_ch9329_hybrid_mouse', 'hid_mouse_macos_drag'].includes(key))),
|
||||
fields: Object.fromEntries(Object.entries(config.value).filter(([key]) => key.startsWith('msd_') || key.startsWith('otg_network_') || key.startsWith('uac_') || ['hid_otg_functions', 'hid_otg_keyboard_leds'].includes(key))),
|
||||
descriptor: [otgVendorIdHex.value, otgProductIdHex.value, otgManufacturer.value, otgProduct.value, otgSerialNumber.value],
|
||||
})
|
||||
}
|
||||
@@ -1573,8 +1569,6 @@ async function loadConfig() {
|
||||
consumer: hid.otg_functions?.consumer ?? true,
|
||||
} as OtgHidFunctions,
|
||||
hid_otg_keyboard_leds: hid.otg_keyboard_leds ?? false,
|
||||
hid_ch9329_hybrid_mouse: hid.ch9329_hybrid_mouse ?? false,
|
||||
hid_mouse_macos_drag: hid.mouse_macos_drag ?? false,
|
||||
msd_enabled: msd.enabled || false,
|
||||
msd_dir: msd.msd_dir || '',
|
||||
msd_flash_inquiry_string: msd.flash_inquiry_string || 'One-KVM Virtual Flash',
|
||||
@@ -3215,33 +3209,9 @@ watch(isWindows, () => {
|
||||
{{ t('settings.ch9329DescriptorWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
<Separator class="my-4" />
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium">{{ t('settings.ch9329Options') }}</h4>
|
||||
<p class="text-sm text-muted-foreground">{{ t('settings.ch9329OptionsDesc') }}</p>
|
||||
</div>
|
||||
<div class="space-y-3 rounded-md border border-border/60 p-3">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<Label>{{ t('settings.ch9329HybridMouse') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.ch9329HybridMouseDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_ch9329_hybrid_mouse" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- OTG Descriptor Settings -->
|
||||
<div v-if="['otg', 'ch9329'].includes(config.hid_backend)" class="flex items-center justify-between gap-4 rounded-md border border-border/60 p-3">
|
||||
<div>
|
||||
<Label>{{ t('settings.mouseMacosDrag') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.mouseMacosDragDesc') }}</p>
|
||||
<p v-if="config.hid_backend === 'otg' && config.hid_mouse_macos_drag && (!effectiveOtgFunctions.mouse_relative || !effectiveOtgFunctions.mouse_absolute)" class="text-xs text-warning">{{ t('settings.mouseMacosDragRequiresBoth') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_mouse_macos_drag" />
|
||||
</div>
|
||||
<template v-if="config.hid_backend === 'otg'">
|
||||
<Separator class="my-4" />
|
||||
<div class="space-y-4">
|
||||
@@ -3344,6 +3314,7 @@ watch(isWindows, () => {
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.mouse_absolute" />
|
||||
</div>
|
||||
<p v-if="hasMacosDragInterfaceConflict" class="text-xs text-warning">{{ t('settings.mouseMacosDragRequiresBoth') }}</p>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<Label>{{ t('settings.uacMic') }}</Label>
|
||||
@@ -5414,6 +5385,10 @@ watch(isWindows, () => {
|
||||
<AlertTriangle class="size-3.5 shrink-0" />
|
||||
<span class="truncate">{{ t('settings.otgFunctionMinWarning') }}</span>
|
||||
</p>
|
||||
<p v-else-if="activeSection === 'hid' && hasMacosDragInterfaceConflict" class="flex min-w-0 items-center gap-1.5 text-xs text-warning">
|
||||
<AlertTriangle class="size-3.5 shrink-0" />
|
||||
<span>{{ t('settings.mouseMacosDragRequiresBoth') }}</span>
|
||||
</p>
|
||||
<p v-else-if="activeSection === 'hid' && !isCh9329DescriptorValid" class="flex min-w-0 items-center gap-1.5 text-xs text-warning">
|
||||
<AlertTriangle class="size-3.5 shrink-0" />
|
||||
<span class="truncate">{{ t('settings.ch9329StringLengthWarning') }}</span>
|
||||
|
||||
@@ -603,6 +603,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
:format="videoFormat"
|
||||
:resolution="videoResolution"
|
||||
:fps="videoFps"
|
||||
resolution-fps-inline
|
||||
:refreshing="refreshingInputStatus"
|
||||
@update:format="videoFormat = $event"
|
||||
@update:resolution="videoResolution = $event"
|
||||
|
||||
Reference in New Issue
Block a user