From 0e855adc351d9df0f7f488c46ff9e40d5334757d Mon Sep 17 00:00:00 2001 From: William Johnstone Date: Wed, 29 Jan 2025 01:52:13 +0000 Subject: [PATCH] Early implementation of different keyboard layouts. Store is functioning as expected, adding new layouts should be trivial and easily scalable. Implementation is different for each function that uses the keyboard (PasteModal vs Typing in the WebRTC window) these will all require their own testing. --- config.go | 2 + jsonrpc.go | 14 ++ ui/src/components/InfoBar.tsx | 15 +- ui/src/components/VirtualKeyboard.tsx | 14 +- ui/src/components/WebRTCVideo.tsx | 13 +- ui/src/components/popovers/PasteModal.tsx | 24 +- ui/src/components/sidebar/settings.tsx | 49 ++++ ui/src/keyboardMappings.ts | 214 ----------------- ui/src/keyboardMappings/KeyboardLayouts.ts | 25 ++ .../keyboardMappings/KeyboardMappingStore.ts | 39 ++++ ui/src/keyboardMappings/layouts/uk_apple.ts | 24 ++ ui/src/keyboardMappings/layouts/us.ts | 215 ++++++++++++++++++ 12 files changed, 427 insertions(+), 221 deletions(-) delete mode 100644 ui/src/keyboardMappings.ts create mode 100644 ui/src/keyboardMappings/KeyboardLayouts.ts create mode 100644 ui/src/keyboardMappings/KeyboardMappingStore.ts create mode 100644 ui/src/keyboardMappings/layouts/uk_apple.ts create mode 100644 ui/src/keyboardMappings/layouts/us.ts diff --git a/config.go b/config.go index 1636434..3ae4066 100644 --- a/config.go +++ b/config.go @@ -17,6 +17,7 @@ type Config struct { GoogleIdentity string `json:"google_identity"` JigglerEnabled bool `json:"jiggler_enabled"` AutoUpdateEnabled bool `json:"auto_update_enabled"` + KeyboardLayout string `json:"keyboard_layout"` IncludePreRelease bool `json:"include_pre_release"` HashedPassword string `json:"hashed_password"` LocalAuthToken string `json:"local_auth_token"` @@ -29,6 +30,7 @@ const configPath = "/userdata/kvm_config.json" var defaultConfig = &Config{ CloudURL: "https://api.jetkvm.com", AutoUpdateEnabled: true, // Set a default value + KeyboardLayout: "us", } var config *Config diff --git a/jsonrpc.go b/jsonrpc.go index 2ce5f18..f7543bb 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -131,6 +131,18 @@ func rpcGetDeviceID() (string, error) { return GetDeviceID(), nil } +func rpcGetKeyboardLayout() (string, error) { + return config.KeyboardLayout, nil +} + +func rpcSetKeyboardLayout(KeyboardLayout string) (string, error) { + config.KeyboardLayout = KeyboardLayout + if err := SaveConfig(); err != nil { + return config.KeyboardLayout, fmt.Errorf("failed to save config: %w", err) + } + return KeyboardLayout, nil +} + var streamFactor = 1.0 func rpcGetStreamQualityFactor() (float64, error) { @@ -523,6 +535,8 @@ var rpcHandlers = map[string]RPCHandler{ "setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}}, "getJigglerState": {Func: rpcGetJigglerState}, "sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}}, + "getKeyboardLayout": {Func: rpcGetKeyboardLayout}, + "setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"kbLayout"}}, "getStreamQualityFactor": {Func: rpcGetStreamQualityFactor}, "setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}}, "getAutoUpdateState": {Func: rpcGetAutoUpdateState}, diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx index be94043..4490afe 100644 --- a/ui/src/components/InfoBar.tsx +++ b/ui/src/components/InfoBar.tsx @@ -6,10 +6,21 @@ import { useSettingsStore, useVideoStore, } from "@/hooks/stores"; -import { useEffect } from "react"; -import { keys, modifiers } from "@/keyboardMappings"; +import { useEffect, useState } from "react"; +import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore"; export default function InfoBar() { + const [keys, setKeys] = useState(keyboardMappingsStore.keys); + const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); + + useEffect(() => { + const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { + setKeys(keyboardMappingsStore.keys); + setModifiers(keyboardMappingsStore.modifiers); + }); + return unsubscribeKeyboardStore; // Cleanup on unmount + }, []); + const activeKeys = useHidStore(state => state.activeKeys); const activeModifiers = useHidStore(state => state.activeModifiers); const mouseX = useMouseStore(state => state.mouseX); diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx index e3858c0..f056c11 100644 --- a/ui/src/components/VirtualKeyboard.tsx +++ b/ui/src/components/VirtualKeyboard.tsx @@ -7,7 +7,8 @@ import "react-simple-keyboard/build/css/index.css"; import { useHidStore, useUiStore } from "@/hooks/stores"; import { Transition } from "@headlessui/react"; import { cx } from "@/cva.config"; -import { keys, modifiers } from "@/keyboardMappings"; +//import { keys, modifiers } from "@/keyboardMappings/KeyboardMappingStore"; +import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore"; import useKeyboard from "@/hooks/useKeyboard"; import DetachIconRaw from "@/assets/detach-icon.svg"; import AttachIconRaw from "@/assets/attach-icon.svg"; @@ -21,6 +22,17 @@ const AttachIcon = ({ className }: { className?: string }) => { }; function KeyboardWrapper() { + const [keys, setKeys] = useState(keyboardMappingsStore.keys); + const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); + + useEffect(() => { + const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { + setKeys(keyboardMappingsStore.keys); + setModifiers(keyboardMappingsStore.modifiers); + }); + return unsubscribeKeyboardStore; // Cleanup on unmount + }, []); + const [layoutName, setLayoutName] = useState("default"); const keyboardRef = useRef(null); diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index f5f083b..8e6b867 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -7,7 +7,7 @@ import { useUiStore, useVideoStore, } from "@/hooks/stores"; -import { keys, modifiers } from "@/keyboardMappings"; +import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore"; import { useResizeObserver } from "@/hooks/useResizeObserver"; import { cx } from "@/cva.config"; import VirtualKeyboard from "@components/VirtualKeyboard"; @@ -18,6 +18,17 @@ import { useJsonRpc } from "@/hooks/useJsonRpc"; import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./VideoOverlay"; export default function WebRTCVideo() { + const [keys, setKeys] = useState(keyboardMappingsStore.keys); + const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); + + useEffect(() => { + const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { + setKeys(keyboardMappingsStore.keys); + setModifiers(keyboardMappingsStore.modifiers); + }); + return unsubscribeKeyboardStore; // Cleanup on unmount + }, []); + // Video and stream related refs and states const videoElm = useRef(null); const mediaStream = useRTCStore(state => state.mediaStream); diff --git a/ui/src/components/popovers/PasteModal.tsx b/ui/src/components/popovers/PasteModal.tsx index 661c48d..6b3878f 100644 --- a/ui/src/components/popovers/PasteModal.tsx +++ b/ui/src/components/popovers/PasteModal.tsx @@ -9,13 +9,26 @@ 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"; -import { chars, keys, modifiers } from "@/keyboardMappings"; +import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore"; const hidKeyboardPayload = (keys: number[], modifier: number) => { return { keys, modifier }; }; export default function PasteModal() { + const [keys, setKeys] = useState(keyboardMappingsStore.keys); + const [chars, setChars] = useState(keyboardMappingsStore.chars); + const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); + + useEffect(() => { + const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { + setKeys(keyboardMappingsStore.keys); + setChars(keyboardMappingsStore.chars); + setModifiers(keyboardMappingsStore.modifiers); + }); + return unsubscribeKeyboardStore; // Cleanup on unmount + }, []); + const TextAreaRef = useRef(null); const setPasteMode = useHidStore(state => state.setPasteModeEnabled); const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); @@ -41,13 +54,18 @@ export default function PasteModal() { try { for (const char of text) { - const { key, shift } = chars[char] ?? {}; + const { key, shift, alt } = chars[char] ?? {}; if (!key) continue; + // Build the modifier bitmask + const modifier = + (shift ? modifiers["ShiftLeft"] : 0) | + (alt ? modifiers["AltLeft"] : 0); + await new Promise((resolve, reject) => { send( "keyboardReport", - hidKeyboardPayload([keys[key]], shift ? modifiers["ShiftLeft"] : 0), + hidKeyboardPayload([keys[key]], modifier), params => { if ("error" in params) return reject(params.error); send("keyboardReport", hidKeyboardPayload([], 0), params => { diff --git a/ui/src/components/sidebar/settings.tsx b/ui/src/components/sidebar/settings.tsx index ec606a6..f7bd99a 100644 --- a/ui/src/components/sidebar/settings.tsx +++ b/ui/src/components/sidebar/settings.tsx @@ -25,6 +25,8 @@ import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog"; import { LocalDevice } from "@routes/devices.$id"; import { useRevalidator } from "react-router-dom"; import { ShieldCheckIcon } from "@heroicons/react/20/solid"; +import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore"; +import { KeyboardLayout } from "@/keyboardMappings/KeyboardLayouts"; export function SettingsItem({ title, @@ -77,6 +79,7 @@ export default function SettingsSidebar() { const setSidebarView = useUiStore(state => state.setSidebarView); const settings = useSettingsStore(); const [send] = useJsonRpc(); + const [keyboardLayout, setKeyboardLayout] = useState("us"); const [streamQuality, setStreamQuality] = useState("1"); const [autoUpdate, setAutoUpdate] = useState(true); const [devChannel, setDevChannel] = useState(false); @@ -146,6 +149,20 @@ export default function SettingsSidebar() { }); }; + const handleKeyboardLayoutChange = (keyboardLayout: string) => { + send("setKeyboardLayout", { kbLayout: keyboardLayout }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to set keyboard layout: ${resp.error.data || "Unknown error"}`, + ); + return; + } + // TODO set this to update to the actual layout chosen + keyboardMappingsStore.setLayout(KeyboardLayout.UKApple) + setKeyboardLayout(keyboardLayout); + }); + }; + const handleStreamQualityChange = (factor: string) => { send("setStreamQualityFactor", { factor: Number(factor) }, resp => { if ("error" in resp) { @@ -274,6 +291,11 @@ export default function SettingsSidebar() { setDevChannel(resp.result as boolean); }); + send("getKeyboardLayout", {}, resp => { + if ("error" in resp) return; + setKeyboardLayout(String(resp.result)); + }); + send("getStreamQualityFactor", {}, resp => { if ("error" in resp) return; setStreamQuality(String(resp.result)); @@ -509,6 +531,33 @@ export default function SettingsSidebar() {
+
+ +
+ + handleKeyboardLayoutChange(e.target.value)} + /> + +
+
+
; - -export const chars = { - A: { key: "KeyA", shift: true }, - B: { key: "KeyB", shift: true }, - C: { key: "KeyC", shift: true }, - D: { key: "KeyD", shift: true }, - E: { key: "KeyE", shift: true }, - F: { key: "KeyF", shift: true }, - G: { key: "KeyG", shift: true }, - H: { key: "KeyH", shift: true }, - I: { key: "KeyI", shift: true }, - J: { key: "KeyJ", shift: true }, - K: { key: "KeyK", shift: true }, - L: { key: "KeyL", shift: true }, - M: { key: "KeyM", shift: true }, - N: { key: "KeyN", shift: true }, - O: { key: "KeyO", shift: true }, - P: { key: "KeyP", shift: true }, - Q: { key: "KeyQ", shift: true }, - R: { key: "KeyR", shift: true }, - S: { key: "KeyS", shift: true }, - T: { key: "KeyT", shift: true }, - U: { key: "KeyU", shift: true }, - V: { key: "KeyV", shift: true }, - W: { key: "KeyW", shift: true }, - X: { key: "KeyX", shift: true }, - Y: { key: "KeyY", shift: true }, - Z: { key: "KeyZ", shift: true }, - a: { key: "KeyA", shift: false }, - b: { key: "KeyB", shift: false }, - c: { key: "KeyC", shift: false }, - d: { key: "KeyD", shift: false }, - e: { key: "KeyE", shift: false }, - f: { key: "KeyF", shift: false }, - g: { key: "KeyG", shift: false }, - h: { key: "KeyH", shift: false }, - i: { key: "KeyI", shift: false }, - j: { key: "KeyJ", shift: false }, - k: { key: "KeyK", shift: false }, - l: { key: "KeyL", shift: false }, - m: { key: "KeyM", shift: false }, - n: { key: "KeyN", shift: false }, - o: { key: "KeyO", shift: false }, - p: { key: "KeyP", shift: false }, - q: { key: "KeyQ", shift: false }, - r: { key: "KeyR", shift: false }, - s: { key: "KeyS", shift: false }, - t: { key: "KeyT", shift: false }, - u: { key: "KeyU", shift: false }, - v: { key: "KeyV", shift: false }, - w: { key: "KeyW", shift: false }, - x: { key: "KeyX", shift: false }, - y: { key: "KeyY", shift: false }, - z: { key: "KeyZ", shift: false }, - 1: { key: "Digit1", shift: false }, - "!": { key: "Digit1", shift: true }, - 2: { key: "Digit2", shift: false }, - "@": { key: "Digit2", shift: true }, - 3: { key: "Digit3", shift: false }, - "#": { key: "Digit3", shift: true }, - 4: { key: "Digit4", shift: false }, - $: { key: "Digit4", shift: true }, - "%": { key: "Digit5", shift: true }, - 5: { key: "Digit5", shift: false }, - "^": { key: "Digit6", shift: true }, - 6: { key: "Digit6", shift: false }, - "&": { key: "Digit7", shift: true }, - 7: { key: "Digit7", shift: false }, - "*": { key: "Digit8", shift: true }, - 8: { key: "Digit8", shift: false }, - "(": { key: "Digit9", shift: true }, - 9: { key: "Digit9", shift: false }, - ")": { key: "Digit0", shift: true }, - 0: { key: "Digit0", shift: false }, - "-": { key: "Minus", shift: false }, - _: { key: "Minus", shift: true }, - "=": { key: "Equal", shift: false }, - "+": { key: "Equal", shift: true }, - "'": { key: "Quote", shift: false }, - '"': { key: "Quote", shift: true }, - ",": { key: "Comma", shift: false }, - "<": { key: "Comma", shift: true }, - "/": { key: "Slash", shift: false }, - "?": { key: "Slash", shift: true }, - ".": { key: "Period", shift: false }, - ">": { key: "Period", shift: true }, - ";": { key: "Semicolon", shift: false }, - ":": { key: "Semicolon", shift: true }, - "[": { key: "BracketLeft", shift: false }, - "{": { key: "BracketLeft", shift: true }, - "]": { key: "BracketRight", shift: false }, - "}": { key: "BracketRight", shift: true }, - "\\": { key: "Backslash", shift: false }, - "|": { key: "Backslash", shift: true }, - "`": { key: "Backquote", shift: false }, - "~": { key: "Backquote", shift: true }, - "§": { key: "IntlBackslash", shift: false }, - "±": { key: "IntlBackslash", shift: true }, - " ": { key: "Space", shift: false }, - "\n": { key: "Enter", shift: false }, - Enter: { key: "Enter", shift: false }, - Tab: { key: "Tab", shift: false }, -} as Record; - -export const modifiers = { - ControlLeft: 0x01, - ControlRight: 0x10, - ShiftLeft: 0x02, - ShiftRight: 0x20, - AltLeft: 0x04, - AltRight: 0x40, - MetaLeft: 0x08, - MetaRight: 0x80, -} as Record; diff --git a/ui/src/keyboardMappings/KeyboardLayouts.ts b/ui/src/keyboardMappings/KeyboardLayouts.ts new file mode 100644 index 0000000..baadeab --- /dev/null +++ b/ui/src/keyboardMappings/KeyboardLayouts.ts @@ -0,0 +1,25 @@ +import {keysUKApple, charsUKApple, modifiersUKApple } from './layouts/uk_apple'; +import {keysUS, charsUS, modifiersUS } from './layouts/us'; + +export enum KeyboardLayout { + US = "us", + UKApple = "uk_apple", + } + +export function getKeyboardMappings(layout: KeyboardLayout) { + switch (layout) { + case KeyboardLayout.UKApple: + return { + keys: keysUKApple, + chars: charsUKApple, + modifiers: modifiersUKApple, + }; + case KeyboardLayout.US: + default: + return { + keys: keysUS, + chars: charsUS, + modifiers: modifiersUS, + }; + } + } \ No newline at end of file diff --git a/ui/src/keyboardMappings/KeyboardMappingStore.ts b/ui/src/keyboardMappings/KeyboardMappingStore.ts new file mode 100644 index 0000000..2d41bc1 --- /dev/null +++ b/ui/src/keyboardMappings/KeyboardMappingStore.ts @@ -0,0 +1,39 @@ +import { getKeyboardMappings, KeyboardLayout } from "@/keyboardMappings/KeyboardLayouts"; + +// TODO Move this in with all the other stores? + +class KeyboardMappingsStore { + private _layout: KeyboardLayout = KeyboardLayout.US; + private _subscribers: (() => void)[] = []; + + public keys = getKeyboardMappings(this._layout).keys; + public chars = getKeyboardMappings(this._layout).chars; + public modifiers = getKeyboardMappings(this._layout).modifiers; + + setLayout(newLayout: KeyboardLayout) { + if (this._layout === newLayout) return; + this._layout = newLayout; + const updatedMappings = getKeyboardMappings(newLayout); + this.keys = updatedMappings.keys; + this.chars = updatedMappings.chars; + this.modifiers = updatedMappings.modifiers; + this._notifySubscribers(); + } + + getLayout() { + return this._layout; + } + + subscribe(callback: () => void) { + this._subscribers.push(callback); + return () => { + this._subscribers = this._subscribers.filter(sub => sub !== callback); // Cleanup + }; + } + + private _notifySubscribers() { + this._subscribers.forEach(callback => callback()); + } +} + +export const keyboardMappingsStore = new KeyboardMappingsStore(); \ No newline at end of file diff --git a/ui/src/keyboardMappings/layouts/uk_apple.ts b/ui/src/keyboardMappings/layouts/uk_apple.ts new file mode 100644 index 0000000..b9107ea --- /dev/null +++ b/ui/src/keyboardMappings/layouts/uk_apple.ts @@ -0,0 +1,24 @@ +import { charsUS, keysUS, modifiersUS } from "./us"; + +// Extend US Keys with UK Apple-specific changes +export const keysUKApple = { + ...keysUS, +} as Record; + +// Extend US Chars with UK Apple-specific changes +export const charsUKApple = { + ...charsUS, + "`": { key: "Backquote", shift: false }, + "~": { key: "Backquote", shift: true }, + "\\" : { key: "Backslash", shift: false }, + "|": { key: "Backslash", shift: true }, + "#": { key: "Digit3", shift: false, alt: true }, + "£": { key: "Digit3", shift: true }, + "@": { key: "Digit2", shift: true }, + "\"": { key: "Quote", shift: true }, +} as Record; + +// Modifiers are typically the same between UK and US layouts +export const modifiersUKApple = { + ...modifiersUS, +}; \ No newline at end of file diff --git a/ui/src/keyboardMappings/layouts/us.ts b/ui/src/keyboardMappings/layouts/us.ts new file mode 100644 index 0000000..4b75b77 --- /dev/null +++ b/ui/src/keyboardMappings/layouts/us.ts @@ -0,0 +1,215 @@ +export const keysUS = { + AltLeft: 0xe2, + AltRight: 0xe6, + ArrowDown: 0x51, + ArrowLeft: 0x50, + ArrowRight: 0x4f, + ArrowUp: 0x52, + Backquote: 0x35, + Backslash: 0x31, + Backspace: 0x2a, + BracketLeft: 0x2f, + BracketRight: 0x30, + CapsLock: 0x39, + Comma: 0x36, + ContextMenu: 0, + Delete: 0x4c, + Digit0: 0x27, + Digit1: 0x1e, + Digit2: 0x1f, + Digit3: 0x20, + Digit4: 0x21, + Digit5: 0x22, + Digit6: 0x23, + Digit7: 0x24, + Digit8: 0x25, + Digit9: 0x26, + End: 0x4d, + Enter: 0x28, + Equal: 0x2e, + Escape: 0x29, + F1: 0x3a, + F2: 0x3b, + F3: 0x3c, + F4: 0x3d, + F5: 0x3e, + F6: 0x3f, + F7: 0x40, + F8: 0x41, + F9: 0x42, + F10: 0x43, + F11: 0x44, + F12: 0x45, + F13: 0x68, + Home: 0x4a, + Insert: 0x49, + IntlBackslash: 0x31, + KeyA: 0x04, + KeyB: 0x05, + KeyC: 0x06, + KeyD: 0x07, + KeyE: 0x08, + KeyF: 0x09, + KeyG: 0x0a, + KeyH: 0x0b, + KeyI: 0x0c, + KeyJ: 0x0d, + KeyK: 0x0e, + KeyL: 0x0f, + KeyM: 0x10, + KeyN: 0x11, + KeyO: 0x12, + KeyP: 0x13, + KeyQ: 0x14, + KeyR: 0x15, + KeyS: 0x16, + KeyT: 0x17, + KeyU: 0x18, + KeyV: 0x19, + KeyW: 0x1a, + KeyX: 0x1b, + KeyY: 0x1c, + KeyZ: 0x1d, + KeypadExclamation: 0xcf, + Minus: 0x2d, + NumLock: 0x53, + Numpad0: 0x62, + Numpad1: 0x59, + Numpad2: 0x5a, + Numpad3: 0x5b, + Numpad4: 0x5c, + Numpad5: 0x5d, + Numpad6: 0x5e, + Numpad7: 0x5f, + Numpad8: 0x60, + Numpad9: 0x61, + NumpadAdd: 0x57, + NumpadDivide: 0x54, + NumpadEnter: 0x58, + NumpadMultiply: 0x55, + NumpadSubtract: 0x56, + NumpadDecimal: 0x63, + PageDown: 0x4e, + PageUp: 0x4b, + Period: 0x37, + Quote: 0x34, + Semicolon: 0x33, + Slash: 0x38, + Space: 0x2c, + Tab: 0x2b, +} as Record; + +export const charsUS = { + A: { key: "KeyA", shift: true }, + B: { key: "KeyB", shift: true }, + C: { key: "KeyC", shift: true }, + D: { key: "KeyD", shift: true }, + E: { key: "KeyE", shift: true }, + F: { key: "KeyF", shift: true }, + G: { key: "KeyG", shift: true }, + H: { key: "KeyH", shift: true }, + I: { key: "KeyI", shift: true }, + J: { key: "KeyJ", shift: true }, + K: { key: "KeyK", shift: true }, + L: { key: "KeyL", shift: true }, + M: { key: "KeyM", shift: true }, + N: { key: "KeyN", shift: true }, + O: { key: "KeyO", shift: true }, + P: { key: "KeyP", shift: true }, + Q: { key: "KeyQ", shift: true }, + R: { key: "KeyR", shift: true }, + S: { key: "KeyS", shift: true }, + T: { key: "KeyT", shift: true }, + U: { key: "KeyU", shift: true }, + V: { key: "KeyV", shift: true }, + W: { key: "KeyW", shift: true }, + X: { key: "KeyX", shift: true }, + Y: { key: "KeyY", shift: true }, + Z: { key: "KeyZ", shift: true }, + a: { key: "KeyA", shift: false }, + b: { key: "KeyB", shift: false }, + c: { key: "KeyC", shift: false }, + d: { key: "KeyD", shift: false }, + e: { key: "KeyE", shift: false }, + f: { key: "KeyF", shift: false }, + g: { key: "KeyG", shift: false }, + h: { key: "KeyH", shift: false }, + i: { key: "KeyI", shift: false }, + j: { key: "KeyJ", shift: false }, + k: { key: "KeyK", shift: false }, + l: { key: "KeyL", shift: false }, + m: { key: "KeyM", shift: false }, + n: { key: "KeyN", shift: false }, + o: { key: "KeyO", shift: false }, + p: { key: "KeyP", shift: false }, + q: { key: "KeyQ", shift: false }, + r: { key: "KeyR", shift: false }, + s: { key: "KeyS", shift: false }, + t: { key: "KeyT", shift: false }, + u: { key: "KeyU", shift: false }, + v: { key: "KeyV", shift: false }, + w: { key: "KeyW", shift: false }, + x: { key: "KeyX", shift: false }, + y: { key: "KeyY", shift: false }, + z: { key: "KeyZ", shift: false }, + 1: { key: "Digit1", shift: false }, + "!": { key: "Digit1", shift: true }, + 2: { key: "Digit2", shift: false }, + "@": { key: "Digit2", shift: true }, + 3: { key: "Digit3", shift: false }, + "#": { key: "Digit3", shift: true }, + 4: { key: "Digit4", shift: false }, + $: { key: "Digit4", shift: true }, + "%": { key: "Digit5", shift: true }, + 5: { key: "Digit5", shift: false }, + "^": { key: "Digit6", shift: true }, + 6: { key: "Digit6", shift: false }, + "&": { key: "Digit7", shift: true }, + 7: { key: "Digit7", shift: false }, + "*": { key: "Digit8", shift: true }, + 8: { key: "Digit8", shift: false }, + "(": { key: "Digit9", shift: true }, + 9: { key: "Digit9", shift: false }, + ")": { key: "Digit0", shift: true }, + 0: { key: "Digit0", shift: false }, + "-": { key: "Minus", shift: false }, + _: { key: "Minus", shift: true }, + "=": { key: "Equal", shift: false }, + "+": { key: "Equal", shift: true }, + "'": { key: "Quote", shift: false }, + '"': { key: "Quote", shift: true }, + ",": { key: "Comma", shift: false }, + "<": { key: "Comma", shift: true }, + "/": { key: "Slash", shift: false }, + "?": { key: "Slash", shift: true }, + ".": { key: "Period", shift: false }, + ">": { key: "Period", shift: true }, + ";": { key: "Semicolon", shift: false }, + ":": { key: "Semicolon", shift: true }, + "[": { key: "BracketLeft", shift: false }, + "{": { key: "BracketLeft", shift: true }, + "]": { key: "BracketRight", shift: false }, + "}": { key: "BracketRight", shift: true }, + "\\": { key: "Backslash", shift: false }, + "|": { key: "Backslash", shift: true }, + "`": { key: "Backquote", shift: false }, + "~": { key: "Backquote", shift: true }, + "§": { key: "IntlBackslash", shift: false }, + "±": { key: "IntlBackslash", shift: true }, + " ": { key: "Space", shift: false }, + "\n": { key: "Enter", shift: false }, + Enter: { key: "Enter", shift: false }, + Tab: { key: "Tab", shift: false }, +} as Record; + +export const modifiersUS = { + ControlLeft: 0x01, + ControlRight: 0x10, + ShiftLeft: 0x02, + ShiftRight: 0x20, + AltLeft: 0x04, + AltRight: 0x40, + MetaLeft: 0x08, + MetaRight: 0x80, +} as Record; + \ No newline at end of file