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