feat(keyboard): update keyboard layouts and key display mappings for multiple languages

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2026-05-08 09:59:04 +08:00
parent 7cef8baa0d
commit bf84660c8b
16 changed files with 190 additions and 43 deletions

View File

@@ -3,6 +3,7 @@ package hidrpc
import "fmt" import "fmt"
type Handler interface { type Handler interface {
HandleHandshake(version byte) error
HandleKeyboardReport(modifier byte, keys []byte) error HandleKeyboardReport(modifier byte, keys []byte) error
HandleKeypressReport(key byte, press bool) error HandleKeypressReport(key byte, press bool) error
HandleKeypressKeepAlive() error HandleKeypressKeepAlive() error
@@ -25,6 +26,11 @@ func (s *Server) HandleMessage(data []byte) error {
} }
switch msg.Type { switch msg.Type {
case MessageTypeHandshake:
if len(msg.Data) < 1 {
return fmt.Errorf("invalid handshake length: %d", len(msg.Data))
}
return s.handler.HandleHandshake(msg.Data[0])
case MessageTypeKeyboardReport: case MessageTypeKeyboardReport:
if len(msg.Data) < 7 { if len(msg.Data) < 7 {
return fmt.Errorf("invalid keyboard report length: %d", len(msg.Data)) return fmt.Errorf("invalid keyboard report length: %d", len(msg.Data))

View File

@@ -30,6 +30,10 @@ func MarshalKeyboardReport(modifier byte, keys []byte) []byte {
return data return data
} }
func MarshalHandshake(version byte) []byte {
return []byte{MessageTypeHandshake, version}
}
func MarshalKeypressReport(key byte, press bool) []byte { func MarshalKeypressReport(key byte, press bool) []byte {
data := make([]byte, 3) data := make([]byte, 3)
data[0] = MessageTypeKeypressReport data[0] = MessageTypeKeypressReport

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"reflect" "reflect"
"strings" "strings"
"time" "time"
@@ -211,15 +212,16 @@ func (u *UsbGadget) OpenKeyboardHidFile() error {
} }
func (u *UsbGadget) keyboardWriteHidFile(data []byte) error { func (u *UsbGadget) keyboardWriteHidFile(data []byte) error {
if err := u.openKeyboardHidFile(); err != nil { var parts []string
return err for _, b := range data {
parts = append(parts, fmt.Sprintf("\\x%02x", b))
} }
hexString := strings.Join(parts, "")
_, err := u.keyboardHidFile.Write(data) cmd := exec.Command("sh", "-c", fmt.Sprintf("echo -n -e '%s' > /dev/hidg0", hexString))
err := cmd.Run()
if err != nil { if err != nil {
u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0") u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
u.keyboardHidFile.Close()
u.keyboardHidFile = nil
return err return err
} }
u.resetLogSuppressionCounter("keyboardWriteHidFile") u.resetLogSuppressionCounter("keyboardWriteHidFile")

View File

@@ -99,11 +99,11 @@ export const chars = {
"Ẇ": { key: "KeyW", shift: true, accentKey: keyOverdot }, "Ẇ": { key: "KeyW", shift: true, accentKey: keyOverdot },
X: { key: "KeyX", shift: true }, X: { key: "KeyX", shift: true },
"Ẋ": { key: "KeyX", shift: true, accentKey: keyOverdot }, "Ẋ": { key: "KeyX", shift: true, accentKey: keyOverdot },
Y: { key: "KeyY", shift: true }, Y: { key: "KeyZ", shift: true },
"Ý": { key: "KeyY", shift: true, accentKey: keyAcute }, "Ý": { key: "KeyZ", shift: true, accentKey: keyAcute },
"Ẏ": { key: "KeyY", shift: true, accentKey: keyOverdot }, "Ẏ": { key: "KeyZ", shift: true, accentKey: keyOverdot },
Z: { key: "KeyZ", shift: true }, Z: { key: "KeyY", shift: true },
"Ż": { key: "KeyZ", shift: true, accentKey: keyOverdot }, "Ż": { key: "KeyY", shift: true, accentKey: keyOverdot },
a: { key: "KeyA" }, a: { key: "KeyA" },
"ä": { key: "KeyA", accentKey: keyTrema }, "ä": { key: "KeyA", accentKey: keyTrema },
"â": { key: "KeyA", accentKey: keyHat }, "â": { key: "KeyA", accentKey: keyHat },
@@ -191,10 +191,10 @@ export const chars = {
x: { key: "KeyX" }, x: { key: "KeyX" },
"#": { key: "KeyX", altRight: true }, "#": { key: "KeyX", altRight: true },
"ẋ": { key: "KeyX", accentKey: keyOverdot }, "ẋ": { key: "KeyX", accentKey: keyOverdot },
y: { key: "KeyY" }, y: { key: "KeyZ" },
"ẏ": { key: "KeyY", accentKey: keyOverdot }, "ẏ": { key: "KeyZ", accentKey: keyOverdot },
z: { key: "KeyZ" }, z: { key: "KeyY" },
"ż": { key: "KeyZ", accentKey: keyOverdot }, "ż": { key: "KeyY", accentKey: keyOverdot },
";": { key: "Backquote" }, ";": { key: "Backquote" },
"°": { key: "Backquote", shift: true, deadKey: true }, "°": { key: "Backquote", shift: true, deadKey: true },
"+": { key: "Digit1" }, "+": { key: "Digit1" },
@@ -245,11 +245,19 @@ export const chars = {
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
const cs_CZ_keyDisplayMap = {
...keyDisplayMap,
KeyY: "z",
KeyZ: "y",
"(KeyY)": "Z",
"(KeyZ)": "Y",
} as Record<string, string>;
export const cs_CZ: KeyboardLayout = { export const cs_CZ: KeyboardLayout = {
isoCode, isoCode,
name, name,
chars, chars,
keyDisplayMap, keyDisplayMap: cs_CZ_keyDisplayMap,
modifierDisplayMap, modifierDisplayMap,
virtualKeyboard, virtualKeyboard,
}; };

View File

@@ -1,5 +1,6 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { modifierDisplayMap, keyDisplayMap, virtualKeyboard } from "./en_US" import { modifierDisplayMap, keyDisplayMap, virtualKeyboard } from "./en_US"
export { keyDisplayMap } from "./en_US";
const name = "Schwiizerdütsch"; const name = "Schwiizerdütsch";
const isoCode = "de-CH"; const isoCode = "de-CH";
@@ -166,11 +167,19 @@ export const chars = {
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
export const de_CH_keyDisplayMap = {
...keyDisplayMap,
KeyY: "z",
KeyZ: "y",
"(KeyY)": "Z",
"(KeyZ)": "Y",
} as Record<string, string>;
export const de_CH: KeyboardLayout = { export const de_CH: KeyboardLayout = {
isoCode, isoCode,
name, name,
chars, chars,
keyDisplayMap, keyDisplayMap: de_CH_keyDisplayMap,
modifierDisplayMap, modifierDisplayMap,
virtualKeyboard, virtualKeyboard,
}; };

View File

@@ -153,11 +153,19 @@ export const chars = {
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
const de_DE_keyDisplayMap = {
...keyDisplayMap,
KeyY: "z",
KeyZ: "y",
"(KeyY)": "Z",
"(KeyZ)": "Y",
} as Record<string, string>;
export const de_DE: KeyboardLayout = { export const de_DE: KeyboardLayout = {
isoCode, isoCode,
name, name,
chars, chars,
keyDisplayMap, keyDisplayMap: de_DE_keyDisplayMap,
modifierDisplayMap, modifierDisplayMap,
virtualKeyboard, virtualKeyboard,
}; };

View File

@@ -58,10 +58,10 @@ export const chars = {
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave }, "Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde }, "Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true }, V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true }, W: { key: "KeyZ", shift: true },
X: { key: "KeyX", shift: true }, X: { key: "KeyX", shift: true },
Y: { key: "KeyZ", shift: true }, Y: { key: "KeyY", shift: true },
Z: { key: "KeyY", shift: true }, Z: { key: "KeyW", shift: true },
a: { key: "KeyQ" }, a: { key: "KeyQ" },
"ä": { key: "KeyQ", accentKey: keyTrema }, "ä": { key: "KeyQ", accentKey: keyTrema },
"â": { key: "KeyQ", accentKey: keyHat }, "â": { key: "KeyQ", accentKey: keyHat },
@@ -106,10 +106,10 @@ export const chars = {
"ú": { key: "KeyU", accentKey: keyAcute }, "ú": { key: "KeyU", accentKey: keyAcute },
"ũ": { key: "KeyU", accentKey: keyTilde }, "ũ": { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" }, v: { key: "KeyV" },
w: { key: "KeyW" }, w: { key: "KeyZ" },
x: { key: "KeyX" }, x: { key: "KeyX" },
y: { key: "KeyZ" }, y: { key: "KeyY" },
z: { key: "KeyY" }, z: { key: "KeyW" },
"²": { key: "Backquote" }, "²": { key: "Backquote" },
"³": { key: "Backquote", shift: true }, "³": { key: "Backquote", shift: true },
"&": { key: "Digit1" }, "&": { key: "Digit1" },
@@ -168,11 +168,27 @@ export const chars = {
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
const fr_BE_keyDisplayMap = {
...keyDisplayMap,
KeyA: "q",
KeyQ: "a",
KeyW: "z",
KeyZ: "w",
Semicolon: "m",
KeyM: ",",
"(KeyA)": "Q",
"(KeyQ)": "A",
"(KeyW)": "Z",
"(KeyZ)": "W",
"(Semicolon)": "M",
"(KeyM)": "?",
} as Record<string, string>;
export const fr_BE: KeyboardLayout = { export const fr_BE: KeyboardLayout = {
isoCode, isoCode,
name, name,
chars, chars,
keyDisplayMap, keyDisplayMap: fr_BE_keyDisplayMap,
modifierDisplayMap, modifierDisplayMap,
virtualKeyboard, virtualKeyboard,
}; };

View File

@@ -1,6 +1,6 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { chars as chars_de_CH } from "./de_CH" import { chars as chars_de_CH, de_CH_keyDisplayMap } from "./de_CH"
import { modifierDisplayMap, keyDisplayMap, virtualKeyboard } from "./en_US" import { modifierDisplayMap, virtualKeyboard } from "./en_US"
const name = "Français de Suisse"; const name = "Français de Suisse";
const isoCode = "fr-CH"; const isoCode = "fr-CH";
@@ -15,11 +15,21 @@ export const chars = {
"ä": { key: "Quote", shift: true }, "ä": { key: "Quote", shift: true },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
const fr_CH_keyDisplayMap = {
...de_CH_keyDisplayMap,
BracketLeft: "è",
"(BracketLeft)": "ü",
Semicolon: "é",
"(Semicolon)": "ö",
Quote: "à",
"(Quote)": "ä",
} as Record<string, string>;
export const fr_CH: KeyboardLayout = { export const fr_CH: KeyboardLayout = {
isoCode, isoCode,
name, name,
chars, chars,
keyDisplayMap, keyDisplayMap: fr_CH_keyDisplayMap,
modifierDisplayMap, modifierDisplayMap,
virtualKeyboard, virtualKeyboard,
}; };

View File

@@ -140,11 +140,27 @@ export const chars = {
Tab: { key: "Tab" }, Tab: { key: "Tab" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
const fr_FR_keyDisplayMap = {
...keyDisplayMap,
KeyA: "q",
KeyQ: "a",
KeyW: "z",
KeyZ: "w",
Semicolon: "m",
KeyM: ",",
"(KeyA)": "Q",
"(KeyQ)": "A",
"(KeyW)": "Z",
"(KeyZ)": "W",
"(Semicolon)": "M",
"(KeyM)": "?",
} as Record<string, string>;
export const fr_FR: KeyboardLayout = { export const fr_FR: KeyboardLayout = {
isoCode, isoCode,
name, name,
chars, chars,
keyDisplayMap, keyDisplayMap: fr_FR_keyDisplayMap,
modifierDisplayMap, modifierDisplayMap,
virtualKeyboard, virtualKeyboard,
}; };

View File

@@ -60,8 +60,8 @@ export const chars = {
V: { key: "KeyV", shift: true }, V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true }, W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true }, X: { key: "KeyX", shift: true },
Y: { key: "KeyZ", shift: true }, Y: { key: "KeyY", shift: true },
Z: { key: "KeyY", shift: true }, Z: { key: "KeyZ", shift: true },
a: { key: "KeyA" }, a: { key: "KeyA" },
"ä": { key: "KeyA", accentKey: keyTrema }, "ä": { key: "KeyA", accentKey: keyTrema },
"á": { key: "KeyA", accentKey: keyAcute }, "á": { key: "KeyA", accentKey: keyAcute },
@@ -112,8 +112,8 @@ export const chars = {
v: { key: "KeyV" }, v: { key: "KeyV" },
w: { key: "KeyW" }, w: { key: "KeyW" },
x: { key: "KeyX" }, x: { key: "KeyX" },
y: { key: "KeyZ" }, y: { key: "KeyY" },
z: { key: "KeyY" }, z: { key: "KeyZ" },
"|": { key: "Backquote" }, "|": { key: "Backquote" },
"§": { key: "Backquote", shift: true }, "§": { key: "Backquote", shift: true },
1: { key: "Digit1" }, 1: { key: "Digit1" },

View File

@@ -146,12 +146,19 @@ export const chars = {
Delete: { key: "Delete" }, Delete: { key: "Delete" },
} as Record<string, KeyCombo>; } as Record<string, KeyCombo>;
const sl_SI_keyDisplayMap = {
...en_US.keyDisplayMap,
KeyY: "z",
KeyZ: "y",
"(KeyY)": "Z",
"(KeyZ)": "Y",
} as Record<string, string>;
export const sl_SI: KeyboardLayout = { export const sl_SI: KeyboardLayout = {
isoCode: isoCode, isoCode: isoCode,
name: name, name: name,
chars: chars, chars: chars,
// TODO need to localize these maps and layouts keyDisplayMap: sl_SI_keyDisplayMap,
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap, modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard, virtualKeyboard: en_US.virtualKeyboard,
}; };

View File

@@ -6,7 +6,7 @@ import { CloseOutlined } from '@ant-design/icons';
import { useReactAt } from "i18n-auto-extractor/react"; import { useReactAt } from "i18n-auto-extractor/react";
import ScrollThrottlingSelect, { Option } from "@components/ScrollThrottlingSelect"; import ScrollThrottlingSelect, { Option } from "@components/ScrollThrottlingSelect";
import { layouts } from "@/keyboardLayouts"; import { layouts, keyboards } from "@/keyboardLayouts";
import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores"; import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications"; import notifications from "@/notifications";
@@ -24,11 +24,21 @@ const KeyboardPanel: React.FC = () => {
const [layoutOptions, setLayoutOptions] = useState<Option[]>(); const [layoutOptions, setLayoutOptions] = useState<Option[]>();
const [maxShowCount, setMaxShowCount] = useState(3); const [maxShowCount, setMaxShowCount] = useState(3);
const layoutAbbrevMap = useMemo(() => {
const map: Record<string, string> = {};
keyboards.forEach(kb => {
const oldCode = kb.isoCode.replace("-", "_");
map[oldCode] = oldCode;
});
return map;
}, []);
useEffect(() => { useEffect(() => {
const curLayoutOptions = (() => { const curLayoutOptions = (() => {
const options = Object.entries(layouts).map(([code, language]) => ({ const options = Object.entries(layouts).map(([code, language]) => ({
value: code, value: code,
label: language, label: `${language} (${layoutAbbrevMap[code] || code})`,
})); }));
const currentLayout = keyboardLayout ?? ""; const currentLayout = keyboardLayout ?? "";
@@ -47,7 +57,7 @@ const KeyboardPanel: React.FC = () => {
return options; return options;
})(); })();
setLayoutOptions(curLayoutOptions); setLayoutOptions(curLayoutOptions);
}, [layouts, keyboardLayout]); }, [layouts, keyboardLayout, layoutAbbrevMap]);
const safeKeyboardLayout = useMemo(() => { const safeKeyboardLayout = useMemo(() => {
if (keyboardLayout && keyboardLayout.length > 0) if (keyboardLayout && keyboardLayout.length > 0)

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Button as AntdButton, Typography } from "antd"; import { Button as AntdButton, Typography } from "antd";
import { useReactAt } from "i18n-auto-extractor/react"; import { useReactAt } from "i18n-auto-extractor/react";
import KeyboardSVG from "@assets/second/keyboard.svg?react"; import KeyboardSVG from "@assets/second/keyboard.svg?react";
@@ -25,6 +25,7 @@ import {
useVpnStore, useVpnStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { keyboards } from "@/keyboardLayouts";
import { import {
button_primary_color, button_primary_color,
dark_bd_style, dark_bd_style,
@@ -47,7 +48,13 @@ const views = [
export default function BottomBarMobile() { export default function BottomBarMobile() {
const { $at } = useReactAt(); const { $at } = useReactAt();
const keyboardLedState = useHidStore(state => state.keyboardLedState); const keyboardLedState = useHidStore(state => state.keyboardLedState);
const keyboardLayout = useSettingsStore(state => state.keyboardLayout);
const { isDark } = useTheme(); const { isDark } = useTheme();
const layoutAbbrev = useMemo(() => {
if (!keyboardLayout) return "en_US";
return keyboardLayout;
}, [keyboardLayout]);
const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
const toggleSidebarView = useUiStore(state => state.toggleSidebarView); const toggleSidebarView = useUiStore(state => state.toggleSidebarView);
const forceHttp = useSettingsStore(state => state.forceHttp); const forceHttp = useSettingsStore(state => state.forceHttp);
@@ -144,6 +151,7 @@ export default function BottomBarMobile() {
<LedStatusButton ledState={keyboardLedState?.num_lock} text={$at("Num")} /> <LedStatusButton ledState={keyboardLedState?.num_lock} text={$at("Num")} />
<LedStatusButton ledState={keyboardLedState?.caps_lock} text={$at("Caps")} /> <LedStatusButton ledState={keyboardLedState?.caps_lock} text={$at("Caps")} />
<LedStatusButton ledState={keyboardLedState?.scroll_lock} text={$at("Scroll")} /> <LedStatusButton ledState={keyboardLedState?.scroll_lock} text={$at("Scroll")} />
<span className="pl-2 text-xs opacity-70">{layoutAbbrev}</span>
</div> </div>
<div className="w-[20%] flex flex-row flex-wrap items-center justify-end"> <div className="w-[20%] flex flex-row flex-wrap items-center justify-end">
<AntdButton <AntdButton

View File

@@ -27,6 +27,7 @@ import {
useVpnStore, useVpnStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
import { keyboards } from "@/keyboardLayouts";
import BottomPopoverButton from "@components/PopoverButton"; import BottomPopoverButton from "@components/PopoverButton";
import MousePanel from "@components/MousePanel"; import MousePanel from "@components/MousePanel";
import KeyboardPanel from "@/layout/components_bottom/keyboard/KeyboardPanel"; import KeyboardPanel from "@/layout/components_bottom/keyboard/KeyboardPanel";
@@ -57,8 +58,14 @@ export default function BottomBarPC() {
const keyboardLedState = useHidStore(state => state.keyboardLedState); const keyboardLedState = useHidStore(state => state.keyboardLedState);
const keyboardLayout = useSettingsStore(state => state.keyboardLayout);
const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse); const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse);
const layoutAbbrev = useMemo(() => {
if (!keyboardLayout) return "en_US";
return keyboardLayout;
}, [keyboardLayout]);
const [hostname, setHostname] = useState(""); const [hostname, setHostname] = useState("");
const [send] = useJsonRpc(); const [send] = useJsonRpc();
const peerConnection = useRTCStore(state => state.peerConnection); const peerConnection = useRTCStore(state => state.peerConnection);
@@ -164,6 +171,7 @@ export default function BottomBarPC() {
ledState={keyboardLedState?.scroll_lock} ledState={keyboardLedState?.scroll_lock}
text={$at("Scroll")} text={$at("Scroll")}
/> />
<span className="pl-1 text-xs opacity-70" style={{ fontSize: 12 }}>{layoutAbbrev}</span>
</div> </div>
} }
align="left" align="left"

View File

@@ -3,6 +3,7 @@ import { useCallback } from "react";
import useKeyboard from "@/hooks/useKeyboard"; import useKeyboard from "@/hooks/useKeyboard";
import { useHidStore, useSettingsStore, useUiStore } from "@/hooks/stores"; import { useHidStore, useSettingsStore, useUiStore } from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings"; import { keys, modifiers } from "@/keyboardMappings";
import { keyboards } from "@/keyboardLayouts";
import { eventMatchesShortcut } from "@/utils/shortcuts"; import { eventMatchesShortcut } from "@/utils/shortcuts";
export const useKeyboardEvents = ( export const useKeyboardEvents = (
@@ -18,6 +19,28 @@ export const useKeyboardEvents = (
const pasteShortcutEnabled = useSettingsStore(state => state.pasteShortcutEnabled); const pasteShortcutEnabled = useSettingsStore(state => state.pasteShortcutEnabled);
const pasteShortcut = useSettingsStore(state => state.pasteShortcut); const pasteShortcut = useSettingsStore(state => state.pasteShortcut);
const isOcrMode = useUiStore(state => state.isOcrMode); const isOcrMode = useUiStore(state => state.isOcrMode);
const keyboardLayout = useSettingsStore(state => state.keyboardLayout);
const remapCode = useCallback((code: string, key: string): string => {
const modifierCodes = ["ControlLeft", "ControlRight", "ShiftLeft", "ShiftRight", "AltLeft", "AltRight", "MetaLeft", "MetaRight", "CapsLock", "Tab", "Enter", "Backspace", "Delete", "Insert", "Home", "End", "PageUp", "PageDown", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "PrintScreen", "ScrollLock", "Pause", "ContextMenu", "Menu"];
if (modifierCodes.includes(code)) return code;
if (code.startsWith("Digit") || code.startsWith("Numpad")) return code;
if (code.startsWith("Key") && code.length === 4) {
const letter = code.charAt(3);
if (letter >= "A" && letter <= "Z") {
const isoCode = (keyboardLayout || "en-US").replace("_", "-");
const layout = keyboards.find(k => k.isoCode === isoCode);
if (layout && layout.chars) {
const charLower = key.toLowerCase();
const charEntry = layout.chars[charLower] || layout.chars[key];
if (charEntry && charEntry.key && typeof charEntry.key === "string" && charEntry.key !== code) {
return charEntry.key;
}
}
}
}
return code;
}, [keyboardLayout]);
const handleModifierKeys = useCallback((e: KeyboardEvent, activeModifiers: number[]) => { const handleModifierKeys = useCallback((e: KeyboardEvent, activeModifiers: number[]) => {
const { shiftKey, ctrlKey, altKey, metaKey } = e; const { shiftKey, ctrlKey, altKey, metaKey } = e;
@@ -59,6 +82,8 @@ export const useKeyboardEvents = (
code = "IntlBackslash"; code = "IntlBackslash";
} }
code = remapCode(code, key);
const newKeys = [...prev.activeKeys, keys[code]].filter(Boolean); const newKeys = [...prev.activeKeys, keys[code]].filter(Boolean);
const newModifiers = handleModifierKeys(e, [...prev.activeModifiers, modifiers[code]]); const newModifiers = handleModifierKeys(e, [...prev.activeModifiers, modifiers[code]]);
@@ -77,13 +102,15 @@ export const useKeyboardEvents = (
// Still update the full state for legacy compatibility and UI display // Still update the full state for legacy compatibility and UI display
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]); sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
}, [handleModifierKeys, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]); }, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, pasteShortcutEnabled, pasteShortcut, pasteCaptureRef, isReinitializingGadget, isOcrMode]);
const keyUpHandler = useCallback((e: KeyboardEvent) => { const keyUpHandler = useCallback((e: KeyboardEvent) => {
if (isOcrMode) return; if (isOcrMode) return;
if (isReinitializingGadget) return; if (isReinitializingGadget) return;
e.preventDefault(); e.preventDefault();
const prev = useHidStore.getState(); const prev = useHidStore.getState();
const key = e.key;
let code = remapCode(e.code, key);
if (!isKeyboardLedManagedByHost) { if (!isKeyboardLedManagedByHost) {
setIsNumLockActive(e.getModifierState("NumLock")); setIsNumLockActive(e.getModifierState("NumLock"));
@@ -91,21 +118,21 @@ export const useKeyboardEvents = (
setIsScrollLockActive(e.getModifierState("ScrollLock")); setIsScrollLockActive(e.getModifierState("ScrollLock"));
} }
const newKeys = prev.activeKeys.filter(k => k !== keys[e.code]).filter(Boolean); const newKeys = prev.activeKeys.filter(k => k !== keys[code]).filter(Boolean);
const newModifiers = handleModifierKeys( const newModifiers = handleModifierKeys(
e, e,
prev.activeModifiers.filter(k => k !== modifiers[e.code]), prev.activeModifiers.filter(k => k !== modifiers[code]),
); );
// Send per-key release event // Send per-key release event
const hidKey = keys[e.code]; const hidKey = keys[code];
if (hidKey !== undefined) { if (hidKey !== undefined) {
sendKeypress(hidKey, false); sendKeypress(hidKey, false);
} }
// Still update the full state for legacy compatibility and UI display // Still update the full state for legacy compatibility and UI display
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]); sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
}, [handleModifierKeys, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode]); }, [handleModifierKeys, remapCode, sendKeyboardEvent, sendKeypress, isKeyboardLedManagedByHost, setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive, isOcrMode, isReinitializingGadget]);
const setupKeyboardEvents = useCallback(() => { const setupKeyboardEvents = useCallback(() => {
const abortController = new AbortController(); const abortController = new AbortController();

8
usb.go
View File

@@ -157,6 +157,14 @@ type hidRpcHandler struct {
session *Session session *Session
} }
func (h *hidRpcHandler) HandleHandshake(version byte) error {
if h.session.HidChannel != nil {
handshakeData := hidrpc.MarshalHandshake(version)
return h.session.HidChannel.Send(handshakeData)
}
return nil
}
func (h *hidRpcHandler) HandleKeyboardReport(modifier byte, keys []byte) error { func (h *hidRpcHandler) HandleKeyboardReport(modifier byte, keys []byte) error {
return rpcKeyboardReport(modifier, keys) return rpcKeyboardReport(modifier, keys)
} }