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.
This commit is contained in:
William Johnstone 2025-01-29 01:52:13 +00:00
parent 8ffe66a1bc
commit 0e855adc35
No known key found for this signature in database
GPG Key ID: 89703D0D4B3BB0FE
12 changed files with 427 additions and 221 deletions

View File

@ -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

View File

@ -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},

View File

@ -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);

View File

@ -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<HTMLDivElement>(null);

View File

@ -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<HTMLVideoElement>(null);
const mediaStream = useRTCStore(state => state.mediaStream);

View File

@ -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<HTMLTextAreaElement>(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<void>((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 => {

View File

@ -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() {
</div>
</div>
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
<div className="pb-2 space-y-4">
<SectionHeader
title="Keyboard"
description="Customize keyboard behaviour"
/>
<div className="space-y-4">
<SettingsItem
title="Keyboard Layout"
description="Set keyboard layout (this should match the target machine)"
>
<SelectMenuBasic
size="SM"
label=""
// TODO figure out how to make this selector wider like the EDID one?
//fullWidth
value={keyboardLayout}
options={[
{ value: "uk", label: "GB" },
{ value: "uk_apple", label: "GB Apple" },
{ value: "us", label: "US" },
]}
onChange={e => handleKeyboardLayoutChange(e.target.value)}
/>
</SettingsItem>
</div>
</div>
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
<div className="pb-2 space-y-4">
<SectionHeader
title="Video"

View File

@ -1,214 +0,0 @@
export const keys = {
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<string, number>;
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<string, { key: string | number; shift: boolean }>;
export const modifiers = {
ControlLeft: 0x01,
ControlRight: 0x10,
ShiftLeft: 0x02,
ShiftRight: 0x20,
AltLeft: 0x04,
AltRight: 0x40,
MetaLeft: 0x08,
MetaRight: 0x80,
} as Record<string, number>;

View File

@ -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,
};
}
}

View File

@ -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();

View File

@ -0,0 +1,24 @@
import { charsUS, keysUS, modifiersUS } from "./us";
// Extend US Keys with UK Apple-specific changes
export const keysUKApple = {
...keysUS,
} as Record<string, number>;
// 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<string, { key: string | number; shift: boolean; alt?: boolean; }>;
// Modifiers are typically the same between UK and US layouts
export const modifiersUKApple = {
...modifiersUS,
};

View File

@ -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<string, number>;
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<string, { key: string | number; shift: boolean; alt?: boolean; }>;
export const modifiersUS = {
ControlLeft: 0x01,
ControlRight: 0x10,
ShiftLeft: 0x02,
ShiftRight: 0x20,
AltLeft: 0x04,
AltRight: 0x40,
MetaLeft: 0x08,
MetaRight: 0x80,
} as Record<string, number>;