fix: 优化 WebRTC 建连流程、修复平台信息、修复虚拟键盘键值映射

- WebRTC:默认 mDNS 调整为 QueryOnly,Answer 阶段改为等待 ICE gathering complete(2.5s 超时),提升首次建连成功率与候选完整性
- WebRTC:前端建连流程增加阶段化状态与串行保护(connectInFlight/ready gate),优化配置变更后的重连时机与失败处理,减少竞态和无效重试
- Device:平台信息补充 `/proc/device-tree/model` 回退并统一展示为“处理器/平台”
- HID:键盘输入链路统一为 HID usage + modifier bitmask,修复虚拟键盘/宏/粘贴键值映射错误
This commit is contained in:
mofeng-git
2026-02-20 13:34:49 +08:00
parent 5f03971579
commit ce622e4492
16 changed files with 667 additions and 390 deletions

View File

@@ -1,136 +1,135 @@
// Character to JavaScript keyCode mapping for text paste functionality
// Maps printable ASCII characters to JavaScript keyCodes that the backend expects
// The backend (keymap.rs) will convert these JS keyCodes to USB HID keycodes
// Character to HID usage mapping for text paste functionality.
// The table follows US QWERTY layout semantics.
import { keys } from '@/lib/keyboardMappings'
export interface CharKeyMapping {
keyCode: number // JavaScript keyCode (same as KeyboardEvent.keyCode)
hidCode: number // USB HID usage code
shift: boolean // Whether Shift modifier is needed
}
// US QWERTY keyboard layout mapping
// Maps characters to their JavaScript keyCode and whether Shift is required
const charToKeyMap: Record<string, CharKeyMapping> = {
// Lowercase letters (no shift) - JS keyCodes 65-90
a: { keyCode: 65, shift: false },
b: { keyCode: 66, shift: false },
c: { keyCode: 67, shift: false },
d: { keyCode: 68, shift: false },
e: { keyCode: 69, shift: false },
f: { keyCode: 70, shift: false },
g: { keyCode: 71, shift: false },
h: { keyCode: 72, shift: false },
i: { keyCode: 73, shift: false },
j: { keyCode: 74, shift: false },
k: { keyCode: 75, shift: false },
l: { keyCode: 76, shift: false },
m: { keyCode: 77, shift: false },
n: { keyCode: 78, shift: false },
o: { keyCode: 79, shift: false },
p: { keyCode: 80, shift: false },
q: { keyCode: 81, shift: false },
r: { keyCode: 82, shift: false },
s: { keyCode: 83, shift: false },
t: { keyCode: 84, shift: false },
u: { keyCode: 85, shift: false },
v: { keyCode: 86, shift: false },
w: { keyCode: 87, shift: false },
x: { keyCode: 88, shift: false },
y: { keyCode: 89, shift: false },
z: { keyCode: 90, shift: false },
// Lowercase letters
a: { hidCode: keys.KeyA, shift: false },
b: { hidCode: keys.KeyB, shift: false },
c: { hidCode: keys.KeyC, shift: false },
d: { hidCode: keys.KeyD, shift: false },
e: { hidCode: keys.KeyE, shift: false },
f: { hidCode: keys.KeyF, shift: false },
g: { hidCode: keys.KeyG, shift: false },
h: { hidCode: keys.KeyH, shift: false },
i: { hidCode: keys.KeyI, shift: false },
j: { hidCode: keys.KeyJ, shift: false },
k: { hidCode: keys.KeyK, shift: false },
l: { hidCode: keys.KeyL, shift: false },
m: { hidCode: keys.KeyM, shift: false },
n: { hidCode: keys.KeyN, shift: false },
o: { hidCode: keys.KeyO, shift: false },
p: { hidCode: keys.KeyP, shift: false },
q: { hidCode: keys.KeyQ, shift: false },
r: { hidCode: keys.KeyR, shift: false },
s: { hidCode: keys.KeyS, shift: false },
t: { hidCode: keys.KeyT, shift: false },
u: { hidCode: keys.KeyU, shift: false },
v: { hidCode: keys.KeyV, shift: false },
w: { hidCode: keys.KeyW, shift: false },
x: { hidCode: keys.KeyX, shift: false },
y: { hidCode: keys.KeyY, shift: false },
z: { hidCode: keys.KeyZ, shift: false },
// Uppercase letters (with shift) - same keyCodes, just with Shift
A: { keyCode: 65, shift: true },
B: { keyCode: 66, shift: true },
C: { keyCode: 67, shift: true },
D: { keyCode: 68, shift: true },
E: { keyCode: 69, shift: true },
F: { keyCode: 70, shift: true },
G: { keyCode: 71, shift: true },
H: { keyCode: 72, shift: true },
I: { keyCode: 73, shift: true },
J: { keyCode: 74, shift: true },
K: { keyCode: 75, shift: true },
L: { keyCode: 76, shift: true },
M: { keyCode: 77, shift: true },
N: { keyCode: 78, shift: true },
O: { keyCode: 79, shift: true },
P: { keyCode: 80, shift: true },
Q: { keyCode: 81, shift: true },
R: { keyCode: 82, shift: true },
S: { keyCode: 83, shift: true },
T: { keyCode: 84, shift: true },
U: { keyCode: 85, shift: true },
V: { keyCode: 86, shift: true },
W: { keyCode: 87, shift: true },
X: { keyCode: 88, shift: true },
Y: { keyCode: 89, shift: true },
Z: { keyCode: 90, shift: true },
// Uppercase letters
A: { hidCode: keys.KeyA, shift: true },
B: { hidCode: keys.KeyB, shift: true },
C: { hidCode: keys.KeyC, shift: true },
D: { hidCode: keys.KeyD, shift: true },
E: { hidCode: keys.KeyE, shift: true },
F: { hidCode: keys.KeyF, shift: true },
G: { hidCode: keys.KeyG, shift: true },
H: { hidCode: keys.KeyH, shift: true },
I: { hidCode: keys.KeyI, shift: true },
J: { hidCode: keys.KeyJ, shift: true },
K: { hidCode: keys.KeyK, shift: true },
L: { hidCode: keys.KeyL, shift: true },
M: { hidCode: keys.KeyM, shift: true },
N: { hidCode: keys.KeyN, shift: true },
O: { hidCode: keys.KeyO, shift: true },
P: { hidCode: keys.KeyP, shift: true },
Q: { hidCode: keys.KeyQ, shift: true },
R: { hidCode: keys.KeyR, shift: true },
S: { hidCode: keys.KeyS, shift: true },
T: { hidCode: keys.KeyT, shift: true },
U: { hidCode: keys.KeyU, shift: true },
V: { hidCode: keys.KeyV, shift: true },
W: { hidCode: keys.KeyW, shift: true },
X: { hidCode: keys.KeyX, shift: true },
Y: { hidCode: keys.KeyY, shift: true },
Z: { hidCode: keys.KeyZ, shift: true },
// Numbers (no shift) - JS keyCodes 48-57
'0': { keyCode: 48, shift: false },
'1': { keyCode: 49, shift: false },
'2': { keyCode: 50, shift: false },
'3': { keyCode: 51, shift: false },
'4': { keyCode: 52, shift: false },
'5': { keyCode: 53, shift: false },
'6': { keyCode: 54, shift: false },
'7': { keyCode: 55, shift: false },
'8': { keyCode: 56, shift: false },
'9': { keyCode: 57, shift: false },
// Number row
'0': { hidCode: keys.Digit0, shift: false },
'1': { hidCode: keys.Digit1, shift: false },
'2': { hidCode: keys.Digit2, shift: false },
'3': { hidCode: keys.Digit3, shift: false },
'4': { hidCode: keys.Digit4, shift: false },
'5': { hidCode: keys.Digit5, shift: false },
'6': { hidCode: keys.Digit6, shift: false },
'7': { hidCode: keys.Digit7, shift: false },
'8': { hidCode: keys.Digit8, shift: false },
'9': { hidCode: keys.Digit9, shift: false },
// Shifted number row symbols (US layout)
')': { keyCode: 48, shift: true }, // Shift + 0
'!': { keyCode: 49, shift: true }, // Shift + 1
'@': { keyCode: 50, shift: true }, // Shift + 2
'#': { keyCode: 51, shift: true }, // Shift + 3
$: { keyCode: 52, shift: true }, // Shift + 4
'%': { keyCode: 53, shift: true }, // Shift + 5
'^': { keyCode: 54, shift: true }, // Shift + 6
'&': { keyCode: 55, shift: true }, // Shift + 7
'*': { keyCode: 56, shift: true }, // Shift + 8
'(': { keyCode: 57, shift: true }, // Shift + 9
// Shifted number row symbols
')': { hidCode: keys.Digit0, shift: true },
'!': { hidCode: keys.Digit1, shift: true },
'@': { hidCode: keys.Digit2, shift: true },
'#': { hidCode: keys.Digit3, shift: true },
'$': { hidCode: keys.Digit4, shift: true },
'%': { hidCode: keys.Digit5, shift: true },
'^': { hidCode: keys.Digit6, shift: true },
'&': { hidCode: keys.Digit7, shift: true },
'*': { hidCode: keys.Digit8, shift: true },
'(': { hidCode: keys.Digit9, shift: true },
// Punctuation and symbols (no shift) - US layout JS keyCodes
'-': { keyCode: 189, shift: false }, // Minus
'=': { keyCode: 187, shift: false }, // Equal
'[': { keyCode: 219, shift: false }, // Left bracket
']': { keyCode: 221, shift: false }, // Right bracket
'\\': { keyCode: 220, shift: false }, // Backslash
';': { keyCode: 186, shift: false }, // Semicolon
"'": { keyCode: 222, shift: false }, // Apostrophe/Quote
'`': { keyCode: 192, shift: false }, // Grave/Backtick
',': { keyCode: 188, shift: false }, // Comma
'.': { keyCode: 190, shift: false }, // Period
'/': { keyCode: 191, shift: false }, // Slash
// Punctuation and symbols
'-': { hidCode: keys.Minus, shift: false },
'=': { hidCode: keys.Equal, shift: false },
'[': { hidCode: keys.BracketLeft, shift: false },
']': { hidCode: keys.BracketRight, shift: false },
'\\': { hidCode: keys.Backslash, shift: false },
';': { hidCode: keys.Semicolon, shift: false },
"'": { hidCode: keys.Quote, shift: false },
'`': { hidCode: keys.Backquote, shift: false },
',': { hidCode: keys.Comma, shift: false },
'.': { hidCode: keys.Period, shift: false },
'/': { hidCode: keys.Slash, shift: false },
// Shifted punctuation and symbols (US layout)
_: { keyCode: 189, shift: true }, // Shift + Minus = Underscore
'+': { keyCode: 187, shift: true }, // Shift + Equal = Plus
'{': { keyCode: 219, shift: true }, // Shift + [ = {
'}': { keyCode: 221, shift: true }, // Shift + ] = }
'|': { keyCode: 220, shift: true }, // Shift + \ = |
':': { keyCode: 186, shift: true }, // Shift + ; = :
'"': { keyCode: 222, shift: true }, // Shift + ' = "
'~': { keyCode: 192, shift: true }, // Shift + ` = ~
'<': { keyCode: 188, shift: true }, // Shift + , = <
'>': { keyCode: 190, shift: true }, // Shift + . = >
'?': { keyCode: 191, shift: true }, // Shift + / = ?
// Shifted punctuation and symbols
_: { hidCode: keys.Minus, shift: true },
'+': { hidCode: keys.Equal, shift: true },
'{': { hidCode: keys.BracketLeft, shift: true },
'}': { hidCode: keys.BracketRight, shift: true },
'|': { hidCode: keys.Backslash, shift: true },
':': { hidCode: keys.Semicolon, shift: true },
'"': { hidCode: keys.Quote, shift: true },
'~': { hidCode: keys.Backquote, shift: true },
'<': { hidCode: keys.Comma, shift: true },
'>': { hidCode: keys.Period, shift: true },
'?': { hidCode: keys.Slash, shift: true },
// Whitespace and control characters
' ': { keyCode: 32, shift: false }, // Space
'\t': { keyCode: 9, shift: false }, // Tab
'\n': { keyCode: 13, shift: false }, // Enter (LF)
'\r': { keyCode: 13, shift: false }, // Enter (CR)
// Whitespace and control
' ': { hidCode: keys.Space, shift: false },
'\t': { hidCode: keys.Tab, shift: false },
'\n': { hidCode: keys.Enter, shift: false },
'\r': { hidCode: keys.Enter, shift: false },
}
/**
* Get the JavaScript keyCode and modifier state for a character
* Get HID usage code and modifier state for a character
* @param char - Single character to convert
* @returns CharKeyMapping or null if character is not mappable
*/
export function charToKey(char: string): CharKeyMapping | null {
if (char.length !== 1) return null
return charToKeyMap[char] || null
return charToKeyMap[char] ?? null
}
/**
@@ -138,7 +137,7 @@ export function charToKey(char: string): CharKeyMapping | null {
* @param char - Single character to check
*/
export function isTypableChar(char: string): boolean {
return char.length === 1 && char in charToKeyMap
return charToKey(char) !== null
}
/**

View File

@@ -191,6 +191,13 @@ export const hidKeyToModifierMask: Record<number, number> = {
0xe7: 0x80, // MetaRight
}
// Update modifier mask when a HID modifier key is pressed/released.
export function updateModifierMaskForHidKey(mask: number, hidKey: number, press: boolean): number {
const bit = hidKeyToModifierMask[hidKey] ?? 0
if (bit === 0) return mask
return press ? (mask | bit) : (mask & ~bit)
}
// Keys that latch (toggle state) instead of being held
export const latchingKeys = ['CapsLock', 'ScrollLock', 'NumLock'] as const
@@ -220,6 +227,23 @@ export function getModifierMask(keyName: string): number {
return 0
}
// Normalize browser-specific KeyboardEvent.code variants.
export function normalizeKeyboardCode(code: string, key: string): string {
if (code === 'IntlBackslash' && (key === '`' || key === '~')) return 'Backquote'
if (code === 'Backquote' && (key === '§' || key === '±')) return 'IntlBackslash'
if (code === 'IntlYen') return 'IntlBackslash'
if (code === 'OSLeft') return 'MetaLeft'
if (code === 'OSRight') return 'MetaRight'
if (code === '' && key === 'Shift') return 'ShiftRight'
return code
}
// Convert KeyboardEvent.code/key to USB HID usage code.
export function keyboardEventToHidCode(code: string, key: string): number | undefined {
const normalizedCode = normalizeKeyboardCode(code, key)
return keys[normalizedCode as KeyName]
}
// Decode modifier byte into individual states
export function decodeModifiers(modifier: number) {
return {