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,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover'
|
} 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 HelpTooltip from '@/components/HelpTooltip.vue'
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
import { HidBackend } from '@/types/generated'
|
import { HidBackend, OtgHidProfile } from '@/types/generated'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -64,18 +64,63 @@ watch(showCursor, (newValue, oldValue) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const buttonText = computed(() => t('actionbar.hidConfig'))
|
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() {
|
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'
|
const newMode = props.mouseMode === 'absolute' ? 'relative' : 'absolute'
|
||||||
emit('update:mouseMode', newMode)
|
emit('update:mouseMode', newMode)
|
||||||
|
|
||||||
// Update backend config
|
// Update backend config
|
||||||
|
hidSaving.value = true
|
||||||
configStore.updateHid({
|
configStore.updateHid({
|
||||||
mouse_absolute: newMode === 'absolute',
|
mouse_absolute: newMode === 'absolute',
|
||||||
}).catch(_e => {
|
}).catch(_e => {
|
||||||
console.info('[HidConfig] Failed to update mouse mode')
|
console.info('[HidConfig] Failed to update mouse mode')
|
||||||
toast.error(t('config.updateFailed'))
|
toast.error(t('config.updateFailed'))
|
||||||
|
}).finally(() => {
|
||||||
|
hidSaving.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +138,7 @@ watch(() => props.open, (open) => {
|
|||||||
if (!open) return
|
if (!open) return
|
||||||
mouseThrottle.value = loadMouseMoveSendIntervalFromStorage()
|
mouseThrottle.value = loadMouseMoveSendIntervalFromStorage()
|
||||||
showCursor.value = localStorage.getItem('hidShowCursor') !== 'false'
|
showCursor.value = localStorage.getItem('hidShowCursor') !== 'false'
|
||||||
void configStore.refreshHid().catch(() => undefined)
|
if (!hidSaving.value) void configStore.refreshHid().catch(() => undefined)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -136,7 +181,7 @@ watch(() => props.open, (open) => {
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
:variant="mouseMode === 'absolute' ? 'default' : 'outline'"
|
:variant="mouseMode === 'absolute' ? 'default' : 'outline'"
|
||||||
:disabled="configStore.hid?.backend === HidBackend.Bluetooth"
|
:disabled="hidControlsDisabled || configStore.hid?.backend === HidBackend.Bluetooth"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="flex-1 h-8 text-xs"
|
class="flex-1 h-8 text-xs"
|
||||||
@click="toggleMouseMode"
|
@click="toggleMouseMode"
|
||||||
@@ -146,6 +191,7 @@ watch(() => props.open, (open) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
:variant="mouseMode === 'relative' ? 'default' : 'outline'"
|
:variant="mouseMode === 'relative' ? 'default' : 'outline'"
|
||||||
|
:disabled="hidControlsDisabled || configStore.hid?.backend === HidBackend.Bluetooth"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="flex-1 h-8 text-xs"
|
class="flex-1 h-8 text-xs"
|
||||||
@click="toggleMouseMode"
|
@click="toggleMouseMode"
|
||||||
@@ -185,6 +231,45 @@ watch(() => props.open, (open) => {
|
|||||||
<Switch v-model="showCursor" />
|
<Switch v-model="showCursor" />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { computed, ref, watch, onUnmounted } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
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 { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
||||||
import { Cable, RefreshCw } from 'lucide-vue-next'
|
import { Cable, RefreshCw } from 'lucide-vue-next'
|
||||||
import { configApi } from '@/api'
|
import { configApi } from '@/api'
|
||||||
@@ -13,6 +14,7 @@ import HidWiringDiagram from './HidWiringDiagram.vue'
|
|||||||
const props = defineProps<{ modelValue: HidSelection; locked?: boolean }>()
|
const props = defineProps<{ modelValue: HidSelection; locked?: boolean }>()
|
||||||
const emit = defineEmits<{ 'update:modelValue': [HidSelection]; valid: [boolean] }>()
|
const emit = defineEmits<{ 'update:modelValue': [HidSelection]; valid: [boolean] }>()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const EMPTY_SELECT_VALUE = '__one-kvm-empty-select-value__'
|
||||||
const options = ref<Array<{ value: string; label: string }>>([])
|
const options = ref<Array<{ value: string; label: string }>>([])
|
||||||
const loading = ref(false), error = ref(''), missing = ref('')
|
const loading = ref(false), error = ref(''), missing = ref('')
|
||||||
let generation = 0
|
let generation = 0
|
||||||
@@ -83,23 +85,54 @@ onUnmounted(() => generation++)
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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 })">
|
<Select
|
||||||
<option v-for="driver in drivers" :key="driver" :value="driver">{{ t(`hidGuide.driver_${driver}`) }}</option>
|
:model-value="modelValue.backend"
|
||||||
</select>
|
@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>
|
</div>
|
||||||
<p v-if="modelValue.backend === 'none'" class="text-sm text-muted-foreground">{{ t('hidGuide.disabledHelp') }}</p>
|
<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">
|
<label v-if="modelValue.backend !== 'none'" class="block space-y-1 text-sm">
|
||||||
<span>{{ t(`hidGuide.device_${modelValue.backend}`) }}</span>
|
<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)">
|
<Select
|
||||||
<option value="" disabled>{{ t('hidGuide.selectDevice') }}</option>
|
:model-value="selected || EMPTY_SELECT_VALUE"
|
||||||
<option v-for="option in options" :key="option.value" :value="option.value">{{ option.label }}</option>
|
:disabled="loading"
|
||||||
</select>
|
@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>
|
||||||
<label v-if="modelValue.backend === 'ch9329'" class="block space-y-1 text-sm">
|
<label v-if="modelValue.backend === 'ch9329'" class="block space-y-1 text-sm">
|
||||||
<span>{{ t('actionbar.baudrate') }}</span>
|
<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) })">
|
<Select
|
||||||
<option v-for="rate in [9600, 19200, 38400, 57600, 115200]" :key="rate">{{ rate }}</option>
|
:model-value="String(modelValue.ch9329_baudrate)"
|
||||||
</select>
|
@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>
|
||||||
<label v-if="modelValue.backend === 'bluetooth'" class="block space-y-1 text-sm">
|
<label v-if="modelValue.backend === 'bluetooth'" class="block space-y-1 text-sm">
|
||||||
<span>{{ t('bluetoothHid.name') }}</span>
|
<span>{{ t('bluetoothHid.name') }}</span>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const props = defineProps<{
|
|||||||
resolution: string
|
resolution: string
|
||||||
fps: number | null
|
fps: number | null
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
|
resolutionFpsInline?: boolean
|
||||||
refreshing?: boolean
|
refreshing?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -98,6 +99,7 @@ const { t } = useI18n()
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :class="resolutionFpsInline ? 'grid grid-cols-2 gap-3' : 'space-y-4'">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label :class="compact ? 'text-xs text-muted-foreground' : undefined">{{ t('videoInput.resolution') }}</Label>
|
<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))">
|
<Select :model-value="resolution" @update:model-value="value => emit('update:resolution', String(value))">
|
||||||
@@ -125,5 +127,6 @@ const { t } = useI18n()
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ export default {
|
|||||||
multiSourceVideoParamsWarning: '{sources} are enabled. Changing video device and input parameters will interrupt the stream.',
|
multiSourceVideoParamsWarning: '{sources} are enabled. Changing video device and input parameters will interrupt the stream.',
|
||||||
hidConfig: 'HID',
|
hidConfig: 'HID',
|
||||||
mouseSettings: 'Mouse Settings',
|
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',
|
hidDeviceSettings: 'HID Device Settings',
|
||||||
positioningMode: 'Positioning Mode',
|
positioningMode: 'Positioning Mode',
|
||||||
sendInterval: 'Send Interval',
|
sendInterval: 'Send Interval',
|
||||||
@@ -821,8 +823,8 @@ export default {
|
|||||||
ch9329HybridMouse: 'Linux Absolute Mouse Compatibility',
|
ch9329HybridMouse: 'Linux Absolute Mouse Compatibility',
|
||||||
ch9329HybridMouseDesc: 'Keep absolute movement on absolute packets, but send buttons and wheel through relative packets',
|
ch9329HybridMouseDesc: 'Keep absolute movement on absolute packets, but send buttons and wheel through relative packets',
|
||||||
mouseMacosDrag: 'macOS Drag Compatibility',
|
mouseMacosDrag: 'macOS Drag Compatibility',
|
||||||
mouseMacosDragRequiresBoth: 'Enable both relative and absolute mouse interfaces before saving.',
|
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 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.',
|
mouseMacosDragDesc: 'Experimental workaround for interrupted drags in macOS absolute mouse mode',
|
||||||
ch9329Descriptor: 'CH9329 USB Device Descriptor',
|
ch9329Descriptor: 'CH9329 USB Device Descriptor',
|
||||||
ch9329DescriptorDesc: 'Read USB identification fields from the CH9329 chip before editing',
|
ch9329DescriptorDesc: 'Read USB identification fields from the CH9329 chip before editing',
|
||||||
ch9329DescriptorLoading: 'Reading CH9329 descriptor...',
|
ch9329DescriptorLoading: 'Reading CH9329 descriptor...',
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ export default {
|
|||||||
multiSourceVideoParamsWarning: '{sources} 已启用,修改视频设备和输入参数将导致流中断',
|
multiSourceVideoParamsWarning: '{sources} 已启用,修改视频设备和输入参数将导致流中断',
|
||||||
hidConfig: '鼠键配置',
|
hidConfig: '鼠键配置',
|
||||||
mouseSettings: '鼠标设置',
|
mouseSettings: '鼠标设置',
|
||||||
|
mouseCompatibility: '鼠标兼容性',
|
||||||
|
macosDragRequiresBoth: '请先在系统设置 → HID 设置中启用并保存相对鼠标和绝对鼠标接口。',
|
||||||
hidDeviceSettings: 'HID 设备设置',
|
hidDeviceSettings: 'HID 设备设置',
|
||||||
positioningMode: '定位模式',
|
positioningMode: '定位模式',
|
||||||
sendInterval: '发送间隔',
|
sendInterval: '发送间隔',
|
||||||
@@ -820,8 +822,8 @@ export default {
|
|||||||
ch9329HybridMouse: 'Linux 绝对鼠标兼容模式',
|
ch9329HybridMouse: 'Linux 绝对鼠标兼容模式',
|
||||||
ch9329HybridMouseDesc: '绝对移动仍使用绝对鼠标包,点击和滚轮改用相对鼠标包发送',
|
ch9329HybridMouseDesc: '绝对移动仍使用绝对鼠标包,点击和滚轮改用相对鼠标包发送',
|
||||||
mouseMacosDrag: 'macOS 拖拽兼容模式',
|
mouseMacosDrag: 'macOS 拖拽兼容模式',
|
||||||
mouseMacosDragRequiresBoth: '请同时启用相对鼠标和绝对鼠标接口后保存。',
|
mouseMacosDragRequiresBoth: 'macOS 拖拽兼容模式需要同时启用相对鼠标和绝对鼠标接口。请保留两个接口,或先在控制台鼠键配置中关闭该模式。',
|
||||||
mouseMacosDragDesc: '实验性缓解绝对鼠标模式下的拖拽中断,适用于 OTG / CH9329。OTG 需同时启用两种鼠标接口。移动速度和落点可能受 macOS 设置影响;本版本尚未进行 Mac 实机验证。',
|
mouseMacosDragDesc: '实验性缓解 macOS 绝对鼠标模式下的拖拽中断',
|
||||||
ch9329Descriptor: 'CH9329 USB 设备描述符',
|
ch9329Descriptor: 'CH9329 USB 设备描述符',
|
||||||
ch9329DescriptorDesc: '先从 CH9329 芯片读取 USB 标识信息,读取成功后再修改',
|
ch9329DescriptorDesc: '先从 CH9329 芯片读取 USB 标识信息,读取成功后再修改',
|
||||||
ch9329DescriptorLoading: '正在读取 CH9329 描述符...',
|
ch9329DescriptorLoading: '正在读取 CH9329 描述符...',
|
||||||
|
|||||||
@@ -691,8 +691,6 @@ const config = ref({
|
|||||||
consumer: true,
|
consumer: true,
|
||||||
} as OtgHidFunctions,
|
} as OtgHidFunctions,
|
||||||
hid_otg_keyboard_leds: false,
|
hid_otg_keyboard_leds: false,
|
||||||
hid_ch9329_hybrid_mouse: false,
|
|
||||||
hid_mouse_macos_drag: false,
|
|
||||||
msd_enabled: false,
|
msd_enabled: false,
|
||||||
msd_dir: '',
|
msd_dir: '',
|
||||||
msd_flash_inquiry_string: 'One-KVM Virtual Flash',
|
msd_flash_inquiry_string: 'One-KVM Virtual Flash',
|
||||||
@@ -1175,10 +1173,14 @@ const isCh9329DescriptorDirty = computed(() => {
|
|||||||
|| current.serialNumber !== baseline.serialNumber
|
|| 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(() =>
|
const isHidSettingsValid = computed(() =>
|
||||||
isHidFunctionSelectionValid.value
|
isHidFunctionSelectionValid.value
|
||||||
&& !(config.value.hid_backend === 'otg' && config.value.hid_mouse_macos_drag
|
&& !hasMacosDragInterfaceConflict.value
|
||||||
&& (!effectiveOtgFunctions.value.mouse_relative || !effectiveOtgFunctions.value.mouse_absolute))
|
|
||||||
&& isCh9329DescriptorValid.value
|
&& isCh9329DescriptorValid.value
|
||||||
&& areMsdInquiryStringsValid.value
|
&& areMsdInquiryStringsValid.value
|
||||||
)
|
)
|
||||||
@@ -1456,13 +1458,7 @@ async function saveConfig() {
|
|||||||
if (!isHidSettingsValid.value) {
|
if (!isHidSettingsValid.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const hidUpdate: HidConfigUpdate = configStore.hid?.backend === 'ch9329'
|
const hidUpdate: HidConfigUpdate = {}
|
||||||
? {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if (config.value.hid_backend === 'ch9329' && isCh9329DescriptorDirty.value) {
|
if (config.value.hid_backend === 'ch9329' && isCh9329DescriptorDirty.value) {
|
||||||
hidUpdate.ch9329_descriptor = {
|
hidUpdate.ch9329_descriptor = {
|
||||||
vendor_id: parseInt(ch9329VendorIdHex.value, 16) || 0x1a86,
|
vendor_id: parseInt(ch9329VendorIdHex.value, 16) || 0x1a86,
|
||||||
@@ -1532,7 +1528,7 @@ async function saveConfig() {
|
|||||||
const hidFeatureBaseline = ref('')
|
const hidFeatureBaseline = ref('')
|
||||||
function hidFeatureSnapshot() {
|
function hidFeatureSnapshot() {
|
||||||
return JSON.stringify({
|
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],
|
descriptor: [otgVendorIdHex.value, otgProductIdHex.value, otgManufacturer.value, otgProduct.value, otgSerialNumber.value],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1573,8 +1569,6 @@ async function loadConfig() {
|
|||||||
consumer: hid.otg_functions?.consumer ?? true,
|
consumer: hid.otg_functions?.consumer ?? true,
|
||||||
} as OtgHidFunctions,
|
} as OtgHidFunctions,
|
||||||
hid_otg_keyboard_leds: hid.otg_keyboard_leds ?? false,
|
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_enabled: msd.enabled || false,
|
||||||
msd_dir: msd.msd_dir || '',
|
msd_dir: msd.msd_dir || '',
|
||||||
msd_flash_inquiry_string: msd.flash_inquiry_string || 'One-KVM Virtual Flash',
|
msd_flash_inquiry_string: msd.flash_inquiry_string || 'One-KVM Virtual Flash',
|
||||||
@@ -3215,33 +3209,9 @@ watch(isWindows, () => {
|
|||||||
{{ t('settings.ch9329DescriptorWarning') }}
|
{{ t('settings.ch9329DescriptorWarning') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|
||||||
<!-- OTG Descriptor Settings -->
|
<!-- 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'">
|
<template v-if="config.hid_backend === 'otg'">
|
||||||
<Separator class="my-4" />
|
<Separator class="my-4" />
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -3344,6 +3314,7 @@ watch(isWindows, () => {
|
|||||||
</div>
|
</div>
|
||||||
<Switch v-model="config.hid_otg_functions.mouse_absolute" />
|
<Switch v-model="config.hid_otg_functions.mouse_absolute" />
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="hasMacosDragInterfaceConflict" class="text-xs text-warning">{{ t('settings.mouseMacosDragRequiresBoth') }}</p>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<Label>{{ t('settings.uacMic') }}</Label>
|
<Label>{{ t('settings.uacMic') }}</Label>
|
||||||
@@ -5414,6 +5385,10 @@ watch(isWindows, () => {
|
|||||||
<AlertTriangle class="size-3.5 shrink-0" />
|
<AlertTriangle class="size-3.5 shrink-0" />
|
||||||
<span class="truncate">{{ t('settings.otgFunctionMinWarning') }}</span>
|
<span class="truncate">{{ t('settings.otgFunctionMinWarning') }}</span>
|
||||||
</p>
|
</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">
|
<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" />
|
<AlertTriangle class="size-3.5 shrink-0" />
|
||||||
<span class="truncate">{{ t('settings.ch9329StringLengthWarning') }}</span>
|
<span class="truncate">{{ t('settings.ch9329StringLengthWarning') }}</span>
|
||||||
|
|||||||
@@ -603,6 +603,7 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
|||||||
:format="videoFormat"
|
:format="videoFormat"
|
||||||
:resolution="videoResolution"
|
:resolution="videoResolution"
|
||||||
:fps="videoFps"
|
:fps="videoFps"
|
||||||
|
resolution-fps-inline
|
||||||
:refreshing="refreshingInputStatus"
|
:refreshing="refreshingInputStatus"
|
||||||
@update:format="videoFormat = $event"
|
@update:format="videoFormat = $event"
|
||||||
@update:resolution="videoResolution = $event"
|
@update:resolution="videoResolution = $event"
|
||||||
|
|||||||
Reference in New Issue
Block a user