From f26862d2e31214b0d6a7fbd76f5b26710af87886 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Wed, 20 Aug 2025 03:13:18 -0500 Subject: [PATCH] Centralized keyboard layout and localized display maps The Polish programmer keyboard is incomplete WIP. --- ui/src/components/ActionBar.tsx | 5 +- ui/src/components/MacroForm.tsx | 9 +- ui/src/components/MacroStepCard.tsx | 17 +- ui/src/components/VirtualKeyboard.tsx | 139 +++++---- ui/src/components/popovers/PasteModal.tsx | 27 +- ui/src/hooks/useKeyboard.ts | 8 +- ui/src/hooks/useKeyboardLayout.ts | 22 ++ ui/src/index.css | 5 + ui/src/keyboardLayouts.ts | 20 +- ui/src/keyboardLayouts/cs_CZ.ts | 33 ++- ui/src/keyboardLayouts/de_CH.ts | 33 ++- ui/src/keyboardLayouts/de_DE.ts | 19 +- ui/src/keyboardLayouts/en_UK.ts | 11 +- ui/src/keyboardLayouts/en_US.ts | 220 +++++++++++++- ui/src/keyboardLayouts/es_ES.ts | 23 +- ui/src/keyboardLayouts/fr_BE.ts | 23 +- ui/src/keyboardLayouts/fr_CH.ts | 19 +- ui/src/keyboardLayouts/fr_FR.ts | 17 +- ui/src/keyboardLayouts/it_IT.ts | 11 +- ui/src/keyboardLayouts/nb_NO.ts | 23 +- ui/src/keyboardLayouts/pl_PL_t.ts | 244 +++++++++++++++ ui/src/keyboardLayouts/sv_SE.ts | 23 +- ui/src/keyboardMappings.ts | 277 ++++++++++-------- .../routes/devices.$id.settings.keyboard.tsx | 39 ++- ui/src/routes/devices.$id.settings.macros.tsx | 11 +- ui/src/routes/devices.$id.settings.tsx | 13 +- 26 files changed, 947 insertions(+), 344 deletions(-) create mode 100644 ui/src/hooks/useKeyboardLayout.ts create mode 100644 ui/src/keyboardLayouts/pl_PL_t.ts diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index 6a8e75e..4f79d7e 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -264,7 +264,10 @@ export default function Actionbar({ theme="light" text="Settings" LeadingIcon={LuSettings} - onClick={() => navigateTo("/settings")} + onClick={() => { + setDisableVideoFocusTrap(true); + navigateTo("/settings") + }} /> diff --git a/ui/src/components/MacroForm.tsx b/ui/src/components/MacroForm.tsx index f74c4ae..6240a8a 100644 --- a/ui/src/components/MacroForm.tsx +++ b/ui/src/components/MacroForm.tsx @@ -1,17 +1,18 @@ import { useState } from "react"; import { LuPlus } from "react-icons/lu"; -import { KeySequence } from "@/hooks/stores"; import { Button } from "@/components/Button"; -import { InputFieldWithLabel, FieldError } from "@/components/InputField"; +import FieldLabel from "@/components/FieldLabel"; import Fieldset from "@/components/Fieldset"; +import { InputFieldWithLabel, FieldError } from "@/components/InputField"; import { MacroStepCard } from "@/components/MacroStepCard"; import { DEFAULT_DELAY, MAX_STEPS_PER_MACRO, MAX_KEYS_PER_STEP, } from "@/constants/macros"; -import FieldLabel from "@/components/FieldLabel"; +import { KeySequence } from "@/hooks/stores"; +import { useKeyboardLayout } from "@/hooks/useKeyboardLayout"; interface ValidationErrors { name?: string; @@ -44,6 +45,7 @@ export function MacroForm({ const [keyQueries, setKeyQueries] = useState>({}); const [errors, setErrors] = useState({}); const [errorMessage, setErrorMessage] = useState(null); + const { keyboard } = useKeyboardLayout(); const showTemporaryError = (message: string) => { setErrorMessage(message); @@ -234,6 +236,7 @@ export function MacroForm({ } onDelayChange={delay => handleDelayChange(stepIndex, delay)} isLastStep={stepIndex === (macro.steps?.length || 0) - 1} + keyboard={keyboard} /> ))} diff --git a/ui/src/components/MacroStepCard.tsx b/ui/src/components/MacroStepCard.tsx index 8642c28..c9d3822 100644 --- a/ui/src/components/MacroStepCard.tsx +++ b/ui/src/components/MacroStepCard.tsx @@ -4,19 +4,22 @@ import { Button } from "@/components/Button"; import { Combobox } from "@/components/Combobox"; import { SelectMenuBasic } from "@/components/SelectMenuBasic"; import Card from "@/components/Card"; -import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings"; -import { MAX_KEYS_PER_STEP, DEFAULT_DELAY } from "@/constants/macros"; import FieldLabel from "@/components/FieldLabel"; +import { MAX_KEYS_PER_STEP, DEFAULT_DELAY } from "@/constants/macros"; +import { KeyboardLayout } from "@/keyboardLayouts"; +import { keys, modifiers } from "@/keyboardMappings"; // Filter out modifier keys since they're handled in the modifiers section const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta']; -const keyOptions = Object.keys(keys) +const keyOptions = (keyDisplayMap: Record) => { + return Object.keys(keys) .filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix))) .map(key => ({ value: key, label: keyDisplayMap[key] || key, })); +} const modifierOptions = Object.keys(modifiers).map(modifier => ({ value: modifier, @@ -67,6 +70,7 @@ interface MacroStepCardProps { onModifierChange: (modifiers: string[]) => void; onDelayChange: (delay: number) => void; isLastStep: boolean; + keyboard: KeyboardLayout } const ensureArray = (arr: T[] | null | undefined): T[] => { @@ -84,11 +88,14 @@ export function MacroStepCard({ keyQuery, onModifierChange, onDelayChange, - isLastStep + isLastStep, + keyboard }: MacroStepCardProps) { + const { keyDisplayMap } = keyboard; + const getFilteredKeys = () => { const selectedKeys = ensureArray(step.keys); - const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value)); + const availableKeys = keyOptions(keyDisplayMap).filter(option => !selectedKeys.includes(option.value)); if (keyQuery === '') { return availableKeys; diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx index 16ccb9b..065c0fc 100644 --- a/ui/src/components/VirtualKeyboard.tsx +++ b/ui/src/components/VirtualKeyboard.tsx @@ -1,6 +1,6 @@ import { ChevronDownIcon } from "@heroicons/react/16/solid"; import { AnimatePresence, motion } from "framer-motion"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import Keyboard from "react-simple-keyboard"; import Card from "@components/Card"; @@ -14,7 +14,8 @@ import DetachIconRaw from "@/assets/detach-icon.svg"; import { cx } from "@/cva.config"; import { useHidStore, useUiStore } from "@/hooks/stores"; import useKeyboard from "@/hooks/useKeyboard"; -import { keyDisplayMap, keys } from "@/keyboardMappings"; +import { useKeyboardLayout } from "@/hooks/useKeyboardLayout"; +import { keys, modifiers, latchingKeys, decodeModifiers } from "@/keyboardMappings"; export const DetachIcon = ({ className }: { className?: string }) => { return Detach Icon; @@ -25,31 +26,40 @@ const AttachIcon = ({ className }: { className?: string }) => { }; function KeyboardWrapper() { - const [layoutName] = useState("default"); - const keyboardRef = useRef(null); const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } = useUiStore(); - const { keysDownState, isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } = useHidStore(); + const { keysDownState, /* keyboardLedState,*/ isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } = useHidStore(); const { handleKeyPress, executeMacro } = useKeyboard(); const [isDragging, setIsDragging] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); const [newPosition, setNewPosition] = useState({ x: 0, y: 0 }); - /* - // These will be used to display the currently pressed keys and modifiers on the virtual keyboard + const { keyboard } = useKeyboardLayout(); - // used to show the modifier keys that are in the "down state" on the virtual keyboard - const keyNamesFromModifierMask = (activeModifiers: number): string[] => { - return Object.entries(modifiers).filter(m => (activeModifiers & m[1]) !== 0).map(m => m[0]); - } + //const isCapsLockActive = useMemo(() => { + // return (keyboardLedState.caps_lock); + //}, [keyboardLedState]); - // used to show the regular keys that are in the "down state" on the virtual keyboard - const keyNamesFromDownKeys = (downKeys: number[]) => { - return Object.entries(keys).filter(([_, code]) => downKeys.includes(code)).map(([name, _]) => name); - } - */ + const { isShiftActive, /*isControlActive, isAltActive, isMetaActive, isAltGrActive*/ } = useMemo(() => { + return decodeModifiers(keysDownState.modifier); + }, [keysDownState]); + const mainLayoutName = useMemo(() => { + const layoutName = isShiftActive ? "shift": "default"; + return layoutName; + }, [isShiftActive]); + + const keyNamesForDownKeys = useMemo(() => { + const activeModifierMask = keysDownState.modifier || 0; + const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name); + + const keysDown = keysDownState.keys || []; + const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name); + + return [...modifierNames,...keyNames, ' ']; // we have to have at least one space to avoid keyboard whining + }, [keysDownState]); + const startDrag = useCallback((e: MouseEvent | TouchEvent) => { if (!keyboardRef.current) return; if (e instanceof TouchEvent && e.touches.length > 1) return; @@ -119,10 +129,16 @@ function KeyboardWrapper() { }; }, [endDrag, onDrag, startDrag]); + const onKeyUp = useCallback( + async (key: string, e: MouseEvent | undefined) => { + e?.preventDefault(); + }, + [] + ); + const onKeyDown = useCallback( - async (key: string) => { - const latchingKeys = ["CapsLock", "ScrollLock", "NumLock", "Meta", "Compose", "Kana"]; - const dynamicKeys = ["ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "MetaLeft", "MetaRight"]; + async (key: string, e: MouseEvent | undefined) => { + e?.preventDefault(); // handle the fake key-macros we have defined for common combinations if (key === "CtrlAltDelete") { @@ -149,8 +165,8 @@ function KeyboardWrapper() { } // if they press any of the dynamic keys, we send a keypress down event but we don't release it until they click it again - if (dynamicKeys.includes(key)) { - const currentlyDown = keysDownState.keys.includes(keys[key]); + if (Object.keys(modifiers).includes(key)) { + const currentlyDown = keyNamesForDownKeys.includes(key); console.debug(`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`); handleKeyPress(keys[key], !currentlyDown) return; @@ -162,16 +178,9 @@ function KeyboardWrapper() { handleKeyPress(keys[cleanKey], true); setTimeout(() => handleKeyPress(keys[cleanKey], false), 50); }, - [executeMacro, handleKeyPress, keysDownState], + [executeMacro, handleKeyPress, keyNamesForDownKeys], ); - // TODO handle the display of down keys and the layout change for shift/caps lock - // const { isCapsLockActive } = useShallow(useHidStore()); - // // Handle toggle of layout for shift or caps lock - // const toggleLayout = () => { - // setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default")); - // }; - return (
+ { /* TODO add optional number pad */ }
diff --git a/ui/src/components/popovers/PasteModal.tsx b/ui/src/components/popovers/PasteModal.tsx index 0b69718..5aebd25 100644 --- a/ui/src/components/popovers/PasteModal.tsx +++ b/ui/src/components/popovers/PasteModal.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { LuCornerDownLeft } from "react-icons/lu"; import { ExclamationCircleIcon } from "@heroicons/react/16/solid"; import { useClose } from "@headlessui/react"; @@ -10,7 +10,8 @@ import { SettingsPageHeader } from "@components/SettingsPageheader"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores"; import { keys, modifiers } from "@/keyboardMappings"; -import { KeyStroke, KeyboardLayout, selectedKeyboard } from "@/keyboardLayouts"; +import { KeyStroke } from "@/keyboardLayouts"; +import { useKeyboardLayout } from "@/hooks/useKeyboardLayout"; import notifications from "@/notifications"; const hidKeyboardPayload = (modifier: number, keys: number[]) => { @@ -18,8 +19,8 @@ const hidKeyboardPayload = (modifier: number, keys: number[]) => { }; const modifierCode = (shift?: boolean, altRight?: boolean) => { - return (shift ? modifiers["ShiftLeft"] : 0) - | (altRight ? modifiers["AltRight"] : 0) + return (shift ? modifiers.ShiftLeft : 0) + | (altRight ? modifiers.AltRight : 0) } const noModifier = 0 @@ -34,15 +35,8 @@ export default function PasteModal() { const [invalidChars, setInvalidChars] = useState([]); const close = useClose(); - const { keyboardLayout, setKeyboardLayout } = useSettingsStore(); - - // this ensures we always get the en-US if it hasn't been set yet - // and if we get en_US from the backend, we convert it to en-US - const safeKeyboardLayout = useMemo(() => { - if (keyboardLayout && keyboardLayout.length > 0) - return keyboardLayout.replace("en_US", "en-US"); - return "en-US"; - }, [keyboardLayout]); + const { setKeyboardLayout } = useSettingsStore(); + const { keyboard } = useKeyboardLayout(); useEffect(() => { send("getKeyboardLayout", {}, resp => { @@ -62,7 +56,6 @@ export default function PasteModal() { setDisableVideoFocusTrap(false); if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return; - const keyboard: KeyboardLayout = selectedKeyboard(safeKeyboardLayout); if (!keyboard) return; const text = TextAreaRef.current.value; @@ -109,7 +102,7 @@ export default function PasteModal() { ); }); } - }, [rpcDataChannel?.readyState, safeKeyboardLayout, send, setDisableVideoFocusTrap, setPasteModeEnabled]); + }, [keyboard, rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteModeEnabled]); useEffect(() => { if (TextAreaRef.current) { @@ -159,7 +152,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 => !selectedKeyboard(safeKeyboardLayout).chars[char]), + .filter(char => !keyboard.chars[char]), ), ]; @@ -180,7 +173,7 @@ export default function PasteModal() {

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

diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index df09730..5f587b0 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -6,9 +6,7 @@ import { hidKeyToModifierMask, keys, modifiers } from "@/keyboardMappings"; export default function useKeyboard() { const { send } = useJsonRpc(); - const { rpcDataChannel } = useRTCStore(); - const { keysDownState, setKeysDownState } = useHidStore(); // INTRODUCTION: The earlier version of the JetKVM device shipped with all keyboard state @@ -23,8 +21,8 @@ export default function useKeyboard() { // getKeysDownState API. const { keyPressReportApiAvailable, setkeyPressReportApiAvailable} = useHidStore(); - // sendKeyboardEvent is used to send the full keyboard state to the device for macro handling and resetting keyboard state. - // It sends the keys currently pressed and the modifier state. + // sendKeyboardEvent is used to send the full keyboard state to the device for macro handling + // and resetting keyboard state. It sends the keys currently pressed and the modifier state. // The device will respond with the keysDownState if it supports the keyPressReport API // or just accept the state if it does not support (returning no result) const sendKeyboardEvent = useCallback( @@ -93,7 +91,6 @@ export default function useKeyboard() { // is clean. const resetKeyboardState = useCallback( async () => { - console.debug("Resetting keyboard state"); // Reset the keys buffer to zeros and the modifier state to zero keysDownState.keys.length = hidKeyBufferSize; keysDownState.keys.fill(0); @@ -139,6 +136,7 @@ export default function useKeyboard() { const handleKeyPress = useCallback( async (key: number, press: boolean) => { if (rpcDataChannel?.readyState !== "open") return; + if ((key || 0) === 0) return; // ignore zero key presses (they are bad mappings) if (keyPressReportApiAvailable) { // if the keyPress api is available, we can just send the key press event diff --git a/ui/src/hooks/useKeyboardLayout.ts b/ui/src/hooks/useKeyboardLayout.ts new file mode 100644 index 0000000..4d4fbcc --- /dev/null +++ b/ui/src/hooks/useKeyboardLayout.ts @@ -0,0 +1,22 @@ +import { useMemo } from "react"; + +import { useSettingsStore } from "@/hooks/stores"; +import { KeyboardLayout, selectedKeyboard } from "@/keyboardLayouts"; + +export function useKeyboardLayout(): { keyboard: KeyboardLayout } { + const { keyboardLayout } = useSettingsStore(); + + const isoCode = useMemo(() => { + console.log("Current keyboard layout from store:", keyboardLayout); + if (keyboardLayout && keyboardLayout.length > 0) + return keyboardLayout.replace("en_US", "en-US"); + return "en-US"; + }, [keyboardLayout]); + + const keyboard = useMemo(() => { + console.log("Selected keyboard layout:", isoCode); + return selectedKeyboard(isoCode); + }, [isoCode]); + + return { keyboard }; +} \ No newline at end of file diff --git a/ui/src/index.css b/ui/src/index.css index 44acd2a..db03b42 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -315,6 +315,11 @@ video::-webkit-media-controls { @apply inline-flex h-auto! w-auto! grow-0 py-1 text-xs; } +.hg-theme-default .hg-row .down-key { + background: rgb(28, 28, 28); + @apply text-white! font-bold!; +} + .hg-theme-default .hg-row .hg-button-container, .hg-theme-default .hg-row .hg-button:not(:last-child) { @apply mr-[2px]! md:mr-[5px]!; diff --git a/ui/src/keyboardLayouts.ts b/ui/src/keyboardLayouts.ts index 4ae3ad9..bc646bc 100644 --- a/ui/src/keyboardLayouts.ts +++ b/ui/src/keyboardLayouts.ts @@ -1,7 +1,18 @@ 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 } +export interface KeyboardLayout { + isoCode: string; + name: string; + chars: Record; + modifierDisplayMap: Record; + keyDisplayMap: Record; + virtualKeyboard: { + main: { default: string[], shift: string[] }, + control?: { default: string[], shift?: string[] }, + arrows?: { default: string[] } + }; +} // to add a new layout, create a file like the above and add it to the list import { cs_CZ } from "@/keyboardLayouts/cs_CZ" @@ -15,13 +26,14 @@ 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 { pl_PL_t } from "@/keyboardLayouts/pl_PL_t" import { sv_SE } from "@/keyboardLayouts/sv_SE" -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 keyboards: KeyboardLayout[] = [ cs_CZ, de_CH, de_DE, en_UK, en_US, es_ES, fr_BE, fr_CH, fr_FR, it_IT, nb_NO, pl_PL_t, sv_SE ]; export const selectedKeyboard = (isoCode: string): KeyboardLayout => { - // fallback to original behaviour of en-US if no isoCode given - return keyboards.find(keyboard => keyboard.isoCode == isoCode) + // fallback to original behaviour of en-US if no isoCode given or matching layout not found + return keyboards.find(keyboard => keyboard.isoCode == isoCode) ?? keyboards.find(keyboard => keyboard.isoCode == "en-US")!; }; diff --git a/ui/src/keyboardLayouts/cs_CZ.ts b/ui/src/keyboardLayouts/cs_CZ.ts index e4f8822..c02be70 100644 --- a/ui/src/keyboardLayouts/cs_CZ.ts +++ b/ui/src/keyboardLayouts/cs_CZ.ts @@ -1,17 +1,20 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Čeština"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 -const keyHat = { key: "Digit3", shift: true, altRight: true } // accent circonflexe (accent hat), mark ^ placed above the letter -const keyCaron = { key: "Equal", shift: true } // caron or haček (inverted hat), mark ˇ placed above the letter -const keyGrave = { key: "Digit7", shift: true, altRight: true } // accent grave, mark ` placed above the letter -const keyTilde = { key: "Digit1", shift: true, altRight: true } // tilde, mark ~ placed above the letter -const keyRing = { key: "Backquote", shift: true } // kroužek (little ring), mark ° placed above the letter -const keyOverdot = { key: "Digit8", shift: true, altRight: true } // overdot (dot above), mark ˙ placed above the letter -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 +const name = "Čeština"; +const isoCode = "cs-CZ"; + +const keyTrema: KeyCombo = { key: "Backslash" } // tréma (umlaut), two dots placed above a vowel +const keyAcute: KeyCombo = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter +const keyHat: KeyCombo = { key: "Digit3", shift: true, altRight: true } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyCaron: KeyCombo = { key: "Equal", shift: true } // caron or haček (inverted hat), mark ˇ placed above the letter +const keyGrave: KeyCombo = { key: "Digit7", shift: true, altRight: true } // accent grave, mark ` placed above the letter +const keyTilde: KeyCombo = { key: "Digit1", shift: true, altRight: true } // tilde, mark ~ placed above the letter +const keyRing: KeyCombo = { key: "Backquote", shift: true } // kroužek (little ring), mark ° placed above the letter +const keyOverdot: KeyCombo = { key: "Digit8", shift: true, altRight: true } // overdot (dot above), mark ˙ placed above the letter +const keyHook: KeyCombo = { key: "Digit6", shift: true, altRight: true } // ogonoek (little hook), mark ˛ placed beneath a letter +const keyCedille: KeyCombo = { key: "Equal", shift: true, altRight: true } // accent cedille (cedilla), mark ¸ placed beneath a letter const chars = { A: { key: "KeyA", shift: true }, @@ -244,7 +247,11 @@ const chars = { } as Record; export const cs_CZ: KeyboardLayout = { - isoCode: "cs-CZ", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/de_CH.ts b/ui/src/keyboardLayouts/de_CH.ts index 4743bcf..8776409 100644 --- a/ui/src/keyboardLayouts/de_CH.ts +++ b/ui/src/keyboardLayouts/de_CH.ts @@ -1,12 +1,15 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Schwiizerdütsch"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 -const keyHat = { key: "Equal" } // accent circonflexe (accent hat), mark ^ placed above the letter -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 +const name = "Schwiizerdütsch"; +const isoCode = "de-CH"; + +const keyTrema: KeyCombo = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel +const keyAcute: KeyCombo = { key: "Minus", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter +const keyHat: KeyCombo = { key: "Equal" } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter +const keyTilde: KeyCombo = { key: "Equal", altRight: true } // tilde, mark ~ placed above the letter const chars = { A: { key: "KeyA", shift: true }, @@ -164,8 +167,22 @@ const chars = { Tab: { key: "Tab" }, } as Record; +const keyDisplayMap = { + ...en_US.keyDisplayMap, + BracketLeft: "è", + "(BracketLeft)": "ü", + Semicolon: "é", + "(Semicolon)": "ö", + Quote: "à", + "(Quote)": "ä", +} as Record; + export const de_CH: KeyboardLayout = { - isoCode: "de-CH", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + keyDisplayMap: keyDisplayMap, + // TODO need to localize these maps and layouts + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/de_DE.ts b/ui/src/keyboardLayouts/de_DE.ts index 89b7eed..69710a1 100644 --- a/ui/src/keyboardLayouts/de_DE.ts +++ b/ui/src/keyboardLayouts/de_DE.ts @@ -1,10 +1,13 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Deutsch"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 +const name = "Deutsch"; +const isoCode = "de-DE"; + +const keyAcute: KeyCombo = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter +const keyHat: KeyCombo = { key: "Backquote" } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter const chars = { A: { key: "KeyA", shift: true }, @@ -152,7 +155,11 @@ const chars = { } as Record; export const de_DE: KeyboardLayout = { - isoCode: "de-DE", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/en_UK.ts b/ui/src/keyboardLayouts/en_UK.ts index a5ef779..5341f0f 100644 --- a/ui/src/keyboardLayouts/en_UK.ts +++ b/ui/src/keyboardLayouts/en_UK.ts @@ -1,6 +1,9 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard + const name = "English (UK)"; +const isoCode = "en-UK"; const chars = { A: { key: "KeyA", shift: true }, @@ -107,7 +110,11 @@ const chars = { } as Record export const en_UK: KeyboardLayout = { - isoCode: "en-UK", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/en_US.ts b/ui/src/keyboardLayouts/en_US.ts index cd7aaf6..872d356 100644 --- a/ui/src/keyboardLayouts/en_US.ts +++ b/ui/src/keyboardLayouts/en_US.ts @@ -1,8 +1,18 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" const name = "English (US)"; +const isoCode = "en-US"; -const chars = { +// dead keys for "international" 101 keyboards TODO +/* +const keyAcute = { key: "Quote", control: true, menu: true, mark: "´" } // acute accent +const keyCedilla = { key: ".", shift: true, alt: true, mark: "¸" } // cedilla accent +const keyComma = { key: "BracketRight", shift: true, altRight: true, mark: "," } // comma accent +const keyDiaeresis = { key: "Quote", shift: true, control: true, menu: true, mark: "¨" } // diaeresis accent +const keyDegree = { key: "Semicolon", shift: true, control: true, menu: true, mark: "°" } // degree accent +*/ + +export const chars = { A: { key: "KeyA", shift: true }, B: { key: "KeyB", shift: true }, C: { key: "KeyC", shift: true }, @@ -89,31 +99,213 @@ const chars = { ">": { key: "Period", shift: true }, ";": { key: "Semicolon" }, ":": { key: "Semicolon", shift: true }, + "¶": { key: "Semicolon", altRight: true }, // pilcrow sign "[": { key: "BracketLeft" }, "{": { key: "BracketLeft", shift: true }, + "«": { key: "BracketLeft", altRight: true }, // double left quote sign "]": { key: "BracketRight" }, "}": { key: "BracketRight", shift: true }, + "»": { key: "BracketRight", altRight: true }, // double right quote sign "\\": { key: "Backslash" }, "|": { key: "Backslash", shift: true }, + "¬": { key: "Backslash", altRight: true }, // not sign "`": { key: "Backquote" }, "~": { key: "Backquote", shift: true }, "§": { key: "IntlBackslash" }, "±": { key: "IntlBackslash", shift: true }, - " ": { key: "Space", shift: false }, - "\n": { key: "Enter", shift: false }, - Enter: { key: "Enter", shift: false }, - Tab: { key: "Tab", shift: false }, - PrintScreen: { key: "Prt Sc", shift: false }, + " ": { key: "Space" }, + "\n": { key: "Enter" }, + Enter: { key: "Enter" }, + Escape: { key: "Escape" }, + Tab: { key: "Tab" }, + PrintScreen: { key: "Prt Sc" }, SystemRequest: { key: "Prt Sc", shift: true }, - ScrollLock: { key: "ScrollLock", shift: false}, - Pause: { key: "Pause", shift: false }, + ScrollLock: { key: "ScrollLock" }, + Pause: { key: "Pause" }, Break: { key: "Pause", shift: true }, - Insert: { key: "Insert", shift: false }, - Delete: { key: "Delete", shift: false }, + Insert: { key: "Insert" }, + Delete: { key: "Delete" }, } as Record +export const modifierDisplayMap: Record = { + ControlLeft: "Left Ctrl", + ControlRight: "Right Ctrl", + ShiftLeft: "Left Shift", + ShiftRight: "Right Shift", + AltLeft: "Left Alt", + AltRight: "Right Alt", + MetaLeft: "Left Meta", + MetaRight: "Right Meta", + AltGr: "AltGr", +} as Record; + +export const keyDisplayMap: Record = { + CtrlAltDelete: "Ctrl + Alt + Delete", + AltMetaEscape: "Alt + Meta + Escape", + CtrlAltBackspace: "Ctrl + Alt + Backspace", + AltGr: "AltGr", + AltLeft: "Alt", + AltRight: "Alt", + ArrowDown: "↓", + ArrowLeft: "←", + ArrowRight: "→", + ArrowUp: "↑", + Backspace: "Backspace", + "(Backspace)": "Backspace", + CapsLock: "Caps Lock", + Clear: "Clear", + ControlLeft: "Ctrl", + ControlRight: "Ctrl", + Delete: "Delete", + End: "End", + Enter: "Enter", + Escape: "Esc", + Home: "Home", + Insert: "Insert", + Menu: "Menu", + MetaLeft: "Meta", + MetaRight: "Meta", + PageDown: "PgDn", + PageUp: "PgUp", + ShiftLeft: "Shift", + ShiftRight: "Shift", + Space: " ", + Tab: "Tab", + + // Letters + KeyA: "a", KeyB: "b", KeyC: "c", KeyD: "d", KeyE: "e", + KeyF: "f", KeyG: "g", KeyH: "h", KeyI: "i", KeyJ: "j", + KeyK: "k", KeyL: "l", KeyM: "m", KeyN: "n", KeyO: "o", + KeyP: "p", KeyQ: "q", KeyR: "r", KeyS: "s", KeyT: "t", + KeyU: "u", KeyV: "v", KeyW: "w", KeyX: "x", KeyY: "y", + KeyZ: "z", + + // Capital letters + "(KeyA)": "A", "(KeyB)": "B", "(KeyC)": "C", "(KeyD)": "D", "(KeyE)": "E", + "(KeyF)": "F", "(KeyG)": "G", "(KeyH)": "H", "(KeyI)": "I", "(KeyJ)": "J", + "(KeyK)": "K", "(KeyL)": "L", "(KeyM)": "M", "(KeyN)": "N", "(KeyO)": "O", + "(KeyP)": "P", "(KeyQ)": "Q", "(KeyR)": "R", "(KeyS)": "S", "(KeyT)": "T", + "(KeyU)": "U", "(KeyV)": "V", "(KeyW)": "W", "(KeyX)": "X", "(KeyY)": "Y", + "(KeyZ)": "Z", + + // Numbers + Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4", Digit5: "5", + Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9", Digit0: "0", + + // Shifted Numbers + "(Digit1)": "!", "(Digit2)": "@", "(Digit3)": "#", "(Digit4)": "$", "(Digit5)": "%", + "(Digit6)": "^", "(Digit7)": "&", "(Digit8)": "*", "(Digit9)": "(", "(Digit0)": ")", + + // Symbols + Minus: "-", + "(Minus)": "_", + Equal: "=", + "(Equal)": "+", + BracketLeft: "[", + "(BracketLeft)": "{", + BracketRight: "]", + "(BracketRight)": "}", + Backslash: "\\", + "(Backslash)": "|", + Semicolon: ";", + "(Semicolon)": ":", + Quote: "'", + "(Quote)": "\"", + Comma: ",", + "(Comma)": "<", + Period: ".", + "(Period)": ">", + Slash: "/", + "(Slash)": "?", + Backquote: "`", + "(Backquote)": "~", + IntlBackslash: "\\", + + // Function keys + F1: "F1", F2: "F2", F3: "F3", F4: "F4", + F5: "F5", F6: "F6", F7: "F7", F8: "F8", + F9: "F9", F10: "F10", F11: "F11", F12: "F12", + + // Numpad + Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2", + Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5", + Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8", + Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -", + NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .", + NumpadEqual: "Num =", NumpadEnter: "Num Enter", NumpadInsert: "Ins", + NumpadDelete: "Del", NumLock: "Num Lock", + + // Modals + PrintScreen: "Prt Sc", ScrollLock: "Scr Lk", Pause: "Pause", + "(PrintScreen)": "Sys Rq", "(Pause)": "Break", + SystemRequest: "Sys Rq", Break: "Break" +}; + +export const virtualKeyboard = { + main: { + default: [ + "CtrlAltDelete AltMetaEscape CtrlAltBackspace", + "Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12", + "Backquote Digit1 Digit2 Digit3 Digit4 Digit5 Digit6 Digit7 Digit8 Digit9 Digit0 Minus Equal Backspace", + "Tab KeyQ KeyW KeyE KeyR KeyT KeyY KeyU KeyI KeyO KeyP BracketLeft BracketRight Backslash", + "CapsLock KeyA KeyS KeyD KeyF KeyG KeyH KeyJ KeyK KeyL Semicolon Quote Enter", + "ShiftLeft KeyZ KeyX KeyC KeyV KeyB KeyN KeyM Comma Period Slash ShiftRight", + "ControlLeft MetaLeft AltLeft Space AltGr MetaRight Menu ControlRight", + ], + shift: [ + "CtrlAltDelete AltMetaEscape CtrlAltBackspace", + "Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12", + "(Backquote) (Digit1) (Digit2) (Digit3) (Digit4) (Digit5) (Digit6) (Digit7) (Digit8) (Digit9) (Digit0) (Minus) (Equal) (Backspace)", + "Tab (KeyQ) (KeyW) (KeyE) (KeyR) (KeyT) (KeyY) (KeyU) (KeyI) (KeyO) (KeyP) (BracketLeft) (BracketRight) (Backslash)", + "CapsLock (KeyA) (KeyS) (KeyD) (KeyF) (KeyG) (KeyH) (KeyJ) (KeyK) (KeyL) (Semicolon) (Quote) Enter", + "ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight", + "ControlLeft MetaLeft AltLeft Space AltGr MetaRight Menu ControlRight", + ] + }, + control: { + default: [ + "PrintScreen ScrollLock Pause", + "Insert Home PageUp", + "Delete End PageDown" + ], + shift: [ + "(PrintScreen) ScrollLock (Pause)", + "Insert Home PageUp", + "Delete End PageDown" + ], + }, + + arrows: { + default: [ + "ArrowUp", + "ArrowLeft ArrowDown ArrowRight"], + }, + + numpad: { + numlocked: [ + "NumLock NumpadDivide NumpadMultiply NumpadSubtract", + "Numpad7 Numpad8 Numpad9 NumpadAdd", + "Numpad4 Numpad5 Numpad6", + "Numpad1 Numpad2 Numpad3 NumpadEnter", + "Numpad0 NumpadDecimal", + ], + default: [ + "NumLock NumpadDivide NumpadMultiply NumpadSubtract", + "Home ArrowUp PageUp NumpadAdd", + "ArrowLeft Clear ArrowRight", + "End ArrowDown PageDown NumpadEnter", + "NumpadInsert NumpadDelete", + ], + } +} + export const en_US: KeyboardLayout = { - isoCode: "en-US", - name: name, - chars: chars -}; \ No newline at end of file + isoCode, + name, + chars, + keyDisplayMap, + modifierDisplayMap, + virtualKeyboard +}; + + diff --git a/ui/src/keyboardLayouts/es_ES.ts b/ui/src/keyboardLayouts/es_ES.ts index 9eb1d6a..ab7762b 100644 --- a/ui/src/keyboardLayouts/es_ES.ts +++ b/ui/src/keyboardLayouts/es_ES.ts @@ -1,12 +1,15 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Español"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 -const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter -const keyGrave = { key: "BracketRight" } // accent grave, mark ` placed above the letter -const keyTilde = { key: "Key4", altRight: true } // tilde, mark ~ placed above the letter +const name = "Español"; +const isoCode = "es-ES"; + +const keyTrema: KeyCombo = { key: "Quote", shift: true } // tréma (umlaut), two dots placed above a vowel +const keyAcute: KeyCombo = { key: "Quote" } // accent aigu (acute accent), mark ´ placed above the letter +const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyGrave: KeyCombo = { key: "BracketRight" } // accent grave, mark ` placed above the letter +const keyTilde: KeyCombo = { key: "Key4", altRight: true } // tilde, mark ~ placed above the letter const chars = { A: { key: "KeyA", shift: true }, @@ -168,7 +171,11 @@ const chars = { } as Record; export const es_ES: KeyboardLayout = { - isoCode: "es-ES", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/fr_BE.ts b/ui/src/keyboardLayouts/fr_BE.ts index bd417e0..fb5a79b 100644 --- a/ui/src/keyboardLayouts/fr_BE.ts +++ b/ui/src/keyboardLayouts/fr_BE.ts @@ -1,12 +1,15 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Belgisch Nederlands"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 -const keyAcute = { key: "Semicolon", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter -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 +const name = "Belgisch Nederlands"; +const isoCode = "nl-BE"; + +const keyTrema: KeyCombo = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel +const keyHat: KeyCombo = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyAcute: KeyCombo = { key: "Semicolon", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter +const keyGrave: KeyCombo = { key: "Quote", shift: true } // accent grave, mark ` placed above the letter +const keyTilde: KeyCombo = { key: "Slash", altRight: true } // tilde, mark ~ placed above the letter const chars = { A: { key: "KeyQ", shift: true }, @@ -167,7 +170,11 @@ const chars = { } as Record; export const fr_BE: KeyboardLayout = { - isoCode: "fr-BE", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/fr_CH.ts b/ui/src/keyboardLayouts/fr_CH.ts index 0ba8cb4..d0a70f3 100644 --- a/ui/src/keyboardLayouts/fr_CH.ts +++ b/ui/src/keyboardLayouts/fr_CH.ts @@ -3,6 +3,7 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" import { de_CH } from "./de_CH" const name = "Français de Suisse"; +const isoCode = "fr-CH"; const chars = { ...de_CH.chars, @@ -14,8 +15,22 @@ const chars = { "ä": { key: "Quote", shift: true }, } as Record; +const keyDisplayMap = { + ...de_CH.keyDisplayMap, + "BracketLeft": "è", + "BracketLeftShift": "ü", + "Semicolon": "é", + "SemicolonShift": "ö", + "Quote": "à", + "QuoteShift": "ä", +} as Record; + export const fr_CH: KeyboardLayout = { - isoCode: "fr-CH", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + keyDisplayMap: keyDisplayMap, + // TODO need to localize these maps and layouts + modifierDisplayMap: de_CH.modifierDisplayMap, + virtualKeyboard: de_CH.virtualKeyboard }; diff --git a/ui/src/keyboardLayouts/fr_FR.ts b/ui/src/keyboardLayouts/fr_FR.ts index 29d5104..2ac5e74 100644 --- a/ui/src/keyboardLayouts/fr_FR.ts +++ b/ui/src/keyboardLayouts/fr_FR.ts @@ -1,9 +1,12 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Français"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 +const name = "Français"; +const isoCode = "fr-FR"; + +const keyTrema: KeyCombo = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel +const keyHat: KeyCombo = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter const chars = { A: { key: "KeyQ", shift: true }, @@ -139,7 +142,11 @@ const chars = { } as Record; export const fr_FR: KeyboardLayout = { - isoCode: "fr-FR", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/it_IT.ts b/ui/src/keyboardLayouts/it_IT.ts index 0ff6e24..160b0fc 100644 --- a/ui/src/keyboardLayouts/it_IT.ts +++ b/ui/src/keyboardLayouts/it_IT.ts @@ -1,6 +1,9 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard + const name = "Italiano"; +const isoCode = "it-IT"; const chars = { A: { key: "KeyA", shift: true }, @@ -113,7 +116,11 @@ const chars = { } as Record; export const it_IT: KeyboardLayout = { - isoCode: "it-IT", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/nb_NO.ts b/ui/src/keyboardLayouts/nb_NO.ts index 4dae9c8..25043d9 100644 --- a/ui/src/keyboardLayouts/nb_NO.ts +++ b/ui/src/keyboardLayouts/nb_NO.ts @@ -1,12 +1,15 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Norsk bokmål"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 -const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter -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 +const name = "Norsk bokmål"; +const isoCode = "nb-NO"; + +const keyTrema: KeyCombo = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel +const keyAcute: KeyCombo = { key: "Equal", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter +const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter +const keyTilde: KeyCombo = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter const chars = { A: { key: "KeyA", shift: true }, @@ -167,7 +170,11 @@ const chars = { } as Record; export const nb_NO: KeyboardLayout = { - isoCode: "nb-NO", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/pl_PL_t.ts b/ui/src/keyboardLayouts/pl_PL_t.ts new file mode 100644 index 0000000..f8c7a6e --- /dev/null +++ b/ui/src/keyboardLayouts/pl_PL_t.ts @@ -0,0 +1,244 @@ +import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" + +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard + +const name = "Polski (Programista)"; +const isoCode = "pl-PL-t"; + +const keyAcute: KeyCombo = { key: "Quote" } // accent aigu (acute accent), mark ´ placed above the letter +const keyCedilla: KeyCombo = { key: "OEM_2", altRight: true } // Cedilla mark ¸ placed below the letter in the center +const keyDiaresis: KeyCombo = { key: "Plus", shift: true } // Diaresis (not umlaut!), two dots placed above a vowel to indicate each vowel should be pronounce +const keyDotAbove: KeyCombo = { key: "", } // Dot above, single TODO! +const keyDoubleAcute: KeyCombo = { key: "˝" } // Double acute mark ˝, placed above the letter in the center +const keyGrave: KeyCombo = { key: "BracketRight" } // accent grave mark ` placed above the letter +const keyHacek: KeyCombo = { key: "", } // TODO! +const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyOgonek: KeyCombo = { key: ""} // Ogonek mark ˛ placed below the letter on the right side +const keyTylda: KeyCombo = { key: "Backquote", altRight: true } // Tilde mark ~ placed above the letter + +const chars = { + A: { key: "KeyA", shift: true }, + "Ä": { key: "KeyA", shift: true, accentKey: keyDiaresis }, + "Ą": { key: "KeyA", shift: true, ctrl: true, alt: true }, + "Á": { key: "KeyA", shift: true, accentKey: keyAcute }, + "Â": { key: "KeyA", shift: true, accentKey: keyHat }, + "À": { key: "KeyA", shift: true, accentKey: keyGrave }, + "Ã": { key: "KeyA", shift: true, accentKey: keyTylda }, + a: { key: "KeyA" }, + "ä": { key: "KeyA", accentKey: keyDiaresis }, + "ą": { key: "KeyA", accentKey: keyOgonek }, // "ą": { key: "KeyA", ctrl: true, alt: true }, + "á": { key: "KeyA", accentKey: keyAcute }, + "â": { key: "KeyA", accentKey: keyHat }, + "à": { key: "KeyA", accentKey: keyGrave }, + "ã": { key: "KeyA", accentKey: keyTylda }, + + B: { key: "KeyB", shift: true }, + b: { key: "KeyB" }, + + C: { key: "KeyC", shift: true }, + "Č": { key: "KeyC", shift: true, accentKey: keyHacek }, + "Ç": { key: "KeyC", shift: true, accentKey: keyCedilla }, // "Ç": { key: "Backslash", shift: true }, + c: { key: "KeyC" }, + "č": { key: "KeyC", accentKey: keyHacek }, + "ç": { key: "KeyC", accentKey: keyCedilla }, // "ç": { key: "Backslash" }, + + D: { key: "KeyD", shift: true }, + "Ď": { key: "KeyD", shift: true, accentKey: keyHacek }, + d: { key: "KeyD" }, + "ď": { key: "KeyD", accentKey: keyHacek }, + + E: { key: "KeyE", shift: true }, + "Ë": { key: "KeyE", shift: true, accentKey: keyDiaresis }, + "Ę": { key: "KeyE", shift: true, ctrl: true, alt: true, accentKey: keyOgonek }, + "Ě": { key: "KeyE", shift: true, accentKey: keyHacek }, + "É": { key: "KeyE", shift: true, accentKey: keyAcute }, + "Ê": { key: "KeyE", shift: true, accentKey: keyHat }, + "È": { key: "KeyE", shift: true, accentKey: keyGrave }, + "Ẽ": { key: "KeyE", shift: true, accentKey: keyTylda }, + e: { key: "KeyE" }, + "ę": { key: "KeyE", ctrl: true, meta: true, accentKey: keyOgonek }, + "ë": { key: "KeyE", accentKey: keyDiaresis }, + "ě": { key: "KeyE", accentKey: keyHacek }, + "é": { key: "KeyE", accentKey: keyAcute }, + "ê": { key: "KeyE", accentKey: keyHat }, + "è": { key: "KeyE", accentKey: keyGrave }, + "ẽ": { key: "KeyE", accentKey: keyTylda }, + + F: { key: "KeyF", shift: true }, + f: { key: "KeyF" }, + + G: { key: "KeyG", shift: true }, + g: { key: "KeyG" }, + + H: { key: "KeyH", shift: true }, + h: { key: "KeyH" }, + + I: { key: "KeyI", shift: true }, + "Ï": { key: "KeyI", shift: true, accentKey: keyDiaresis }, + "Í": { key: "KeyI", shift: true, accentKey: keyAcute }, + "Î": { key: "KeyI", shift: true, accentKey: keyHat }, + "Ì": { key: "KeyI", shift: true, accentKey: keyGrave }, + "Ĩ": { key: "KeyI", shift: true, accentKey: keyTylda }, + i: { key: "KeyI" }, + "ï": { key: "KeyI", accentKey: keyDiaresis }, + "í": { key: "KeyI", accentKey: keyAcute }, + "î": { key: "KeyI", accentKey: keyHat }, + "ì": { key: "KeyI", accentKey: keyGrave }, + "ĩ": { key: "KeyI", accentKey: keyTylda }, + + + J: { key: "KeyJ", shift: true }, + j: { key: "KeyJ" }, + + K: { key: "KeyK", shift: true }, + k: { key: "KeyK" }, + + L: { key: "KeyL", shift: true }, + l: { key: "KeyL" }, + + M: { key: "KeyM", shift: true }, + m: { key: "KeyM" }, + + N: { key: "KeyN", shift: true }, + "Ň": { key: "KeyN", shift: true, accentKey: keyHacek }, + n: { key: "KeyN" }, + "ň": { key: "KeyR", accentKey: keyHacek }, + + O: { key: "KeyO", shift: true }, + "Ö": { key: "KeyO", shift: true, accentKey: keyDiaresis }, + "Ő": { key: "KeyO", shift: true, accentKey: keyDoubleAcute }, + "Ó": { key: "KeyO", shift: true, accentKey: keyAcute }, // "Ó": { key: "KeyO", shift: true, ctrl: true, alt: true }, + "Ô": { key: "KeyO", shift: true, accentKey: keyHat }, + "Ò": { key: "KeyO", shift: true, accentKey: keyGrave }, + "Õ": { key: "KeyO", shift: true, accentKey: keyTylda }, + o: { key: "KeyO" }, + "ó": { key: "KeyO", ctrl: true, alt: true, accentKey: keyAcute }, + "ö": { key: "KeyO", accentKey: keyDiaresis }, + "ő": { key: "KeyO", accentKey: keyDoubleAcute }, + "ô": { key: "KeyO", accentKey: keyHat }, + "ò": { key: "KeyO", accentKey: keyGrave }, + "õ": { key: "KeyO", accentKey: keyTylda }, + + P: { key: "KeyP", shift: true }, + p: { key: "KeyP" }, + + Q: { key: "KeyQ", shift: true }, + q: { key: "KeyQ" }, + + R: { key: "KeyR", shift: true }, + "Ř": { key: "KeyR", shift: true, accentKey: keyHacek }, + r: { key: "KeyR" }, + "ř": { key: "KeyR", accentKey: keyHacek }, + + S: { key: "KeyS", shift: true }, + "Š": { key: "KeyS", shift: true, accentKey: keyHacek }, + "Ş": { key: "KeyS", shift: true, accentKey: keyCedilla }, + s: { key: "KeyS" }, + "š": { key: "KeyS", accentKey: keyHacek }, + "ş": { key: "KeyS", accentKey: keyCedilla }, + + T: { key: "KeyT", shift: true }, + "Ť": { key: "KeyT", shift: true, accentKey: keyHacek }, + "Ţ": { key: "KeyT", shift: true, accentKey: keyCedilla }, + t: { key: "KeyT" }, + "ť": { key: "KeyT", accentKey: keyHacek }, + "ţ": { key: "KeyS", accentKey: keyCedilla }, + + U: { key: "KeyU", shift: true }, + "Ü": { key: "KeyU", shift: true, accentKey: keyDiaresis }, + "Ű": { key: "KeyU", shift: true, accentKey: keyDoubleAcute }, + "Ú": { key: "KeyU", shift: true, accentKey: keyAcute }, + "Û": { key: "KeyU", shift: true, accentKey: keyHat }, + "Ù": { key: "KeyU", shift: true, accentKey: keyGrave }, + "Ũ": { key: "KeyU", shift: true, accentKey: keyTylda }, + u: { key: "KeyU" }, + "€": { key: "KeyU", ctrl: true, alt: true }, + "ü": { key: "KeyU", accentKey: keyDiaresis }, + "ű": { key: "KeyU", accentKey: keyDoubleAcute }, + "ú": { key: "KeyU", accentKey: keyAcute }, + "û": { key: "KeyU", accentKey: keyHat }, + "ù": { key: "KeyU", accentKey: keyGrave }, + "ũ": { key: "KeyU", accentKey: keyTylda }, + + V: { key: "KeyV", shift: true }, + v: { key: "KeyV" }, + + W: { key: "KeyW", shift: true }, + w: { key: "KeyW" }, + + X: { key: "KeyX", shift: true }, + x: { key: "KeyX" }, + + Y: { key: "KeyY", shift: true }, + y: { key: "KeyY" }, + + Z: { key: "KeyZ", shift: true }, + "Ž": { key: "KeyZ", shift: true, accentKey: keyHacek }, + "Ź": { key: "KeyZ", shift: true, ctrl: true, meta: true, accentKey: keyAcute }, + z: { key: "KeyZ" }, + "ž": { key: "KeyZ", accentKey: keyHacek }, + "ź": { key: "KeyX",ctrl: true, meta: true, accentKey: keyAcute }, // not a typo, it's on the X key + "ż": { key: "KeyZ", ctrl: true, meta: true, accentKey: keyDotAbove }, + + "º": { key: "Backquote" }, + "ª": { key: "Backquote", shift: true }, + "\\": { key: "Backquote", altRight: true }, + 1: { key: "Digit1" }, + "!": { key: "Digit1", shift: true }, + "|": { key: "Digit1", altRight: true }, + 2: { key: "Digit2" }, + "\"": { key: "Digit2", shift: true }, + "@": { key: "Digit2", altRight: true }, + 3: { key: "Digit3" }, + "·": { key: "Digit3", shift: true }, + "#": { key: "Digit3", altRight: true }, + 4: { key: "Digit4" }, + "$": { key: "Digit4", shift: true }, + 5: { key: "Digit5" }, + "%": { key: "Digit5", shift: true }, + 6: { key: "Digit6" }, + "&": { key: "Digit6", shift: true }, + "¬": { key: "Digit6", altRight: true }, + 7: { key: "Digit7" }, + "/": { key: "Digit7", shift: true }, + 8: { key: "Digit8" }, + "(": { key: "Digit8", shift: true }, + 9: { key: "Digit9" }, + ")": { key: "Digit9", shift: true }, + 0: { key: "Digit0" }, + "=": { key: "Digit0", shift: true }, + "'": { key: "Minus" }, + "?": { key: "Minus", shift: true }, + "¡": { key: "Equal", deadKey: true }, + "¿": { key: "Equal", shift: true }, + "[": { key: "BracketLeft", altRight: true }, + "+": { key: "BracketRight" }, + "*": { key: "BracketRight", shift: true }, + "]": { key: "BracketRight", altRight: true }, + "ñ": { key: "Semicolon" }, + "Ñ": { key: "Semicolon", shift: true }, + "{": { key: "Quote", altRight: true }, + "}": { key: "Backslash", altRight: true }, + ",": { key: "Comma" }, + ";": { key: "Comma", shift: true }, + ".": { key: "Period" }, + ":": { key: "Period", shift: true }, + "-": { key: "Slash" }, + "_": { key: "Slash", shift: true }, + "<": { key: "IntlBackslash" }, + ">": { key: "IntlBackslash", shift: true }, + " ": { key: "Space" }, + "ˇ": { key: "Space", accentKey: keyHacek }, + "\n": { key: "Enter" }, + Enter: { key: "Enter" }, + Tab: { key: "Tab" }, +} as Record; + +export const pl_PL_t: KeyboardLayout = { + isoCode: isoCode, + name: name, + chars: chars, + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard +}; \ No newline at end of file diff --git a/ui/src/keyboardLayouts/sv_SE.ts b/ui/src/keyboardLayouts/sv_SE.ts index fbde3d0..388ddf9 100644 --- a/ui/src/keyboardLayouts/sv_SE.ts +++ b/ui/src/keyboardLayouts/sv_SE.ts @@ -1,12 +1,15 @@ import { KeyboardLayout, KeyCombo } from "../keyboardLayouts" -const name = "Svenska"; +import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard -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 -const keyHat = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter -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 +const name = "Svenska"; +const isoCode = "sv-SE"; + +const keyTrema: KeyCombo = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel +const keyAcute: KeyCombo = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter +const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter +const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter +const keyTilde: KeyCombo = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter const chars = { A: { key: "KeyA", shift: true }, @@ -164,7 +167,11 @@ const chars = { } as Record; export const sv_SE: KeyboardLayout = { - isoCode: "sv-SE", + isoCode: isoCode, name: name, - chars: chars + chars: chars, + // TODO need to localize these maps and layouts + keyDisplayMap: en_US.keyDisplayMap, + modifierDisplayMap: en_US.modifierDisplayMap, + virtualKeyboard: en_US.virtualKeyboard }; \ No newline at end of file diff --git a/ui/src/keyboardMappings.ts b/ui/src/keyboardMappings.ts index 7dddd88..c7991d3 100644 --- a/ui/src/keyboardMappings.ts +++ b/ui/src/keyboardMappings.ts @@ -1,20 +1,39 @@ // 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) +// [Universal Serial Bus HID Usage Tables: Section 10](https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf) +// These are all the key codes (not scan codes) that an 85/101/102 keyboard might have on it export const keys = { + Again: 0x79, + AlternateErase: 0x9d, + AltGr: 0xe6, // aka AltRight + AltLeft: 0xe2, + AltRight: 0xe6, + Application: 0x65, ArrowDown: 0x51, ArrowLeft: 0x50, ArrowRight: 0x4f, ArrowUp: 0x52, + Attention: 0x9a, Backquote: 0x35, // aka Grave Backslash: 0x31, Backspace: 0x2a, BracketLeft: 0x2f, // aka LeftBrace BracketRight: 0x30, // aka RightBrace + Cancel: 0x9b, CapsLock: 0x39, + Clear: 0x9c, + ClearAgain: 0xa2, Comma: 0x36, - Compose: 0x65, - ContextMenu: 0x65, // same as Compose + Compose: 0xe3, + ContextMenu: 0x65, + ControlLeft: 0xe0, + ControlRight: 0xe4, + Copy: 0x7c, + CrSel: 0xa3, + CurrencySubunit: 0xb5, + CurrencyUnit: 0xb4, + Cut: 0x7b, + DecimalSeparator: 0xb3, Delete: 0x4c, Digit0: 0x27, Digit1: 0x1e, @@ -30,6 +49,8 @@ export const keys = { Enter: 0x28, Equal: 0x2e, Escape: 0x29, + Execute: 0x74, + ExSel: 0xa4, F1: 0x3a, F2: 0x3b, F3: 0x3c, @@ -54,9 +75,21 @@ export const keys = { F22: 0x71, F23: 0x72, F24: 0x73, - Home: 0x4a, + Find: 0x7e, + Grave: 0x35, HashTilde: 0x32, // non-US # and ~ + Help: 0x75, + Home: 0x4a, Insert: 0x49, + International1: 0x87, + International2: 0x88, + International3: 0x89, + International4: 0x8a, + International5: 0x8b, + International6: 0x8c, + International7: 0x8d, + International8: 0x8e, + International9: 0x8f, IntlBackslash: 0x64, // non-US \ and | KeyA: 0x04, KeyB: 0x05, @@ -84,10 +117,27 @@ export const keys = { KeyX: 0x1b, KeyY: 0x1c, KeyZ: 0x1d, - KeypadExclamation: 0xcf, + LockingCapsLock: 0x82, + LockingNumLock: 0x83, + LockingScrollLock: 0x84, + Lang1: 0x90, // Hangul/English toggle on Korean keyboards + Lang2: 0x91, // Hanja conversion on Korean keyboards + Lang3: 0x92, // Katakana on Japanese keyboards + Lang4: 0x93, // Hiragana on Japanese keyboards + Lang5: 0x94, // Zenkaku/Hankaku toggle on Japanese keyboards + Lang6: 0x95, + Lang7: 0x96, + Lang8: 0x97, + Lang9: 0x98, + Menu: 0x76, + MetaLeft: 0xe3, + MetaRight: 0xe7, Minus: 0x2d, + Mute: 0x7f, NumLock: 0x53, // and Clear Numpad0: 0x62, // and Insert + Numpad00: 0xb0, + Numpad000: 0xb1, Numpad1: 0x59, // and End Numpad2: 0x5a, // and Down Arrow Numpad3: 0x5b, // and Page Down @@ -98,38 +148,111 @@ export const keys = { Numpad8: 0x60, // and Up Arrow Numpad9: 0x61, // and Page Up NumpadAdd: 0x57, + NumpadAnd: 0xc7, + NumpadAt: 0xce, + NumpadBackspace: 0xbb, + NumpadBinary: 0xda, + NumpadCircumflex: 0xc3, + NumpadClear: 0xd8, + NumpadClearEntry: 0xd9, + NumpadColon: 0xcb, NumpadComma: 0x85, NumpadDecimal: 0x63, + NumpadDecimalBase: 0xdc, + NumpadDelete: 0x63, NumpadDivide: 0x54, + NumpadDownArrow: 0x5a, + NumpadEnd: 0x59, NumpadEnter: 0x58, NumpadEqual: 0x67, + NumpadExclamation: 0xcf, + NumpadGreaterThan: 0xc6, + NumpadHexadecimal: 0xdd, + NumpadHome: 0x5f, + NumpadKeyA: 0xbc, + NumpadKeyB: 0xbd, + NumpadKeyC: 0xbe, + NumpadKeyD: 0xbf, + NumpadKeyE: 0xc0, + NumpadKeyF: 0xc1, + NumpadLeftArrow: 0x5c, + NumpadLeftBrace: 0xb8, NumpadLeftParen: 0xb6, + NumpadLessThan: 0xc5, + NumpadLogicalAnd: 0xc8, + NumpadLogicalOr: 0xca, + NumpadMemoryAdd: 0xd3, + NumpadMemoryClear: 0xd2, + NumpadMemoryDivide: 0xd6, + NumpadMemoryMultiply: 0xd5, + NumpadMemoryRecall: 0xd1, + NumpadMemoryStore: 0xd0, + NumpadMemorySubtract: 0xd4, NumpadMultiply: 0x55, + NumpadOctal: 0xdb, + NumpadOctathorpe: 0xcc, + NumpadOr: 0xc9, + NumpadPageDown: 0x5b, + NumpadPageUp: 0x61, + NumpadPercent: 0xc4, + NumpadPlusMinus: 0xd7, + NumpadRightArrow: 0x5e, + NumpadRightBrace: 0xb9, NumpadRightParen: 0xb7, + NumpadSpace: 0xcd, NumpadSubtract: 0x56, + NumpadTab: 0xba, + NumpadUpArrow: 0x60, + NumpadXOR: 0xc2, + Octothorpe: 0x32, // non-US # and ~ + Operation: 0xa1, + Out: 0xa0, PageDown: 0x4e, PageUp: 0x4b, - Period: 0x37, - PrintScreen: 0x46, + Paste: 0x7d, Pause: 0x48, + Period: 0x37, Power: 0x66, + PrintScreen: 0x46, + Prior: 0x9d, Quote: 0x34, // aka Single Quote or Apostrophe + Return: 0x9e, ScrollLock: 0x47, + Select: 0x77, Semicolon: 0x33, - Slash: 0x38, - Space: 0x2c, - SystemRequest: 0x9a, - Tab: 0x2b, - ControlLeft: 0xe0, - ControlRight: 0xe4, + Separator: 0x9f, ShiftLeft: 0xe1, ShiftRight: 0xe5, - AltLeft: 0xe2, - AltRight: 0xe6, - MetaLeft: 0xe3, - MetaRight: 0xe7, + Slash: 0x38, + Space: 0x2c, + Stop: 0x78, + SystemRequest: 0x9a, + Tab: 0x2b, + ThousandsSeparator: 0xb2, + Tilde: 0x35, + Undo: 0x7a, + VolumeDown: 0x81, + VolumeUp: 0x80, } as Record; +export const deadKeys = { + Acute: 0x00b4, + Breve: 0x02d8, + Caron: 0x02c7, + Cedilla: 0x00b8, + Circumflex: 0x005e, // or 0x02c6? + Comma: 0x002c, + Dot: 0x00b7, + DoubleAcute: 0x02dd, + Grave: 0x0060, + Kreis: 0x00b0, + Ogonek: 0x02db, + Ring: 0x02da, + Slash: 0x02f8, + Tilde: 0x007e, + Umlaut: 0x00a8, +} as Record + export const modifiers = { ControlLeft: 0x01, ControlRight: 0x10, @@ -139,6 +262,7 @@ export const modifiers = { AltRight: 0x40, MetaLeft: 0x08, MetaRight: 0x80, + AltGr: 0x01 | 0x40, } as Record; export const hidKeyToModifierMask = { @@ -148,115 +272,18 @@ export const hidKeyToModifierMask = { 0xe3: modifiers.MetaLeft, 0xe4: modifiers.ControlRight, 0xe5: modifiers.ShiftRight, - 0xe6: modifiers.AltRight, + 0xe6: modifiers.AltRight, // can also be AltGr 0xe7: modifiers.MetaRight, } as Record; -export const modifierDisplayMap: Record = { - ControlLeft: "Left Ctrl", - ControlRight: "Right Ctrl", - ShiftLeft: "Left Shift", - ShiftRight: "Right Shift", - AltLeft: "Left Alt", - AltRight: "Right Alt", - MetaLeft: "Left Meta", - MetaRight: "Right Meta", -} as Record; +export const latchingKeys = ["CapsLock", "ScrollLock", "NumLock", "Meta", "Compose", "Kana"]; -export const keyDisplayMap: Record = { - CtrlAltDelete: "Ctrl + Alt + Delete", - AltMetaEscape: "Alt + Meta + Escape", - CtrlAltBackspace: "Ctrl + Alt + Backspace", - Escape: "esc", - Tab: "tab", - Backspace: "backspace", - "(Backspace)": "backspace", - Enter: "enter", - CapsLock: "caps lock", - ShiftLeft: "shift", - ShiftRight: "shift", - ControlLeft: "ctrl", - AltLeft: "alt", - AltRight: "alt", - MetaLeft: "meta", - MetaRight: "meta", - Space: " ", - Insert: "insert", - Home: "home", - PageUp: "page up", - Delete: "delete", - End: "end", - PageDown: "page down", - ArrowLeft: "←", - ArrowRight: "→", - ArrowUp: "↑", - ArrowDown: "↓", - - // Letters - KeyA: "a", KeyB: "b", KeyC: "c", KeyD: "d", KeyE: "e", - KeyF: "f", KeyG: "g", KeyH: "h", KeyI: "i", KeyJ: "j", - KeyK: "k", KeyL: "l", KeyM: "m", KeyN: "n", KeyO: "o", - KeyP: "p", KeyQ: "q", KeyR: "r", KeyS: "s", KeyT: "t", - KeyU: "u", KeyV: "v", KeyW: "w", KeyX: "x", KeyY: "y", - KeyZ: "z", - - // Capital letters - "(KeyA)": "A", "(KeyB)": "B", "(KeyC)": "C", "(KeyD)": "D", "(KeyE)": "E", - "(KeyF)": "F", "(KeyG)": "G", "(KeyH)": "H", "(KeyI)": "I", "(KeyJ)": "J", - "(KeyK)": "K", "(KeyL)": "L", "(KeyM)": "M", "(KeyN)": "N", "(KeyO)": "O", - "(KeyP)": "P", "(KeyQ)": "Q", "(KeyR)": "R", "(KeyS)": "S", "(KeyT)": "T", - "(KeyU)": "U", "(KeyV)": "V", "(KeyW)": "W", "(KeyX)": "X", "(KeyY)": "Y", - "(KeyZ)": "Z", - - // Numbers - Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4", Digit5: "5", - Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9", Digit0: "0", - - // Shifted Numbers - "(Digit1)": "!", "(Digit2)": "@", "(Digit3)": "#", "(Digit4)": "$", "(Digit5)": "%", - "(Digit6)": "^", "(Digit7)": "&", "(Digit8)": "*", "(Digit9)": "(", "(Digit0)": ")", - - // Symbols - Minus: "-", - "(Minus)": "_", - Equal: "=", - "(Equal)": "+", - BracketLeft: "[", - "(BracketLeft)": "{", - BracketRight: "]", - "(BracketRight)": "}", - Backslash: "\\", - "(Backslash)": "|", - Semicolon: ";", - "(Semicolon)": ":", - Quote: "'", - "(Quote)": "\"", - Comma: ",", - "(Comma)": "<", - Period: ".", - "(Period)": ">", - Slash: "/", - "(Slash)": "?", - Backquote: "`", - "(Backquote)": "~", - IntlBackslash: "\\", - - // Function keys - F1: "F1", F2: "F2", F3: "F3", F4: "F4", - F5: "F5", F6: "F6", F7: "F7", F8: "F8", - F9: "F9", F10: "F10", F11: "F11", F12: "F12", - - // Numpad - Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2", - Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5", - Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8", - Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -", - NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .", - NumpadEqual: "Num =", NumpadEnter: "Num Enter", - NumLock: "Num Lock", - - // Modals - PrintScreen: "prt sc", ScrollLock: "scr lk", Pause: "pause", - "(PrintScreen)": "sys rq", "(Pause)": "break", - SystemRequest: "sys rq", Break: "break" -}; +export function decodeModifiers(modifier: number) { + return { + isShiftActive: (modifier & (modifiers.ShiftLeft | modifiers.ShiftRight)) !== 0, + isControlActive: (modifier & (modifiers.ControlLeft | modifiers.ControlRight)) !== 0, + isAltActive: (modifier & (modifiers.AltLeft | modifiers.AltRight)) !== 0, + isMetaActive: (modifier & (modifiers.MetaLeft | modifiers.MetaRight)) !== 0, + isAltGrActive: (modifier & modifiers.AltGr) === modifiers.AltGr, + }; +} \ No newline at end of file diff --git a/ui/src/routes/devices.$id.settings.keyboard.tsx b/ui/src/routes/devices.$id.settings.keyboard.tsx index d740ffb..bdc65cd 100644 --- a/ui/src/routes/devices.$id.settings.keyboard.tsx +++ b/ui/src/routes/devices.$id.settings.keyboard.tsx @@ -1,28 +1,20 @@ -import { useCallback, useEffect, useMemo } from "react"; +import { useCallback, useEffect } from "react"; import { useSettingsStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; -import notifications from "@/notifications"; +import { useKeyboardLayout } from "@/hooks/useKeyboardLayout"; import { SettingsPageHeader } from "@components/SettingsPageheader"; -import { keyboardOptions } from "@/keyboardLayouts"; import { Checkbox } from "@/components/Checkbox"; - -import { SelectMenuBasic } from "../components/SelectMenuBasic"; +import { SelectMenuBasic } from "@/components/SelectMenuBasic"; +import { keyboardOptions } from "@/keyboardLayouts"; +import notifications from "@/notifications"; import { SettingsItem } from "./devices.$id.settings"; export default function SettingsKeyboardRoute() { - const { keyboardLayout, setKeyboardLayout } = useSettingsStore(); + const { setKeyboardLayout } = useSettingsStore(); const { showPressedKeys, setShowPressedKeys } = useSettingsStore(); - - // this ensures we always get the en-US if it hasn't been set yet - // and if we get en_US from the backend, we convert it to en-US - const safeKeyboardLayout = useMemo(() => { - if (keyboardLayout && keyboardLayout.length > 0) - return keyboardLayout.replace("en_US", "en-US"); - return "en-US"; - }, [keyboardLayout]); - + const { keyboard } = useKeyboardLayout(); const layoutOptions = keyboardOptions(); const { send } = useJsonRpc(); @@ -30,21 +22,25 @@ export default function SettingsKeyboardRoute() { useEffect(() => { send("getKeyboardLayout", {}, resp => { if ("error" in resp) return; - setKeyboardLayout(resp.result as string); + const isoCode = resp.result as string; + console.log("Fetched keyboard layout from backend:", isoCode); + if (isoCode && isoCode.length > 0) { + setKeyboardLayout(isoCode); + } }); }, [send, setKeyboardLayout]); const onKeyboardLayoutChange = useCallback( (e: React.ChangeEvent) => { - const layout = e.target.value; - send("setKeyboardLayout", { layout }, resp => { + const isoCode = e.target.value; + send("setKeyboardLayout", { layout: isoCode }, resp => { if ("error" in resp) { notifications.error( `Failed to set keyboard layout: ${resp.error.data || "Unknown error"}`, ); } - notifications.success("Keyboard layout set successfully"); - setKeyboardLayout(layout); + notifications.success("Keyboard layout set successfully to " + isoCode); + setKeyboardLayout(isoCode); }); }, [send, setKeyboardLayout], @@ -58,7 +54,6 @@ export default function SettingsKeyboardRoute() { />
- { /* this menu item could be renamed to plain "Keyboard layout" in the future, when also the virtual keyboard layout mappings are being implemented */ } diff --git a/ui/src/routes/devices.$id.settings.macros.tsx b/ui/src/routes/devices.$id.settings.macros.tsx index fd9270b..7e147f4 100644 --- a/ui/src/routes/devices.$id.settings.macros.tsx +++ b/ui/src/routes/devices.$id.settings.macros.tsx @@ -17,10 +17,10 @@ import { Button } from "@/components/Button"; import EmptyCard from "@/components/EmptyCard"; import Card from "@/components/Card"; import { MAX_TOTAL_MACROS, COPY_SUFFIX, DEFAULT_DELAY } from "@/constants/macros"; -import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings"; import notifications from "@/notifications"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import LoadingSpinner from "@/components/LoadingSpinner"; +import { useKeyboardLayout } from "@/hooks/useKeyboardLayout"; const normalizeSortOrders = (macros: KeySequence[]): KeySequence[] => { return macros.map((macro, index) => ({ @@ -35,6 +35,7 @@ export default function SettingsMacrosRoute() { const [actionLoadingId, setActionLoadingId] = useState(null); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [macroToDelete, setMacroToDelete] = useState(null); + const { keyboard } = useKeyboardLayout(); const isMaxMacrosReached = useMemo( () => macros.length >= MAX_TOTAL_MACROS, @@ -185,7 +186,7 @@ export default function SettingsMacrosRoute() { step.modifiers.map((modifier, idx) => ( - {modifierDisplayMap[modifier] || modifier} + {keyboard.modifierDisplayMap[modifier] || modifier} {idx < step.modifiers.length - 1 && ( @@ -210,7 +211,7 @@ export default function SettingsMacrosRoute() { step.keys.map((key, idx) => ( - {keyDisplayMap[key] || key} + {keyboard.keyDisplayMap[key] || key} {idx < step.keys.length - 1 && ( @@ -297,8 +298,10 @@ export default function SettingsMacrosRoute() { actionLoadingId, handleDeleteMacro, handleMoveMacro, + keyboard.modifierDisplayMap, + keyboard.keyDisplayMap, handleDuplicateMacro, - navigate, + navigate ], ); diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx index 0309ce9..6c9314c 100644 --- a/ui/src/routes/devices.$id.settings.tsx +++ b/ui/src/routes/devices.$id.settings.tsx @@ -17,14 +17,13 @@ import { useResizeObserver } from "usehooks-ts"; import Card from "@/components/Card"; import { LinkButton } from "@/components/Button"; +import { FeatureFlag } from "@/components/FeatureFlag"; import LoadingSpinner from "@/components/LoadingSpinner"; import { useUiStore } from "@/hooks/stores"; import useKeyboard from "@/hooks/useKeyboard"; -import { FeatureFlag } from "../components/FeatureFlag"; import { cx } from "../cva.config"; - /* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */ export default function SettingsRoute() { const location = useLocation(); @@ -65,16 +64,10 @@ export default function SettingsRoute() { }, [width]); useEffect(() => { - // disable focus trap setTimeout(() => { - // Reset keyboard state. In case the user is pressing a key while enabling the sidebar - resetKeyboardState(); setDisableVideoFocusTrap(true); - // For some reason, the focus trap is not disabled immediately - // so we need to blur the active element - (document.activeElement as HTMLElement)?.blur(); - console.debug("Just disabled focus trap"); - }, 300); + resetKeyboardState(); + }, 500); return () => { setDisableVideoFocusTrap(false);