mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-03-29 22:56:45 +08:00
refactor(otg): 简化运行时与设置逻辑
This commit is contained in:
@@ -86,6 +86,9 @@ export const systemApi = {
|
||||
hid_ch9329_baudrate?: number
|
||||
hid_otg_udc?: string
|
||||
hid_otg_profile?: string
|
||||
hid_otg_endpoint_budget?: string
|
||||
hid_otg_keyboard_leds?: boolean
|
||||
msd_enabled?: boolean
|
||||
encoder_backend?: string
|
||||
audio_device?: string
|
||||
ttyd_enabled?: boolean
|
||||
@@ -330,6 +333,14 @@ export const hidApi = {
|
||||
initialized: boolean
|
||||
online: boolean
|
||||
supports_absolute_mouse: boolean
|
||||
keyboard_leds_enabled: boolean
|
||||
led_state: {
|
||||
num_lock: boolean
|
||||
caps_lock: boolean
|
||||
scroll_lock: boolean
|
||||
compose: boolean
|
||||
kana: boolean
|
||||
}
|
||||
screen_resolution: [number, number] | null
|
||||
device: string | null
|
||||
error: string | null
|
||||
|
||||
@@ -7,6 +7,9 @@ import { cn } from '@/lib/utils'
|
||||
const props = defineProps<{
|
||||
pressedKeys?: CanonicalKey[]
|
||||
capsLock?: boolean
|
||||
numLock?: boolean
|
||||
scrollLock?: boolean
|
||||
keyboardLedEnabled?: boolean
|
||||
mousePosition?: { x: number; y: number }
|
||||
debugMode?: boolean
|
||||
compact?: boolean
|
||||
@@ -42,12 +45,21 @@ const keysDisplay = computed(() => {
|
||||
<!-- Compact mode for small screens -->
|
||||
<div v-if="compact" class="flex items-center justify-between text-xs px-2 py-0.5">
|
||||
<!-- LED indicator only in compact mode -->
|
||||
<div class="flex items-center gap-1">
|
||||
<div v-if="keyboardLedEnabled" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="capsLock"
|
||||
class="px-1.5 py-0.5 bg-primary/10 text-primary rounded text-[10px] font-medium"
|
||||
>C</span>
|
||||
<span v-else class="text-muted-foreground/40 text-[10px]">-</span>
|
||||
<span v-else class="text-muted-foreground/40 text-[10px]">C</span>
|
||||
<span
|
||||
:class="numLock ? 'px-1.5 py-0.5 bg-primary/10 text-primary rounded text-[10px] font-medium' : 'text-muted-foreground/40 text-[10px]'"
|
||||
>N</span>
|
||||
<span
|
||||
:class="scrollLock ? 'px-1.5 py-0.5 bg-primary/10 text-primary rounded text-[10px] font-medium' : 'text-muted-foreground/40 text-[10px]'"
|
||||
>S</span>
|
||||
</div>
|
||||
<div v-else class="text-[10px] text-muted-foreground/60">
|
||||
{{ t('infobar.keyboardLedUnavailable') }}
|
||||
</div>
|
||||
<!-- Keys in compact mode -->
|
||||
<div v-if="keysDisplay" class="text-[10px] text-muted-foreground truncate max-w-[150px]">
|
||||
@@ -72,16 +84,39 @@ const keysDisplay = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side: Caps Lock LED state -->
|
||||
<!-- Right side: Keyboard LED states -->
|
||||
<div class="flex items-center shrink-0">
|
||||
<div
|
||||
:class="cn(
|
||||
'px-2 py-1 select-none transition-colors',
|
||||
capsLock ? 'text-foreground font-medium bg-primary/5' : 'text-muted-foreground/40'
|
||||
)"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ t('infobar.caps') }}</span>
|
||||
<span class="sm:hidden">C</span>
|
||||
<template v-if="keyboardLedEnabled">
|
||||
<div
|
||||
:class="cn(
|
||||
'px-2 py-1 select-none transition-colors',
|
||||
capsLock ? 'text-foreground font-medium bg-primary/5' : 'text-muted-foreground/40'
|
||||
)"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ t('infobar.caps') }}</span>
|
||||
<span class="sm:hidden">C</span>
|
||||
</div>
|
||||
<div
|
||||
:class="cn(
|
||||
'px-2 py-1 select-none transition-colors',
|
||||
numLock ? 'text-foreground font-medium bg-primary/5' : 'text-muted-foreground/40'
|
||||
)"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ t('infobar.num') }}</span>
|
||||
<span class="sm:hidden">N</span>
|
||||
</div>
|
||||
<div
|
||||
:class="cn(
|
||||
'px-2 py-1 select-none transition-colors',
|
||||
scrollLock ? 'text-foreground font-medium bg-primary/5' : 'text-muted-foreground/40'
|
||||
)"
|
||||
>
|
||||
<span class="hidden sm:inline">{{ t('infobar.scroll') }}</span>
|
||||
<span class="sm:hidden">S</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="px-3 py-1 text-muted-foreground/60">
|
||||
{{ t('infobar.keyboardLedUnavailable') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,6 @@ export default {
|
||||
password: 'Password',
|
||||
enterUsername: 'Enter username',
|
||||
enterPassword: 'Enter password',
|
||||
loginPrompt: 'Enter your credentials to login',
|
||||
loginFailed: 'Login failed',
|
||||
invalidPassword: 'Invalid username or password',
|
||||
changePassword: 'Change Password',
|
||||
@@ -169,6 +168,7 @@ export default {
|
||||
caps: 'Caps',
|
||||
num: 'Num',
|
||||
scroll: 'Scroll',
|
||||
keyboardLedUnavailable: 'Keyboard LED status is disabled or unsupported',
|
||||
},
|
||||
paste: {
|
||||
title: 'Paste Text',
|
||||
@@ -270,7 +270,7 @@ export default {
|
||||
otgAdvanced: 'Advanced: OTG Preset',
|
||||
otgProfile: 'Initial HID Preset',
|
||||
otgProfileDesc: 'Choose the initial OTG HID preset. You can change this later in Settings.',
|
||||
otgLowEndpointHint: 'Detected low-endpoint UDC; multimedia keys will be disabled automatically.',
|
||||
otgLowEndpointHint: 'Detected low-endpoint UDC; Consumer Control Keyboard will be disabled automatically.',
|
||||
videoDeviceHelp: 'Select the video capture device for capturing the remote host display. Usually an HDMI capture card.',
|
||||
videoFormatHelp: 'MJPEG has best compatibility. H.264/H.265 uses less bandwidth but requires encoding support.',
|
||||
// Extensions
|
||||
@@ -651,28 +651,28 @@ export default {
|
||||
hidBackend: 'HID Backend',
|
||||
serialDevice: 'Serial Device',
|
||||
baudRate: 'Baud Rate',
|
||||
otgHidProfile: 'OTG HID Profile',
|
||||
otgHidProfile: 'OTG HID Functions',
|
||||
otgHidProfileDesc: 'Select which HID functions are exposed to the host',
|
||||
profile: 'Profile',
|
||||
otgProfileFull: 'Keyboard + relative mouse + absolute mouse + multimedia + MSD',
|
||||
otgProfileFullNoMsd: 'Keyboard + relative mouse + absolute mouse + multimedia (no MSD)',
|
||||
otgProfileFullNoConsumer: 'Keyboard + relative mouse + absolute mouse + MSD (no multimedia)',
|
||||
otgProfileFullNoConsumerNoMsd: 'Keyboard + relative mouse + absolute mouse (no multimedia, no MSD)',
|
||||
otgProfileLegacyKeyboard: 'Keyboard only',
|
||||
otgProfileLegacyMouseRelative: 'Relative mouse only',
|
||||
otgProfileCustom: 'Custom',
|
||||
otgEndpointBudget: 'Max Endpoints',
|
||||
otgEndpointBudgetUnlimited: 'Unlimited',
|
||||
otgEndpointBudgetHint: 'This is a hardware limit. If the OTG selection exceeds the real hardware endpoint count, OTG will fail.',
|
||||
otgEndpointUsage: 'Endpoint usage: {used} / {limit}',
|
||||
otgEndpointUsageUnlimited: 'Endpoint usage: {used} / unlimited',
|
||||
otgEndpointExceeded: 'The current OTG selection needs {used} endpoints, exceeding the limit {limit}.',
|
||||
otgFunctionKeyboard: 'Keyboard',
|
||||
otgFunctionKeyboardDesc: 'Standard HID keyboard device',
|
||||
otgKeyboardLeds: 'Keyboard LED Status',
|
||||
otgKeyboardLedsDesc: 'Enable Caps/Num/Scroll LED feedback from the host',
|
||||
otgFunctionMouseRelative: 'Relative Mouse',
|
||||
otgFunctionMouseRelativeDesc: 'Traditional mouse movement (HID boot mouse)',
|
||||
otgFunctionMouseAbsolute: 'Absolute Mouse',
|
||||
otgFunctionMouseAbsoluteDesc: 'Absolute positioning (touchscreen-like)',
|
||||
otgFunctionConsumer: 'Consumer Control',
|
||||
otgFunctionConsumerDesc: 'Media keys like volume/play/pause',
|
||||
otgFunctionConsumer: 'Consumer Control Keyboard',
|
||||
otgFunctionConsumerDesc: 'Consumer Control keys such as volume/play/pause',
|
||||
otgFunctionMsd: 'Mass Storage (MSD)',
|
||||
otgFunctionMsdDesc: 'Expose USB storage to the host',
|
||||
otgProfileWarning: 'Changing HID functions will reconnect the USB device',
|
||||
otgLowEndpointHint: 'Low-endpoint UDC detected; multimedia keys will be disabled automatically.',
|
||||
otgLowEndpointHint: 'Low-endpoint UDC detected; Consumer Control Keyboard will be disabled automatically.',
|
||||
otgFunctionMinWarning: 'Enable at least one HID function before saving',
|
||||
// OTG Descriptor
|
||||
otgDescriptor: 'USB Device Descriptor',
|
||||
@@ -799,7 +799,7 @@ export default {
|
||||
osWindows: 'Windows',
|
||||
osMac: 'Mac',
|
||||
osAndroid: 'Android',
|
||||
mediaKeys: 'Media Keys',
|
||||
mediaKeys: 'Consumer Control Keyboard',
|
||||
},
|
||||
config: {
|
||||
applied: 'Configuration applied',
|
||||
|
||||
@@ -61,7 +61,6 @@ export default {
|
||||
password: '密码',
|
||||
enterUsername: '请输入用户名',
|
||||
enterPassword: '请输入密码',
|
||||
loginPrompt: '请输入您的账号和密码',
|
||||
loginFailed: '登录失败',
|
||||
invalidPassword: '用户名或密码错误',
|
||||
changePassword: '修改密码',
|
||||
@@ -169,6 +168,7 @@ export default {
|
||||
caps: 'Caps',
|
||||
num: 'Num',
|
||||
scroll: 'Scroll',
|
||||
keyboardLedUnavailable: '键盘状态灯功能未开启或不支持',
|
||||
},
|
||||
paste: {
|
||||
title: '粘贴文本',
|
||||
@@ -270,7 +270,7 @@ export default {
|
||||
otgAdvanced: '高级:OTG 预设',
|
||||
otgProfile: '初始 HID 预设',
|
||||
otgProfileDesc: '选择 OTG HID 的初始预设,后续可在设置中修改。',
|
||||
otgLowEndpointHint: '检测到低端点 UDC,将自动禁用多媒体键。',
|
||||
otgLowEndpointHint: '检测到低端点 UDC,将自动禁用多媒体键盘。',
|
||||
videoDeviceHelp: '选择用于捕获远程主机画面的视频采集设备。通常是 HDMI 采集卡。',
|
||||
videoFormatHelp: 'MJPEG 格式兼容性最好,H.264/H.265 带宽占用更低但需要编码支持。',
|
||||
// Extensions
|
||||
@@ -651,28 +651,28 @@ export default {
|
||||
hidBackend: 'HID 后端',
|
||||
serialDevice: '串口设备',
|
||||
baudRate: '波特率',
|
||||
otgHidProfile: 'OTG HID 组合',
|
||||
otgHidProfile: 'OTG HID 功能',
|
||||
otgHidProfileDesc: '选择对目标主机暴露的 HID 功能',
|
||||
profile: '组合',
|
||||
otgProfileFull: '键盘 + 相对鼠标 + 绝对鼠标 + 多媒体 + 虚拟媒体',
|
||||
otgProfileFullNoMsd: '键盘 + 相对鼠标 + 绝对鼠标 + 多媒体(不含虚拟媒体)',
|
||||
otgProfileFullNoConsumer: '键盘 + 相对鼠标 + 绝对鼠标 + 虚拟媒体(不含多媒体)',
|
||||
otgProfileFullNoConsumerNoMsd: '键盘 + 相对鼠标 + 绝对鼠标(不含多媒体与虚拟媒体)',
|
||||
otgProfileLegacyKeyboard: '仅键盘',
|
||||
otgProfileLegacyMouseRelative: '仅相对鼠标',
|
||||
otgProfileCustom: '自定义',
|
||||
otgEndpointBudget: '最大端点数量',
|
||||
otgEndpointBudgetUnlimited: '无限制',
|
||||
otgEndpointBudgetHint: '此为硬件限制。若超出硬件端点数量,OTG 功能将无法使用。',
|
||||
otgEndpointUsage: '当前端点占用:{used} / {limit}',
|
||||
otgEndpointUsageUnlimited: '当前端点占用:{used} / 不限',
|
||||
otgEndpointExceeded: '当前 OTG 组合需要 {used} 个端点,已超出上限 {limit}。',
|
||||
otgFunctionKeyboard: '键盘',
|
||||
otgFunctionKeyboardDesc: '标准 HID 键盘设备',
|
||||
otgKeyboardLeds: '键盘状态灯',
|
||||
otgKeyboardLedsDesc: '启用 Caps/Num/Scroll 状态灯回读',
|
||||
otgFunctionMouseRelative: '相对鼠标',
|
||||
otgFunctionMouseRelativeDesc: '传统鼠标移动(HID 启动鼠标)',
|
||||
otgFunctionMouseAbsolute: '绝对鼠标',
|
||||
otgFunctionMouseAbsoluteDesc: '绝对定位(类似触控)',
|
||||
otgFunctionConsumer: '多媒体控制',
|
||||
otgFunctionConsumerDesc: '音量/播放/暂停等按键',
|
||||
otgFunctionConsumer: '多媒体键盘',
|
||||
otgFunctionConsumerDesc: '音量/播放/暂停等多媒体按键',
|
||||
otgFunctionMsd: '虚拟媒体(MSD)',
|
||||
otgFunctionMsdDesc: '向目标主机暴露 USB 存储',
|
||||
otgProfileWarning: '修改 HID 功能将导致 USB 设备重新连接',
|
||||
otgLowEndpointHint: '检测到低端点 UDC,将自动禁用多媒体键。',
|
||||
otgLowEndpointHint: '检测到低端点 UDC,将自动禁用多媒体键盘。',
|
||||
otgFunctionMinWarning: '请至少启用一个 HID 功能后再保存',
|
||||
// OTG Descriptor
|
||||
otgDescriptor: 'USB 设备描述符',
|
||||
@@ -799,7 +799,7 @@ export default {
|
||||
osWindows: 'Windows',
|
||||
osMac: 'Mac',
|
||||
osAndroid: 'Android',
|
||||
mediaKeys: '多媒体键',
|
||||
mediaKeys: '多媒体键盘',
|
||||
},
|
||||
config: {
|
||||
applied: '配置已应用',
|
||||
|
||||
@@ -85,6 +85,9 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
hid_ch9329_baudrate?: number
|
||||
hid_otg_udc?: string
|
||||
hid_otg_profile?: string
|
||||
hid_otg_endpoint_budget?: string
|
||||
hid_otg_keyboard_leds?: boolean
|
||||
msd_enabled?: boolean
|
||||
encoder_backend?: string
|
||||
audio_device?: string
|
||||
ttyd_enabled?: boolean
|
||||
|
||||
@@ -34,6 +34,12 @@ interface HidState {
|
||||
initialized: boolean
|
||||
online: boolean
|
||||
supportsAbsoluteMouse: boolean
|
||||
keyboardLedsEnabled: boolean
|
||||
ledState: {
|
||||
numLock: boolean
|
||||
capsLock: boolean
|
||||
scrollLock: boolean
|
||||
}
|
||||
device: string | null
|
||||
error: string | null
|
||||
errorCode: string | null
|
||||
@@ -89,6 +95,14 @@ export interface HidDeviceInfo {
|
||||
initialized: boolean
|
||||
online: boolean
|
||||
supports_absolute_mouse: boolean
|
||||
keyboard_leds_enabled: boolean
|
||||
led_state: {
|
||||
num_lock: boolean
|
||||
caps_lock: boolean
|
||||
scroll_lock: boolean
|
||||
compose: boolean
|
||||
kana: boolean
|
||||
}
|
||||
device: string | null
|
||||
error: string | null
|
||||
error_code?: string | null
|
||||
@@ -194,6 +208,12 @@ export const useSystemStore = defineStore('system', () => {
|
||||
initialized: state.initialized,
|
||||
online: state.online,
|
||||
supportsAbsoluteMouse: state.supports_absolute_mouse,
|
||||
keyboardLedsEnabled: state.keyboard_leds_enabled,
|
||||
ledState: {
|
||||
numLock: state.led_state.num_lock,
|
||||
capsLock: state.led_state.caps_lock,
|
||||
scrollLock: state.led_state.scroll_lock,
|
||||
},
|
||||
device: state.device ?? null,
|
||||
error: state.error ?? null,
|
||||
errorCode: state.error_code ?? null,
|
||||
@@ -298,6 +318,12 @@ export const useSystemStore = defineStore('system', () => {
|
||||
initialized: data.hid.initialized,
|
||||
online: data.hid.online,
|
||||
supportsAbsoluteMouse: data.hid.supports_absolute_mouse,
|
||||
keyboardLedsEnabled: data.hid.keyboard_leds_enabled,
|
||||
ledState: {
|
||||
numLock: data.hid.led_state.num_lock,
|
||||
capsLock: data.hid.led_state.caps_lock,
|
||||
scrollLock: data.hid.led_state.scroll_lock,
|
||||
},
|
||||
device: data.hid.device,
|
||||
error: data.hid.error,
|
||||
errorCode: data.hid.error_code ?? null,
|
||||
|
||||
@@ -58,12 +58,8 @@ export interface OtgDescriptorConfig {
|
||||
export enum OtgHidProfile {
|
||||
/** Full HID device set (keyboard + relative mouse + absolute mouse + consumer control) */
|
||||
Full = "full",
|
||||
/** Full HID device set without MSD */
|
||||
FullNoMsd = "full_no_msd",
|
||||
/** Full HID device set without consumer control */
|
||||
FullNoConsumer = "full_no_consumer",
|
||||
/** Full HID device set without consumer control and MSD */
|
||||
FullNoConsumerNoMsd = "full_no_consumer_no_msd",
|
||||
/** Legacy profile: only keyboard */
|
||||
LegacyKeyboard = "legacy_keyboard",
|
||||
/** Legacy profile: only relative mouse */
|
||||
@@ -72,6 +68,18 @@ export enum OtgHidProfile {
|
||||
Custom = "custom",
|
||||
}
|
||||
|
||||
/** OTG endpoint budget policy. */
|
||||
export enum OtgEndpointBudget {
|
||||
/** Derive a safe default from the selected UDC. */
|
||||
Auto = "auto",
|
||||
/** Limit OTG gadget functions to 5 endpoints. */
|
||||
Five = "five",
|
||||
/** Limit OTG gadget functions to 6 endpoints. */
|
||||
Six = "six",
|
||||
/** Do not impose a software endpoint budget. */
|
||||
Unlimited = "unlimited",
|
||||
}
|
||||
|
||||
/** OTG HID function selection (used when profile is Custom) */
|
||||
export interface OtgHidFunctions {
|
||||
keyboard: boolean;
|
||||
@@ -84,18 +92,18 @@ export interface OtgHidFunctions {
|
||||
export interface HidConfig {
|
||||
/** HID backend type */
|
||||
backend: HidBackend;
|
||||
/** OTG keyboard device path */
|
||||
otg_keyboard: string;
|
||||
/** OTG mouse device path */
|
||||
otg_mouse: string;
|
||||
/** OTG UDC (USB Device Controller) name */
|
||||
otg_udc?: string;
|
||||
/** OTG USB device descriptor configuration */
|
||||
otg_descriptor?: OtgDescriptorConfig;
|
||||
/** OTG HID function profile */
|
||||
otg_profile?: OtgHidProfile;
|
||||
/** OTG endpoint budget policy */
|
||||
otg_endpoint_budget?: OtgEndpointBudget;
|
||||
/** OTG HID function selection (used when profile is Custom) */
|
||||
otg_functions?: OtgHidFunctions;
|
||||
/** Enable keyboard LED/status feedback for OTG keyboard */
|
||||
otg_keyboard_leds?: boolean;
|
||||
/** CH9329 serial port */
|
||||
ch9329_port: string;
|
||||
/** CH9329 baud rate */
|
||||
@@ -580,7 +588,9 @@ export interface HidConfigUpdate {
|
||||
otg_udc?: string;
|
||||
otg_descriptor?: OtgDescriptorConfigUpdate;
|
||||
otg_profile?: OtgHidProfile;
|
||||
otg_endpoint_budget?: OtgEndpointBudget;
|
||||
otg_functions?: OtgHidFunctionsUpdate;
|
||||
otg_keyboard_leds?: boolean;
|
||||
mouse_absolute?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,9 +119,12 @@ const myClientId = generateUUID()
|
||||
// HID state
|
||||
const mouseMode = ref<'absolute' | 'relative'>('absolute')
|
||||
const pressedKeys = ref<CanonicalKey[]>([])
|
||||
const keyboardLed = ref({
|
||||
capsLock: false,
|
||||
})
|
||||
const keyboardLed = computed(() => ({
|
||||
capsLock: systemStore.hid?.ledState.capsLock ?? false,
|
||||
numLock: systemStore.hid?.ledState.numLock ?? false,
|
||||
scrollLock: systemStore.hid?.ledState.scrollLock ?? false,
|
||||
}))
|
||||
const keyboardLedEnabled = computed(() => systemStore.hid?.keyboardLedsEnabled ?? false)
|
||||
const activeModifierMask = ref(0)
|
||||
const mousePosition = ref({ x: 0, y: 0 })
|
||||
const lastMousePosition = ref({ x: 0, y: 0 }) // Track last position for relative mode
|
||||
@@ -346,6 +349,13 @@ const hidDetails = computed<StatusDetail[]>(() => {
|
||||
{ label: t('statusCard.initialized'), value: hid.initialized ? t('statusCard.yes') : t('statusCard.no'), status: hid.error ? 'error' : hid.initialized ? 'ok' : 'warning' },
|
||||
{ label: t('statusCard.online'), value: hid.online ? t('statusCard.yes') : t('statusCard.no'), status: hid.online ? 'ok' : hid.initialized ? 'warning' : 'error' },
|
||||
{ label: t('statusCard.currentMode'), value: mouseMode.value === 'absolute' ? t('statusCard.absolute') : t('statusCard.relative'), status: 'ok' },
|
||||
{
|
||||
label: t('settings.otgKeyboardLeds'),
|
||||
value: hid.keyboardLedsEnabled
|
||||
? `Caps:${hid.ledState.capsLock ? t('common.on') : t('common.off')} Num:${hid.ledState.numLock ? t('common.on') : t('common.off')} Scroll:${hid.ledState.scrollLock ? t('common.on') : t('common.off')}`
|
||||
: t('infobar.keyboardLedUnavailable'),
|
||||
status: hid.keyboardLedsEnabled ? 'ok' : undefined,
|
||||
},
|
||||
]
|
||||
|
||||
if (hid.errorCode) {
|
||||
@@ -1618,8 +1628,6 @@ function handleKeyDown(e: KeyboardEvent) {
|
||||
})
|
||||
}
|
||||
|
||||
keyboardLed.value.capsLock = e.getModifierState('CapsLock')
|
||||
|
||||
const canonicalKey = keyboardEventToCanonicalKey(e.code, e.key)
|
||||
if (canonicalKey === undefined) {
|
||||
console.warn(`[HID] Unmapped key down: code=${e.code}, key=${e.key}`)
|
||||
@@ -2088,10 +2096,6 @@ function handleVirtualKeyDown(key: CanonicalKey) {
|
||||
if (!pressedKeys.value.includes(key)) {
|
||||
pressedKeys.value = [...pressedKeys.value, key]
|
||||
}
|
||||
// Toggle CapsLock state when virtual keyboard presses CapsLock
|
||||
if (key === CanonicalKey.CapsLock) {
|
||||
keyboardLed.value.capsLock = !keyboardLed.value.capsLock
|
||||
}
|
||||
}
|
||||
|
||||
function handleVirtualKeyUp(key: CanonicalKey) {
|
||||
@@ -2536,6 +2540,9 @@ onUnmounted(() => {
|
||||
<InfoBar
|
||||
:pressed-keys="pressedKeys"
|
||||
:caps-lock="keyboardLed.capsLock"
|
||||
:num-lock="keyboardLed.numLock"
|
||||
:scroll-lock="keyboardLed.scrollLock"
|
||||
:keyboard-led-enabled="keyboardLedEnabled"
|
||||
:mouse-position="mousePosition"
|
||||
:debug-mode="false"
|
||||
/>
|
||||
|
||||
@@ -33,6 +33,7 @@ import type {
|
||||
AtxDriverType,
|
||||
ActiveLevel,
|
||||
AtxDevices,
|
||||
OtgEndpointBudget,
|
||||
OtgHidProfile,
|
||||
OtgHidFunctions,
|
||||
} from '@/types/generated'
|
||||
@@ -326,13 +327,15 @@ const config = ref({
|
||||
hid_serial_device: '',
|
||||
hid_serial_baudrate: 9600,
|
||||
hid_otg_udc: '',
|
||||
hid_otg_profile: 'full' as OtgHidProfile,
|
||||
hid_otg_profile: 'custom' as OtgHidProfile,
|
||||
hid_otg_endpoint_budget: 'six' as OtgEndpointBudget,
|
||||
hid_otg_functions: {
|
||||
keyboard: true,
|
||||
mouse_relative: true,
|
||||
mouse_absolute: true,
|
||||
consumer: true,
|
||||
} as OtgHidFunctions,
|
||||
hid_otg_keyboard_leds: false,
|
||||
msd_enabled: false,
|
||||
msd_dir: '',
|
||||
encoder_backend: 'auto',
|
||||
@@ -345,20 +348,6 @@ const config = ref({
|
||||
|
||||
// Tracks whether TURN password is configured on the server
|
||||
const hasTurnPassword = ref(false)
|
||||
const configLoaded = ref(false)
|
||||
const devicesLoaded = ref(false)
|
||||
const hidProfileAligned = ref(false)
|
||||
|
||||
const isLowEndpointUdc = computed(() => {
|
||||
if (config.value.hid_otg_udc) {
|
||||
return /musb/i.test(config.value.hid_otg_udc)
|
||||
}
|
||||
return devices.value.udc.some((udc) => /musb/i.test(udc.name))
|
||||
})
|
||||
|
||||
const showLowEndpointHint = computed(() =>
|
||||
config.value.hid_backend === 'otg' && isLowEndpointUdc.value
|
||||
)
|
||||
|
||||
type OtgSelfCheckLevel = 'info' | 'warn' | 'error'
|
||||
type OtgCheckGroupStatus = 'ok' | 'warn' | 'error' | 'skipped'
|
||||
@@ -619,28 +608,80 @@ async function onRunVideoEncoderSelfCheckClick() {
|
||||
await runVideoEncoderSelfCheck()
|
||||
}
|
||||
|
||||
function alignHidProfileForLowEndpoint() {
|
||||
if (hidProfileAligned.value) return
|
||||
if (!configLoaded.value || !devicesLoaded.value) return
|
||||
if (config.value.hid_backend !== 'otg') {
|
||||
hidProfileAligned.value = true
|
||||
return
|
||||
function defaultOtgEndpointBudgetForUdc(udc?: string): OtgEndpointBudget {
|
||||
return /musb/i.test(udc || '') ? 'five' as OtgEndpointBudget : 'six' as OtgEndpointBudget
|
||||
}
|
||||
|
||||
function normalizeOtgEndpointBudget(budget: OtgEndpointBudget | undefined, udc?: string): OtgEndpointBudget {
|
||||
if (!budget || budget === 'auto') {
|
||||
return defaultOtgEndpointBudgetForUdc(udc)
|
||||
}
|
||||
if (!isLowEndpointUdc.value) {
|
||||
hidProfileAligned.value = true
|
||||
return
|
||||
return budget
|
||||
}
|
||||
|
||||
function endpointLimitForBudget(budget: OtgEndpointBudget): number | null {
|
||||
if (budget === 'unlimited') return null
|
||||
return budget === 'five' ? 5 : 6
|
||||
}
|
||||
|
||||
const effectiveOtgFunctions = computed(() => ({ ...config.value.hid_otg_functions }))
|
||||
|
||||
const otgEndpointLimit = computed(() =>
|
||||
endpointLimitForBudget(config.value.hid_otg_endpoint_budget)
|
||||
)
|
||||
|
||||
const otgRequiredEndpoints = computed(() => {
|
||||
if (config.value.hid_backend !== 'otg') return 0
|
||||
const functions = effectiveOtgFunctions.value
|
||||
let endpoints = 0
|
||||
if (functions.keyboard) {
|
||||
endpoints += 1
|
||||
if (config.value.hid_otg_keyboard_leds) endpoints += 1
|
||||
}
|
||||
if (config.value.hid_otg_profile === 'full') {
|
||||
config.value.hid_otg_profile = 'full_no_consumer' as OtgHidProfile
|
||||
} else if (config.value.hid_otg_profile === 'full_no_msd') {
|
||||
config.value.hid_otg_profile = 'full_no_consumer_no_msd' as OtgHidProfile
|
||||
if (functions.mouse_relative) endpoints += 1
|
||||
if (functions.mouse_absolute) endpoints += 1
|
||||
if (functions.consumer) endpoints += 1
|
||||
if (config.value.msd_enabled) endpoints += 2
|
||||
return endpoints
|
||||
})
|
||||
|
||||
const isOtgEndpointBudgetValid = computed(() => {
|
||||
if (config.value.hid_backend !== 'otg') return true
|
||||
const limit = otgEndpointLimit.value
|
||||
return limit === null || otgRequiredEndpoints.value <= limit
|
||||
})
|
||||
|
||||
const otgEndpointUsageText = computed(() => {
|
||||
const limit = otgEndpointLimit.value
|
||||
if (limit === null) {
|
||||
return t('settings.otgEndpointUsageUnlimited', { used: otgRequiredEndpoints.value })
|
||||
}
|
||||
return t('settings.otgEndpointUsage', { used: otgRequiredEndpoints.value, limit })
|
||||
})
|
||||
|
||||
const showOtgEndpointBudgetHint = computed(() =>
|
||||
config.value.hid_backend === 'otg'
|
||||
)
|
||||
|
||||
const isKeyboardLedToggleDisabled = computed(() =>
|
||||
config.value.hid_backend !== 'otg' || !effectiveOtgFunctions.value.keyboard
|
||||
)
|
||||
|
||||
function describeEndpointBudget(budget: OtgEndpointBudget): string {
|
||||
switch (budget) {
|
||||
case 'five':
|
||||
return '5'
|
||||
case 'six':
|
||||
return '6'
|
||||
case 'unlimited':
|
||||
return t('settings.otgEndpointBudgetUnlimited')
|
||||
default:
|
||||
return '6'
|
||||
}
|
||||
hidProfileAligned.value = true
|
||||
}
|
||||
|
||||
const isHidFunctionSelectionValid = computed(() => {
|
||||
if (config.value.hid_backend !== 'otg') return true
|
||||
if (config.value.hid_otg_profile !== 'custom') return true
|
||||
const f = config.value.hid_otg_functions
|
||||
return !!(f.keyboard || f.mouse_relative || f.mouse_absolute || f.consumer)
|
||||
})
|
||||
@@ -946,26 +987,9 @@ async function saveConfig() {
|
||||
|
||||
// HID config
|
||||
if (activeSection.value === 'hid') {
|
||||
if (!isHidFunctionSelectionValid.value) {
|
||||
if (!isHidFunctionSelectionValid.value || !isOtgEndpointBudgetValid.value) {
|
||||
return
|
||||
}
|
||||
let desiredMsdEnabled = config.value.msd_enabled
|
||||
if (config.value.hid_backend === 'otg') {
|
||||
if (config.value.hid_otg_profile === 'full') {
|
||||
desiredMsdEnabled = true
|
||||
} else if (config.value.hid_otg_profile === 'full_no_msd') {
|
||||
desiredMsdEnabled = false
|
||||
} else if (config.value.hid_otg_profile === 'full_no_consumer') {
|
||||
desiredMsdEnabled = true
|
||||
} else if (config.value.hid_otg_profile === 'full_no_consumer_no_msd') {
|
||||
desiredMsdEnabled = false
|
||||
} else if (
|
||||
config.value.hid_otg_profile === 'legacy_keyboard'
|
||||
|| config.value.hid_otg_profile === 'legacy_mouse_relative'
|
||||
) {
|
||||
desiredMsdEnabled = false
|
||||
}
|
||||
}
|
||||
const hidUpdate: any = {
|
||||
backend: config.value.hid_backend as any,
|
||||
ch9329_port: config.value.hid_serial_device || undefined,
|
||||
@@ -980,16 +1004,15 @@ async function saveConfig() {
|
||||
product: otgProduct.value || 'One-KVM USB Device',
|
||||
serial_number: otgSerialNumber.value || undefined,
|
||||
}
|
||||
hidUpdate.otg_profile = config.value.hid_otg_profile
|
||||
hidUpdate.otg_profile = 'custom'
|
||||
hidUpdate.otg_endpoint_budget = config.value.hid_otg_endpoint_budget
|
||||
hidUpdate.otg_functions = { ...config.value.hid_otg_functions }
|
||||
hidUpdate.otg_keyboard_leds = config.value.hid_otg_keyboard_leds
|
||||
}
|
||||
savePromises.push(configStore.updateHid(hidUpdate))
|
||||
if (config.value.msd_enabled !== desiredMsdEnabled) {
|
||||
config.value.msd_enabled = desiredMsdEnabled
|
||||
}
|
||||
savePromises.push(
|
||||
configStore.updateMsd({
|
||||
enabled: desiredMsdEnabled,
|
||||
enabled: config.value.msd_enabled,
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1034,13 +1057,15 @@ async function loadConfig() {
|
||||
hid_serial_device: hid.ch9329_port || '',
|
||||
hid_serial_baudrate: hid.ch9329_baudrate || 9600,
|
||||
hid_otg_udc: hid.otg_udc || '',
|
||||
hid_otg_profile: (hid.otg_profile || 'full') as OtgHidProfile,
|
||||
hid_otg_profile: 'custom' as OtgHidProfile,
|
||||
hid_otg_endpoint_budget: normalizeOtgEndpointBudget(hid.otg_endpoint_budget, hid.otg_udc || ''),
|
||||
hid_otg_functions: {
|
||||
keyboard: hid.otg_functions?.keyboard ?? true,
|
||||
mouse_relative: hid.otg_functions?.mouse_relative ?? true,
|
||||
mouse_absolute: hid.otg_functions?.mouse_absolute ?? true,
|
||||
consumer: hid.otg_functions?.consumer ?? true,
|
||||
} as OtgHidFunctions,
|
||||
hid_otg_keyboard_leds: hid.otg_keyboard_leds ?? false,
|
||||
msd_enabled: msd.enabled || false,
|
||||
msd_dir: msd.msd_dir || '',
|
||||
encoder_backend: stream.encoder || 'auto',
|
||||
@@ -1065,9 +1090,6 @@ async function loadConfig() {
|
||||
|
||||
} catch (e) {
|
||||
console.error('Failed to load config:', e)
|
||||
} finally {
|
||||
configLoaded.value = true
|
||||
alignHidProfileForLowEndpoint()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,9 +1098,6 @@ async function loadDevices() {
|
||||
devices.value = await configApi.listDevices()
|
||||
} catch (e) {
|
||||
console.error('Failed to load devices:', e)
|
||||
} finally {
|
||||
devicesLoaded.value = true
|
||||
alignHidProfileForLowEndpoint()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2230,63 +2249,75 @@ watch(() => route.query.tab, (tab) => {
|
||||
<p class="text-sm text-muted-foreground">{{ t('settings.otgHidProfileDesc') }}</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="otg-profile">{{ t('settings.profile') }}</Label>
|
||||
<select id="otg-profile" v-model="config.hid_otg_profile" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
||||
<option value="full">{{ t('settings.otgProfileFull') }}</option>
|
||||
<option value="full_no_msd">{{ t('settings.otgProfileFullNoMsd') }}</option>
|
||||
<option value="full_no_consumer">{{ t('settings.otgProfileFullNoConsumer') }}</option>
|
||||
<option value="full_no_consumer_no_msd">{{ t('settings.otgProfileFullNoConsumerNoMsd') }}</option>
|
||||
<option value="legacy_keyboard">{{ t('settings.otgProfileLegacyKeyboard') }}</option>
|
||||
<option value="legacy_mouse_relative">{{ t('settings.otgProfileLegacyMouseRelative') }}</option>
|
||||
<option value="custom">{{ t('settings.otgProfileCustom') }}</option>
|
||||
<Label for="otg-endpoint-budget">{{ t('settings.otgEndpointBudget') }}</Label>
|
||||
<select id="otg-endpoint-budget" v-model="config.hid_otg_endpoint_budget" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
|
||||
<option value="five">5</option>
|
||||
<option value="six">6</option>
|
||||
<option value="unlimited">{{ t('settings.otgEndpointBudgetUnlimited') }}</option>
|
||||
</select>
|
||||
<p class="text-xs text-muted-foreground">{{ otgEndpointUsageText }}</p>
|
||||
</div>
|
||||
<div v-if="config.hid_otg_profile === 'custom'" class="space-y-3 rounded-md border border-border/60 p-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionKeyboard') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionKeyboardDesc') }}</p>
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-3 rounded-md border border-border/60 p-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMouseRelative') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMouseRelativeDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.mouse_relative" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMouseAbsolute') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMouseAbsoluteDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.mouse_absolute" />
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.keyboard" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMouseRelative') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMouseRelativeDesc') }}</p>
|
||||
<div class="space-y-3 rounded-md border border-border/60 p-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionKeyboard') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionKeyboardDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.keyboard" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionConsumer') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionConsumerDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.consumer" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgKeyboardLeds') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgKeyboardLedsDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_keyboard_leds" :disabled="isKeyboardLedToggleDisabled" />
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.mouse_relative" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMouseAbsolute') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMouseAbsoluteDesc') }}</p>
|
||||
<div class="space-y-3 rounded-md border border-border/60 p-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMsd') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMsdDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.msd_enabled" />
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.mouse_absolute" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionConsumer') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionConsumerDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.hid_otg_functions.consumer" />
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMsd') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMsdDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="config.msd_enabled" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400">
|
||||
{{ t('settings.otgProfileWarning') }}
|
||||
</p>
|
||||
<p v-if="showLowEndpointHint" class="text-xs text-amber-600 dark:text-amber-400">
|
||||
{{ t('settings.otgLowEndpointHint') }}
|
||||
<p v-if="showOtgEndpointBudgetHint" class="text-xs text-muted-foreground">
|
||||
{{ t('settings.otgEndpointBudgetHint') }}
|
||||
</p>
|
||||
<p v-if="!isOtgEndpointBudgetValid" class="text-xs text-amber-600 dark:text-amber-400">
|
||||
{{ t('settings.otgEndpointExceeded', { used: otgRequiredEndpoints, limit: describeEndpointBudget(config.hid_otg_endpoint_budget) }) }}
|
||||
</p>
|
||||
</div>
|
||||
<Separator class="my-4" />
|
||||
|
||||
@@ -97,7 +97,12 @@ const ch9329Port = ref('')
|
||||
const ch9329Baudrate = ref(9600)
|
||||
const otgUdc = ref('')
|
||||
const hidOtgProfile = ref('full')
|
||||
const otgMsdEnabled = ref(true)
|
||||
const otgEndpointBudget = ref<'five' | 'six' | 'unlimited'>('six')
|
||||
const otgKeyboardLeds = ref(true)
|
||||
const otgProfileTouched = ref(false)
|
||||
const otgEndpointBudgetTouched = ref(false)
|
||||
const otgKeyboardLedsTouched = ref(false)
|
||||
const showAdvancedOtg = ref(false)
|
||||
|
||||
// Extension settings
|
||||
@@ -203,19 +208,67 @@ const availableFps = computed(() => {
|
||||
return resolution?.fps || []
|
||||
})
|
||||
|
||||
const isLowEndpointUdc = computed(() => {
|
||||
if (otgUdc.value) {
|
||||
return /musb/i.test(otgUdc.value)
|
||||
function defaultOtgEndpointBudgetForUdc(udc?: string): 'five' | 'six' {
|
||||
return /musb/i.test(udc || '') ? 'five' : 'six'
|
||||
}
|
||||
|
||||
function endpointLimitForBudget(budget: 'five' | 'six' | 'unlimited'): number | null {
|
||||
if (budget === 'unlimited') return null
|
||||
return budget === 'five' ? 5 : 6
|
||||
}
|
||||
|
||||
const otgRequiredEndpoints = computed(() => {
|
||||
if (hidBackend.value !== 'otg') return 0
|
||||
const functions = {
|
||||
keyboard: hidOtgProfile.value === 'full' || hidOtgProfile.value === 'full_no_consumer' || hidOtgProfile.value === 'legacy_keyboard',
|
||||
mouseRelative: hidOtgProfile.value === 'full' || hidOtgProfile.value === 'full_no_consumer' || hidOtgProfile.value === 'legacy_mouse_relative',
|
||||
mouseAbsolute: hidOtgProfile.value === 'full' || hidOtgProfile.value === 'full_no_consumer',
|
||||
consumer: hidOtgProfile.value === 'full',
|
||||
}
|
||||
return devices.value.udc.some((udc) => /musb/i.test(udc.name))
|
||||
let endpoints = 0
|
||||
if (functions.keyboard) {
|
||||
endpoints += 1
|
||||
if (otgKeyboardLeds.value) endpoints += 1
|
||||
}
|
||||
if (functions.mouseRelative) endpoints += 1
|
||||
if (functions.mouseAbsolute) endpoints += 1
|
||||
if (functions.consumer) endpoints += 1
|
||||
if (otgMsdEnabled.value) endpoints += 2
|
||||
return endpoints
|
||||
})
|
||||
|
||||
function applyOtgProfileDefault() {
|
||||
if (otgProfileTouched.value) return
|
||||
const otgProfileHasKeyboard = computed(() =>
|
||||
hidOtgProfile.value === 'full'
|
||||
|| hidOtgProfile.value === 'full_no_consumer'
|
||||
|| hidOtgProfile.value === 'legacy_keyboard'
|
||||
)
|
||||
|
||||
const isOtgEndpointBudgetValid = computed(() => {
|
||||
const limit = endpointLimitForBudget(otgEndpointBudget.value)
|
||||
return limit === null || otgRequiredEndpoints.value <= limit
|
||||
})
|
||||
|
||||
const otgEndpointUsageText = computed(() => {
|
||||
const limit = endpointLimitForBudget(otgEndpointBudget.value)
|
||||
if (limit === null) {
|
||||
return t('settings.otgEndpointUsageUnlimited', { used: otgRequiredEndpoints.value })
|
||||
}
|
||||
return t('settings.otgEndpointUsage', { used: otgRequiredEndpoints.value, limit })
|
||||
})
|
||||
|
||||
function applyOtgDefaults() {
|
||||
if (hidBackend.value !== 'otg') return
|
||||
const preferred = isLowEndpointUdc.value ? 'full_no_consumer' : 'full'
|
||||
if (hidOtgProfile.value === preferred) return
|
||||
hidOtgProfile.value = preferred
|
||||
|
||||
const recommendedBudget = defaultOtgEndpointBudgetForUdc(otgUdc.value)
|
||||
if (!otgEndpointBudgetTouched.value) {
|
||||
otgEndpointBudget.value = recommendedBudget
|
||||
}
|
||||
if (!otgProfileTouched.value) {
|
||||
hidOtgProfile.value = 'full_no_consumer'
|
||||
}
|
||||
if (!otgKeyboardLedsTouched.value) {
|
||||
otgKeyboardLeds.value = otgEndpointBudget.value !== 'five'
|
||||
}
|
||||
}
|
||||
|
||||
function onOtgProfileChange(value: unknown) {
|
||||
@@ -223,6 +276,20 @@ function onOtgProfileChange(value: unknown) {
|
||||
otgProfileTouched.value = true
|
||||
}
|
||||
|
||||
function onOtgEndpointBudgetChange(value: unknown) {
|
||||
otgEndpointBudget.value =
|
||||
value === 'five' || value === 'six' || value === 'unlimited' ? value : 'six'
|
||||
otgEndpointBudgetTouched.value = true
|
||||
if (!otgKeyboardLedsTouched.value) {
|
||||
otgKeyboardLeds.value = otgEndpointBudget.value !== 'five'
|
||||
}
|
||||
}
|
||||
|
||||
function onOtgKeyboardLedsChange(value: boolean) {
|
||||
otgKeyboardLeds.value = value
|
||||
otgKeyboardLedsTouched.value = true
|
||||
}
|
||||
|
||||
// Common baud rates for CH9329
|
||||
const baudRates = [9600, 19200, 38400, 57600, 115200]
|
||||
|
||||
@@ -338,16 +405,16 @@ watch(hidBackend, (newBackend) => {
|
||||
if (newBackend === 'otg' && !otgUdc.value && devices.value.udc.length > 0) {
|
||||
otgUdc.value = devices.value.udc[0]?.name || ''
|
||||
}
|
||||
applyOtgProfileDefault()
|
||||
applyOtgDefaults()
|
||||
})
|
||||
|
||||
watch(otgUdc, () => {
|
||||
applyOtgProfileDefault()
|
||||
applyOtgDefaults()
|
||||
})
|
||||
|
||||
watch(showAdvancedOtg, (open) => {
|
||||
if (open) {
|
||||
applyOtgProfileDefault()
|
||||
applyOtgDefaults()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -370,7 +437,7 @@ onMounted(async () => {
|
||||
if (result.udc.length > 0 && result.udc[0]) {
|
||||
otgUdc.value = result.udc[0].name
|
||||
}
|
||||
applyOtgProfileDefault()
|
||||
applyOtgDefaults()
|
||||
|
||||
// Auto-select audio device if available (and no video device to trigger watch)
|
||||
if (result.audio.length > 0 && !audioDevice.value) {
|
||||
@@ -461,6 +528,13 @@ function validateStep3(): boolean {
|
||||
error.value = t('setup.selectUdc')
|
||||
return false
|
||||
}
|
||||
if (hidBackend.value === 'otg' && !isOtgEndpointBudgetValid.value) {
|
||||
error.value = t('settings.otgEndpointExceeded', {
|
||||
used: otgRequiredEndpoints.value,
|
||||
limit: otgEndpointBudget.value === 'unlimited' ? t('settings.otgEndpointBudgetUnlimited') : otgEndpointBudget.value === 'five' ? '5' : '6',
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -523,6 +597,9 @@ async function handleSetup() {
|
||||
if (hidBackend.value === 'otg' && otgUdc.value) {
|
||||
setupData.hid_otg_udc = otgUdc.value
|
||||
setupData.hid_otg_profile = hidOtgProfile.value
|
||||
setupData.hid_otg_endpoint_budget = otgEndpointBudget.value
|
||||
setupData.hid_otg_keyboard_leds = otgKeyboardLeds.value
|
||||
setupData.msd_enabled = otgMsdEnabled.value
|
||||
}
|
||||
|
||||
// Encoder backend setting
|
||||
@@ -990,16 +1067,47 @@ const stepIcons = [User, Video, Keyboard, Puzzle]
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="full">{{ t('settings.otgProfileFull') }}</SelectItem>
|
||||
<SelectItem value="full_no_msd">{{ t('settings.otgProfileFullNoMsd') }}</SelectItem>
|
||||
<SelectItem value="full_no_consumer">{{ t('settings.otgProfileFullNoConsumer') }}</SelectItem>
|
||||
<SelectItem value="full_no_consumer_no_msd">{{ t('settings.otgProfileFullNoConsumerNoMsd') }}</SelectItem>
|
||||
<SelectItem value="legacy_keyboard">{{ t('settings.otgProfileLegacyKeyboard') }}</SelectItem>
|
||||
<SelectItem value="legacy_mouse_relative">{{ t('settings.otgProfileLegacyMouseRelative') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<p v-if="isLowEndpointUdc" class="text-xs text-amber-600 dark:text-amber-400">
|
||||
{{ t('setup.otgLowEndpointHint') }}
|
||||
<div class="space-y-2">
|
||||
<Label for="otgEndpointBudget">{{ t('settings.otgEndpointBudget') }}</Label>
|
||||
<Select :model-value="otgEndpointBudget" @update:modelValue="onOtgEndpointBudgetChange">
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="five">5</SelectItem>
|
||||
<SelectItem value="six">6</SelectItem>
|
||||
<SelectItem value="unlimited">{{ t('settings.otgEndpointBudgetUnlimited') }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ otgEndpointUsageText }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-md border border-border/60 p-3">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgKeyboardLeds') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgKeyboardLedsDesc') }}</p>
|
||||
</div>
|
||||
<Switch :model-value="otgKeyboardLeds" :disabled="!otgProfileHasKeyboard" @update:model-value="onOtgKeyboardLedsChange" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-md border border-border/60 p-3">
|
||||
<div>
|
||||
<Label>{{ t('settings.otgFunctionMsd') }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMsdDesc') }}</p>
|
||||
</div>
|
||||
<Switch v-model="otgMsdEnabled" />
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ t('settings.otgEndpointBudgetHint') }}
|
||||
</p>
|
||||
<p v-if="!isOtgEndpointBudgetValid" class="text-xs text-amber-600 dark:text-amber-400">
|
||||
{{ t('settings.otgEndpointExceeded', { used: otgRequiredEndpoints, limit: otgEndpointBudget === 'unlimited' ? t('settings.otgEndpointBudgetUnlimited') : otgEndpointBudget === 'five' ? '5' : '6' }) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user