From a5b07b4862debff72314a711ebe0871946737be8 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Wed, 21 May 2025 18:40:23 -0500 Subject: [PATCH] refactor(ui): Refactor the keyboardLayouts Add missing keyboard mappings for most layouts Change pasteModel.tsx to use the new structure and vastly clarified the way that keys are emitted. Make each layout export just the KeyboardLayout object (which is a package of isoCode, name, and chars) Made keyboardLayouts.ts export a function to select keyboard by `isoCode`, export the keyboards as label . value pairs (for a select list) and the list of keyboards. Changed devices.$id.settings.keyboard.tsx use the exported keyboard option list. --- ui/src/components/Terminal.tsx | 14 ++-- ui/src/components/popovers/PasteModal.tsx | 73 ++++++++++--------- .../components/popovers/WakeOnLan/Index.tsx | 12 +-- ui/src/hooks/stores.ts | 2 +- ui/src/keyboardLayouts.ts | 69 +++++++----------- ui/src/keyboardLayouts/cs_CZ.ts | 12 ++- ui/src/keyboardLayouts/de_CH.ts | 12 ++- ui/src/keyboardLayouts/de_DE.ts | 12 ++- ui/src/keyboardLayouts/en_UK.ts | 12 ++- ui/src/keyboardLayouts/en_US.ts | 12 ++- ui/src/keyboardLayouts/es_ES.ts | 12 ++- ui/src/keyboardLayouts/fr_BE.ts | 12 ++- ui/src/keyboardLayouts/fr_CH.ts | 16 ++-- ui/src/keyboardLayouts/fr_FR.ts | 12 ++- ui/src/keyboardLayouts/it_IT.ts | 12 ++- ui/src/keyboardLayouts/nb_NO.ts | 12 ++- ui/src/keyboardLayouts/sv_SE.ts | 12 ++- ui/src/keyboardMappings.ts | 52 ++++++++----- .../routes/devices.$id.settings.keyboard.tsx | 4 +- ui/src/routes/devices.$id.settings.tsx | 2 +- ui/src/routes/devices.$id.tsx | 4 +- 21 files changed, 232 insertions(+), 148 deletions(-) diff --git a/ui/src/components/Terminal.tsx b/ui/src/components/Terminal.tsx index 5451afe..f5d662d 100644 --- a/ui/src/components/Terminal.tsx +++ b/ui/src/components/Terminal.tsx @@ -67,19 +67,19 @@ function Terminal({ }) { const enableTerminal = useUiStore(state => state.terminalType == type); const setTerminalType = useUiStore(state => state.setTerminalType); - const setDisableKeyboardFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); + const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const { instance, ref } = useXTerm({ options: TERMINAL_CONFIG }); useEffect(() => { setTimeout(() => { - setDisableKeyboardFocusTrap(enableTerminal); + setDisableVideoFocusTrap(enableTerminal); }, 500); return () => { - setDisableKeyboardFocusTrap(false); + setDisableVideoFocusTrap(false); }; - }, [ref, instance, enableTerminal, setDisableKeyboardFocusTrap, type]); + }, [enableTerminal, setDisableVideoFocusTrap]); const readyState = dataChannel.readyState; useEffect(() => { @@ -116,7 +116,7 @@ function Terminal({ const { domEvent } = e; if (domEvent.key === "Escape") { setTerminalType("none"); - setDisableKeyboardFocusTrap(false); + setDisableVideoFocusTrap(false); domEvent.preventDefault(); } }); @@ -131,7 +131,7 @@ function Terminal({ onDataHandler.dispose(); onKeyHandler.dispose(); }; - }, [instance, dataChannel, readyState, setDisableKeyboardFocusTrap, setTerminalType]); + }, [dataChannel, instance, readyState, setDisableVideoFocusTrap, setTerminalType]); useEffect(() => { if (!instance) return; @@ -158,7 +158,7 @@ function Terminal({ return () => { window.removeEventListener("resize", handleResize); }; - }, [ref, instance]); + }, [instance]); return (
{ - return { keys, modifier }; +const hidKeyboardPayload = (modifier: number, keys: number[]) => { + return { modifier, keys }; }; const modifierCode = (shift?: boolean, altRight?: boolean) => { @@ -62,49 +62,56 @@ export default function PasteModal() { const onConfirmPaste = useCallback(async () => { setPasteMode(false); setDisableVideoFocusTrap(false); + if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return; - if (!safeKeyboardLayout) return; - if (!chars[safeKeyboardLayout]) return; + const keyboard: KeyboardLayout = selectedKeyboard(safeKeyboardLayout); + if (!keyboard) return; + const text = TextAreaRef.current.value; try { for (const char of text) { - const { key, shift, altRight, deadKey, accentKey } = chars[safeKeyboardLayout][char] + const keyprops = keyboard.chars[char]; + if (!keyprops) continue; + + const { key, shift, altRight, deadKey, accentKey } = keyprops; if (!key) continue; - const keyz = [ keys[key] ]; - const modz = [ modifierCode(shift, altRight) ]; - - if (deadKey) { - keyz.push(keys["Space"]); - modz.push(noModifier); - } + // if this is an accented character, we need to send that accent FIRST if (accentKey) { - keyz.unshift(keys[accentKey.key]) - modz.unshift(modifierCode(accentKey.shift, accentKey.altRight)) + await sendKeystroke({modifier: modifierCode(accentKey.shift, accentKey.altRight), keys: [ keys[accentKey.key] ] }) } - for (const [index, kei] of keyz.entries()) { - await new Promise((resolve, reject) => { - send( - "keyboardReport", - hidKeyboardPayload([kei], modz[index]), - params => { - if ("error" in params) return reject(params.error); - send("keyboardReport", hidKeyboardPayload([], 0), params => { - if ("error" in params) return reject(params.error); - resolve(); - }); - }, - ); - }); + // now send the actual key + await sendKeystroke({ modifier: modifierCode(shift, altRight), keys: [ keys[key] ]}); + + // if what was requested was a dead key, we need to send an unmodified space to emit + // just the accent character + if (deadKey) { + await sendKeystroke({ modifier: noModifier, keys: [ keys["Space"] ] }); } + + // now send a message with no keys down to "release" the keys + await sendKeystroke({ modifier: 0, keys: [] }); } } catch (error) { - console.error(error); + console.error("Failed to paste text:", error); notifications.error("Failed to paste text"); } - }, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode, safeKeyboardLayout]); + + async function sendKeystroke(stroke: KeyStroke) { + await new Promise((resolve, reject) => { + send( + "keyboardReport", + hidKeyboardPayload(stroke.modifier, stroke.keys), + params => { + if ("error" in params) return reject(params.error); + resolve(); + } + ); + }); + } + }, [rpcDataChannel?.readyState, safeKeyboardLayout, send, setDisableVideoFocusTrap, setPasteMode]); useEffect(() => { if (TextAreaRef.current) { @@ -154,7 +161,7 @@ export default function PasteModal() { // @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments [...new Intl.Segmenter().segment(value)] .map(x => x.segment) - .filter(char => !chars[safeKeyboardLayout][char]), + .filter(char => !selectedKeyboard(safeKeyboardLayout).chars[char]), ), ]; @@ -175,7 +182,7 @@ export default function PasteModal() {

- Sending text using keyboard layout: {layouts[safeKeyboardLayout]} + Sending text using keyboard layout: {selectedKeyboard(safeKeyboardLayout).name}

diff --git a/ui/src/components/popovers/WakeOnLan/Index.tsx b/ui/src/components/popovers/WakeOnLan/Index.tsx index f4f4951..1cf7f18 100644 --- a/ui/src/components/popovers/WakeOnLan/Index.tsx +++ b/ui/src/components/popovers/WakeOnLan/Index.tsx @@ -14,7 +14,7 @@ import AddDeviceForm from "./AddDeviceForm"; export default function WakeOnLanModal() { const [storedDevices, setStoredDevices] = useState([]); const [showAddForm, setShowAddForm] = useState(false); - const setDisableFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); + const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); @@ -24,9 +24,9 @@ export default function WakeOnLanModal() { const [addDeviceErrorMessage, setAddDeviceErrorMessage] = useState(null); const onCancelWakeOnLanModal = useCallback(() => { + setDisableVideoFocusTrap(false); close(); - setDisableFocusTrap(false); - }, [close, setDisableFocusTrap]); + }, [close, setDisableVideoFocusTrap]); const onSendMagicPacket = useCallback( (macAddress: string) => { @@ -43,12 +43,12 @@ export default function WakeOnLanModal() { } } else { notifications.success("Magic Packet sent successfully"); - setDisableFocusTrap(false); + setDisableVideoFocusTrap(false); close(); } }); }, - [close, rpcDataChannel?.readyState, send, setDisableFocusTrap], + [close, rpcDataChannel?.readyState, send, setDisableVideoFocusTrap], ); const syncStoredDevices = useCallback(() => { @@ -78,7 +78,7 @@ export default function WakeOnLanModal() { } }); }, - [storedDevices, send, syncStoredDevices], + [send, storedDevices, syncStoredDevices], ); const onAddDevice = useCallback( diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index 6bc7e17..b7ad53e 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -941,5 +941,5 @@ export const useMacrosStore = create((set, get) => ({ } finally { set({ loading: false }); } - }, + } })); diff --git a/ui/src/keyboardLayouts.ts b/ui/src/keyboardLayouts.ts index 3b835b1..4ae3ad9 100644 --- a/ui/src/keyboardLayouts.ts +++ b/ui/src/keyboardLayouts.ts @@ -1,45 +1,32 @@ -import { chars as chars_fr_BE, name as name_fr_BE } from "@/keyboardLayouts/fr_BE" -import { chars as chars_cs_CZ, name as name_cs_CZ } from "@/keyboardLayouts/cs_CZ" -import { chars as chars_en_UK, name as name_en_UK } from "@/keyboardLayouts/en_UK" -import { chars as chars_en_US, name as name_en_US } from "@/keyboardLayouts/en_US" -import { chars as chars_fr_FR, name as name_fr_FR } from "@/keyboardLayouts/fr_FR" -import { chars as chars_de_DE, name as name_de_DE } from "@/keyboardLayouts/de_DE" -import { chars as chars_it_IT, name as name_it_IT } from "@/keyboardLayouts/it_IT" -import { chars as chars_nb_NO, name as name_nb_NO } from "@/keyboardLayouts/nb_NO" -import { chars as chars_es_ES, name as name_es_ES } from "@/keyboardLayouts/es_ES" -import { chars as chars_sv_SE, name as name_sv_SE } from "@/keyboardLayouts/sv_SE" -import { chars as chars_fr_CH, name as name_fr_CH } from "@/keyboardLayouts/fr_CH" -import { chars as chars_de_CH, name as name_de_CH } from "@/keyboardLayouts/de_CH" +export interface KeyStroke { modifier: number; keys: number[]; } +export interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean } +export interface KeyCombo extends KeyInfo { deadKey?: boolean, accentKey?: KeyInfo } +export interface KeyboardLayout { isoCode: string, name: string, chars: Record } -interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean } -export type KeyCombo = KeyInfo & { deadKey?: boolean, accentKey?: KeyInfo } +// to add a new layout, create a file like the above and add it to the list +import { cs_CZ } from "@/keyboardLayouts/cs_CZ" +import { de_CH } from "@/keyboardLayouts/de_CH" +import { de_DE } from "@/keyboardLayouts/de_DE" +import { en_US } from "@/keyboardLayouts/en_US" +import { en_UK } from "@/keyboardLayouts/en_UK" +import { es_ES } from "@/keyboardLayouts/es_ES" +import { fr_BE } from "@/keyboardLayouts/fr_BE" +import { fr_CH } from "@/keyboardLayouts/fr_CH" +import { fr_FR } from "@/keyboardLayouts/fr_FR" +import { it_IT } from "@/keyboardLayouts/it_IT" +import { nb_NO } from "@/keyboardLayouts/nb_NO" +import { sv_SE } from "@/keyboardLayouts/sv_SE" -export const layouts: Record = { - be_FR: name_fr_BE, - cs_CZ: name_cs_CZ, - en_UK: name_en_UK, - en_US: name_en_US, - fr_FR: name_fr_FR, - de_DE: name_de_DE, - it_IT: name_it_IT, - nb_NO: name_nb_NO, - es_ES: name_es_ES, - sv_SE: name_sv_SE, - fr_CH: name_fr_CH, - de_CH: name_de_CH, -} +export const keyboards: KeyboardLayout[] = [ cs_CZ, de_CH, de_DE, en_UK, en_US, es_ES, fr_BE, fr_CH, fr_FR, it_IT, nb_NO, sv_SE ]; -export const chars: Record> = { - be_FR: chars_fr_BE, - cs_CZ: chars_cs_CZ, - en_UK: chars_en_UK, - en_US: chars_en_US, - fr_FR: chars_fr_FR, - de_DE: chars_de_DE, - it_IT: chars_it_IT, - nb_NO: chars_nb_NO, - es_ES: chars_es_ES, - sv_SE: chars_sv_SE, - fr_CH: chars_fr_CH, - de_CH: chars_de_CH, +export const selectedKeyboard = (isoCode: string): KeyboardLayout => { + // fallback to original behaviour of en-US if no isoCode given + return keyboards.find(keyboard => keyboard.isoCode == isoCode) + ?? keyboards.find(keyboard => keyboard.isoCode == "en-US")!; }; + +export const keyboardOptions = () => { + return keyboards.map((keyboard) => { + return { label: keyboard.name, value: keyboard.isoCode } + }); +} diff --git a/ui/src/keyboardLayouts/cs_CZ.ts b/ui/src/keyboardLayouts/cs_CZ.ts index a289d75..e4f8822 100644 --- a/ui/src/keyboardLayouts/cs_CZ.ts +++ b/ui/src/keyboardLayouts/cs_CZ.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Čeština"; +const name = "Čeština"; const keyTrema = { key: "Backslash" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter @@ -13,7 +13,7 @@ const keyOverdot = { key: "Digit8", shift: true, altRight: true } // overdot (do const keyHook = { key: "Digit6", shift: true, altRight: true } // ogonoek (little hook), mark ˛ placed beneath a letter const keyCedille = { key: "Equal", shift: true, altRight: true } // accent cedille (cedilla), mark ¸ placed beneath a letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -242,3 +242,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const cs_CZ: KeyboardLayout = { + isoCode: "cs-CZ", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/de_CH.ts b/ui/src/keyboardLayouts/de_CH.ts index 06c0619..4743bcf 100644 --- a/ui/src/keyboardLayouts/de_CH.ts +++ b/ui/src/keyboardLayouts/de_CH.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Schwiizerdütsch"; +const name = "Schwiizerdütsch"; const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Minus", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "Equal" } // accent circonflexe (accent hat), mark ^ place const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "Equal", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -163,3 +163,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const de_CH: KeyboardLayout = { + isoCode: "de-CH", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/de_DE.ts b/ui/src/keyboardLayouts/de_DE.ts index 87a8d2e..89b7eed 100644 --- a/ui/src/keyboardLayouts/de_DE.ts +++ b/ui/src/keyboardLayouts/de_DE.ts @@ -1,12 +1,12 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Deutsch"; +const name = "Deutsch"; const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter const keyHat = { key: "Backquote" } // accent circonflexe (accent hat), mark ^ placed above the letter const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Â": { key: "KeyA", shift: true, accentKey: keyHat }, @@ -150,3 +150,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const de_DE: KeyboardLayout = { + isoCode: "de-DE", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/en_UK.ts b/ui/src/keyboardLayouts/en_UK.ts index ed0c8dd..a5ef779 100644 --- a/ui/src/keyboardLayouts/en_UK.ts +++ b/ui/src/keyboardLayouts/en_UK.ts @@ -1,8 +1,8 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "English (UK)"; +const name = "English (UK)"; -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -105,3 +105,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record + +export const en_UK: KeyboardLayout = { + isoCode: "en-UK", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/en_US.ts b/ui/src/keyboardLayouts/en_US.ts index 592bf27..cd7aaf6 100644 --- a/ui/src/keyboardLayouts/en_US.ts +++ b/ui/src/keyboardLayouts/en_US.ts @@ -1,8 +1,8 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "English (US)"; +const name = "English (US)"; -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -111,3 +111,9 @@ export const chars = { Insert: { key: "Insert", shift: false }, Delete: { key: "Delete", shift: false }, } as Record + +export const en_US: KeyboardLayout = { + isoCode: "en-US", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/es_ES.ts b/ui/src/keyboardLayouts/es_ES.ts index 47fc230..9eb1d6a 100644 --- a/ui/src/keyboardLayouts/es_ES.ts +++ b/ui/src/keyboardLayouts/es_ES.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Español"; +const name = "Español"; const keyTrema = { key: "Quote", shift: true } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Quote" } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accen const keyGrave = { key: "BracketRight" } // accent grave, mark ` placed above the letter const keyTilde = { key: "Key4", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -166,3 +166,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const es_ES: KeyboardLayout = { + isoCode: "es-ES", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/fr_BE.ts b/ui/src/keyboardLayouts/fr_BE.ts index 2b8b34c..bd417e0 100644 --- a/ui/src/keyboardLayouts/fr_BE.ts +++ b/ui/src/keyboardLayouts/fr_BE.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Belgisch Nederlands"; +const name = "Belgisch Nederlands"; const keyTrema = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel const keyHat = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter @@ -8,7 +8,7 @@ const keyAcute = { key: "Semicolon", altRight: true } // accent aigu (acute acce const keyGrave = { key: "Quote", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "Slash", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyQ", shift: true }, "Ä": { key: "KeyQ", shift: true, accentKey: keyTrema }, "Â": { key: "KeyQ", shift: true, accentKey: keyHat }, @@ -165,3 +165,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const fr_BE: KeyboardLayout = { + isoCode: "fr-BE", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/fr_CH.ts b/ui/src/keyboardLayouts/fr_CH.ts index cf1d3df..0ba8cb4 100644 --- a/ui/src/keyboardLayouts/fr_CH.ts +++ b/ui/src/keyboardLayouts/fr_CH.ts @@ -1,11 +1,11 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -import { chars as chars_de_CH } from "./de_CH" +import { de_CH } from "./de_CH" -export const name = "Français de Suisse"; +const name = "Français de Suisse"; -export const chars = { - ...chars_de_CH, +const chars = { + ...de_CH.chars, "è": { key: "BracketLeft" }, "ü": { key: "BracketLeft", shift: true }, "é": { key: "Semicolon" }, @@ -13,3 +13,9 @@ export const chars = { "à": { key: "Quote" }, "ä": { key: "Quote", shift: true }, } as Record; + +export const fr_CH: KeyboardLayout = { + isoCode: "fr-CH", + name: name, + chars: chars +}; diff --git a/ui/src/keyboardLayouts/fr_FR.ts b/ui/src/keyboardLayouts/fr_FR.ts index 27a03fd..29d5104 100644 --- a/ui/src/keyboardLayouts/fr_FR.ts +++ b/ui/src/keyboardLayouts/fr_FR.ts @@ -1,11 +1,11 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Français"; +const name = "Français"; const keyTrema = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel const keyHat = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter -export const chars = { +const chars = { A: { key: "KeyQ", shift: true }, "Ä": { key: "KeyQ", shift: true, accentKey: keyTrema }, "Â": { key: "KeyQ", shift: true, accentKey: keyHat }, @@ -137,3 +137,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const fr_FR: KeyboardLayout = { + isoCode: "fr-FR", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/it_IT.ts b/ui/src/keyboardLayouts/it_IT.ts index 9de61c5..0ff6e24 100644 --- a/ui/src/keyboardLayouts/it_IT.ts +++ b/ui/src/keyboardLayouts/it_IT.ts @@ -1,8 +1,8 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Italiano"; +const name = "Italiano"; -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -111,3 +111,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const it_IT: KeyboardLayout = { + isoCode: "it-IT", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/nb_NO.ts b/ui/src/keyboardLayouts/nb_NO.ts index 83918b2..4dae9c8 100644 --- a/ui/src/keyboardLayouts/nb_NO.ts +++ b/ui/src/keyboardLayouts/nb_NO.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Norsk bokmål"; +const name = "Norsk bokmål"; const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Equal", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accen const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Ä": { key: "KeyA", shift: true, accentKey: keyTrema }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, @@ -165,3 +165,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const nb_NO: KeyboardLayout = { + isoCode: "nb-NO", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/sv_SE.ts b/ui/src/keyboardLayouts/sv_SE.ts index 75197cb..fbde3d0 100644 --- a/ui/src/keyboardLayouts/sv_SE.ts +++ b/ui/src/keyboardLayouts/sv_SE.ts @@ -1,6 +1,6 @@ -import { KeyCombo } from "../keyboardLayouts" +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -export const name = "Svenska"; +const name = "Svenska"; const keyTrema = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel const keyAcute = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter @@ -8,7 +8,7 @@ const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accen const keyGrave = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const keyTilde = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter -export const chars = { +const chars = { A: { key: "KeyA", shift: true }, "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, "Â": { key: "KeyA", shift: true, accentKey: keyHat }, @@ -162,3 +162,9 @@ export const chars = { Enter: { key: "Enter" }, Tab: { key: "Tab" }, } as Record; + +export const sv_SE: KeyboardLayout = { + isoCode: "sv-SE", + name: name, + chars: chars +}; \ No newline at end of file diff --git a/ui/src/keyboardMappings.ts b/ui/src/keyboardMappings.ts index 891b96e..bb24fbb 100644 --- a/ui/src/keyboardMappings.ts +++ b/ui/src/keyboardMappings.ts @@ -1,17 +1,19 @@ // Key codes and modifiers correspond to definitions in the // [Linux USB HID gadget driver](https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt) +// [Section 10. Keyboard/Keypad Page 0x07](https://usb.org/sites/default/files/hut1_21.pdf) export const keys = { ArrowDown: 0x51, ArrowLeft: 0x50, ArrowRight: 0x4f, ArrowUp: 0x52, - Backquote: 0x35, + Backquote: 0x35, // aka Grave Backslash: 0x31, Backspace: 0x2a, - BracketLeft: 0x2f, - BracketRight: 0x30, + BracketLeft: 0x2f, // aka LeftBrace + BracketRight: 0x30, // aka RightBrace CapsLock: 0x39, Comma: 0x36, + Compose: 0x65, ContextMenu: 0, Delete: 0x4c, Digit0: 0x27, @@ -40,10 +42,21 @@ export const keys = { F10: 0x43, F11: 0x44, F12: 0x45, - F13: 0x68, + F14: 0x69, + F15: 0x6a, + F16: 0x6b, + F17: 0x6c, + F18: 0x6d, + F19: 0x6e, + F20: 0x6f, + F21: 0x70, + F22: 0x71, + F23: 0x72, + F24: 0x73, Home: 0x4a, + HashTilde: 0x32, // non-US # and ~ Insert: 0x49, - IntlBackslash: 0x64, + IntlBackslash: 0x64, // non-US \ and | KeyA: 0x04, KeyB: 0x05, KeyC: 0x06, @@ -72,30 +85,35 @@ export const keys = { KeyZ: 0x1d, KeypadExclamation: 0xcf, Minus: 0x2d, - NumLock: 0x53, - Numpad0: 0x62, - Numpad1: 0x59, - Numpad2: 0x5a, - Numpad3: 0x5b, - Numpad4: 0x5c, + None: 0x00, + NumLock: 0x53, // and Clear + Numpad0: 0x62, // and Insert + Numpad1: 0x59, // and End + Numpad2: 0x5a, // and Down Arrow + Numpad3: 0x5b, // and Page Down + Numpad4: 0x5c, // and Left Arrow Numpad5: 0x5d, - Numpad6: 0x5e, - Numpad7: 0x5f, - Numpad8: 0x60, - Numpad9: 0x61, + Numpad6: 0x5e, // and Right Arrow + Numpad7: 0x5f, // and Home + Numpad8: 0x60, // and Up Arrow + Numpad9: 0x61, // and Page Up NumpadAdd: 0x57, + NumpadComma: 0x85, + NumpadDecimal: 0x63, NumpadDivide: 0x54, NumpadEnter: 0x58, NumpadEqual: 0x67, + NumpadLeftParen: 0xb6, NumpadMultiply: 0x55, + NumpadRightParen: 0xb7, NumpadSubtract: 0x56, - NumpadDecimal: 0x63, PageDown: 0x4e, PageUp: 0x4b, Period: 0x37, PrintScreen: 0x46, Pause: 0x48, - Quote: 0x34, + Power: 0x66, + Quote: 0x34, // aka Single Quote or Apostrophe ScrollLock: 0x47, Semicolon: 0x33, Slash: 0x38, diff --git a/ui/src/routes/devices.$id.settings.keyboard.tsx b/ui/src/routes/devices.$id.settings.keyboard.tsx index 12ed6f2..d9b0d54 100644 --- a/ui/src/routes/devices.$id.settings.keyboard.tsx +++ b/ui/src/routes/devices.$id.settings.keyboard.tsx @@ -4,7 +4,7 @@ import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import notifications from "@/notifications"; import { SettingsPageHeader } from "@components/SettingsPageheader"; -import { layouts } from "@/keyboardLayouts"; +import { keyboardOptions } from "@/keyboardLayouts"; import { Checkbox } from "@/components/Checkbox"; import { SelectMenuBasic } from "../components/SelectMenuBasic"; @@ -32,7 +32,7 @@ export default function SettingsKeyboardRoute() { return "en-US"; }, [keyboardLayout]); - const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } }) + const layoutOptions = keyboardOptions(); const ledSyncOptions = [ { value: "auto", label: "Automatic" }, { value: "browser", label: "Browser Only" }, diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx index 1e888f6..b729bf0 100644 --- a/ui/src/routes/devices.$id.settings.tsx +++ b/ui/src/routes/devices.$id.settings.tsx @@ -79,7 +79,7 @@ export default function SettingsRoute() { return () => { setDisableVideoFocusTrap(false); }; - }, [setDisableVideoFocusTrap, sendKeyboardEvent]); + }, [sendKeyboardEvent, setDisableVideoFocusTrap]); return (
diff --git a/ui/src/routes/devices.$id.tsx b/ui/src/routes/devices.$id.tsx index 8cdb5b3..1785bcd 100644 --- a/ui/src/routes/devices.$id.tsx +++ b/ui/src/routes/devices.$id.tsx @@ -707,7 +707,7 @@ export default function KvmIdRoute() { }, [diskChannel, file]); // System update - const disableKeyboardFocusTrap = useUiStore(state => state.disableVideoFocusTrap); + const disableVideoFocusTrap = useUiStore(state => state.disableVideoFocusTrap); const [kvmTerminal, setKvmTerminal] = useState(null); const [serialConsole, setSerialConsole] = useState(null); @@ -805,7 +805,7 @@ export default function KvmIdRoute() { )}