feat(hid): 添加 Linux 键盘与 SysRq 快捷键 (#307)

控制台增加独立快捷键菜单,提供常用组合键和 Linux SysRq 指令,虚拟键盘补充 Linux 布局。
通过可靠且有序的 WebSocket 发送组合键,取消或失败时逐一释放按键,防止修饰键残留。
执行期间避免物理键盘与虚拟键盘交错输入,断连、失焦和切换布局时停止执行。
修正焦点移动后的物理按键释放,并将原虚拟键盘组合键迁移到统一菜单。

验证:独立暂存快照前端构建通过;6 项快捷键单测及 5 组浏览器测试通过,覆盖组合键序列、失败清理、取消及键盘焦点。

Refs #307
This commit is contained in:
mofeng-git
2026-10-28 07:21:52 +08:00
parent 8d58119b77
commit 3c15168ddc
8 changed files with 371 additions and 127 deletions

View File

@@ -48,6 +48,8 @@ import HidConfigPopover from '@/components/HidConfigPopover.vue'
import AudioConfigPopover from '@/components/AudioConfigPopover.vue'
import MsdDialog from '@/components/MsdDialog.vue'
import VideoDisplayControls from '@/components/VideoDisplayControls.vue'
import KeyboardShortcutsPopover from '@/components/KeyboardShortcutsPopover.vue'
import type { KeyboardShortcut } from '@/lib/keyboardShortcuts'
import type { ConsoleLayout } from '@/composables/useConsoleLayout'
const { t, locale } = useI18n()
@@ -73,6 +75,9 @@ const props = defineProps<{
fullscreen?: boolean
statsOpen?: boolean
terminalOpen?: boolean
shortcutsReady?: boolean
shortcutInputBusy?: boolean
shortcutBusy?: boolean
}>()
const isSidebarLayout = computed(() => props.layout === 'sidebar')
const isFloatingLayout = computed(() => props.layout === 'floating')
@@ -106,6 +111,8 @@ const emit = defineEmits<{
(e: 'reset'): void
(e: 'wol', macAddress: string): void
(e: 'openTerminal'): void
(e: 'executeShortcut', shortcut: KeyboardShortcut): void
(e: 'cancelShortcut'): void
}>()
const pasteOpen = ref(false)
@@ -114,6 +121,7 @@ const videoPopoverOpen = ref(false)
const hidPopoverOpen = ref(false)
const audioPopoverOpen = ref(false)
const msdDialogOpen = ref(false)
const shortcutsOpen = ref(false)
const mobileAtxOpen = ref(false)
const mobilePasteOpen = ref(false)
@@ -259,6 +267,7 @@ watch([() => props.layout, floatingCollapsed, minimalDisplayControls], () => {
hidPopoverOpen.value = false
audioPopoverOpen.value = false
msdDialogOpen.value = false
shortcutsOpen.value = false
mobileAtxOpen.value = false
mobilePasteOpen.value = false
void observeLayout()
@@ -521,6 +530,16 @@ const hasRightOverflow = computed(() => {
@toggle-virtual-keyboard="emit('toggleVirtualKeyboard')"
/>
<KeyboardShortcutsPopover
v-model:open="shortcutsOpen"
:ready="props.shortcutsReady === true"
:input-busy="props.shortcutInputBusy"
:busy="props.shortcutBusy"
:side="isSidebarLayout ? 'right' : 'bottom'"
@execute="emit('executeShortcut', $event)"
@cancel="emit('cancelShortcut')"
/>
<!-- Connection Stats - Adaptive -->
<div v-if="isVisible('stats')">
<TooltipProvider>