mirror of https://github.com/jetkvm/kvm.git
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:
parent
8ffe66a1bc
commit
0e855adc35
|
@ -17,6 +17,7 @@ type Config struct {
|
||||||
GoogleIdentity string `json:"google_identity"`
|
GoogleIdentity string `json:"google_identity"`
|
||||||
JigglerEnabled bool `json:"jiggler_enabled"`
|
JigglerEnabled bool `json:"jiggler_enabled"`
|
||||||
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
||||||
|
KeyboardLayout string `json:"keyboard_layout"`
|
||||||
IncludePreRelease bool `json:"include_pre_release"`
|
IncludePreRelease bool `json:"include_pre_release"`
|
||||||
HashedPassword string `json:"hashed_password"`
|
HashedPassword string `json:"hashed_password"`
|
||||||
LocalAuthToken string `json:"local_auth_token"`
|
LocalAuthToken string `json:"local_auth_token"`
|
||||||
|
@ -29,6 +30,7 @@ const configPath = "/userdata/kvm_config.json"
|
||||||
var defaultConfig = &Config{
|
var defaultConfig = &Config{
|
||||||
CloudURL: "https://api.jetkvm.com",
|
CloudURL: "https://api.jetkvm.com",
|
||||||
AutoUpdateEnabled: true, // Set a default value
|
AutoUpdateEnabled: true, // Set a default value
|
||||||
|
KeyboardLayout: "us",
|
||||||
}
|
}
|
||||||
|
|
||||||
var config *Config
|
var config *Config
|
||||||
|
|
14
jsonrpc.go
14
jsonrpc.go
|
@ -131,6 +131,18 @@ func rpcGetDeviceID() (string, error) {
|
||||||
return GetDeviceID(), nil
|
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
|
var streamFactor = 1.0
|
||||||
|
|
||||||
func rpcGetStreamQualityFactor() (float64, error) {
|
func rpcGetStreamQualityFactor() (float64, error) {
|
||||||
|
@ -523,6 +535,8 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
||||||
"getJigglerState": {Func: rpcGetJigglerState},
|
"getJigglerState": {Func: rpcGetJigglerState},
|
||||||
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
||||||
|
"getKeyboardLayout": {Func: rpcGetKeyboardLayout},
|
||||||
|
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"kbLayout"}},
|
||||||
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
||||||
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
||||||
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
|
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
|
||||||
|
|
|
@ -6,10 +6,21 @@ import {
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
useVideoStore,
|
useVideoStore,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
|
||||||
|
|
||||||
export default function InfoBar() {
|
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 activeKeys = useHidStore(state => state.activeKeys);
|
||||||
const activeModifiers = useHidStore(state => state.activeModifiers);
|
const activeModifiers = useHidStore(state => state.activeModifiers);
|
||||||
const mouseX = useMouseStore(state => state.mouseX);
|
const mouseX = useMouseStore(state => state.mouseX);
|
||||||
|
|
|
@ -7,7 +7,8 @@ import "react-simple-keyboard/build/css/index.css";
|
||||||
import { useHidStore, useUiStore } from "@/hooks/stores";
|
import { useHidStore, useUiStore } from "@/hooks/stores";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { cx } from "@/cva.config";
|
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 useKeyboard from "@/hooks/useKeyboard";
|
||||||
import DetachIconRaw from "@/assets/detach-icon.svg";
|
import DetachIconRaw from "@/assets/detach-icon.svg";
|
||||||
import AttachIconRaw from "@/assets/attach-icon.svg";
|
import AttachIconRaw from "@/assets/attach-icon.svg";
|
||||||
|
@ -21,6 +22,17 @@ const AttachIcon = ({ className }: { className?: string }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function KeyboardWrapper() {
|
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 [layoutName, setLayoutName] = useState("default");
|
||||||
|
|
||||||
const keyboardRef = useRef<HTMLDivElement>(null);
|
const keyboardRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
useUiStore,
|
useUiStore,
|
||||||
useVideoStore,
|
useVideoStore,
|
||||||
} from "@/hooks/stores";
|
} from "@/hooks/stores";
|
||||||
import { keys, modifiers } from "@/keyboardMappings";
|
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
|
||||||
import { useResizeObserver } from "@/hooks/useResizeObserver";
|
import { useResizeObserver } from "@/hooks/useResizeObserver";
|
||||||
import { cx } from "@/cva.config";
|
import { cx } from "@/cva.config";
|
||||||
import VirtualKeyboard from "@components/VirtualKeyboard";
|
import VirtualKeyboard from "@components/VirtualKeyboard";
|
||||||
|
@ -18,6 +18,17 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./VideoOverlay";
|
import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./VideoOverlay";
|
||||||
|
|
||||||
export default function WebRTCVideo() {
|
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
|
// Video and stream related refs and states
|
||||||
const videoElm = useRef<HTMLVideoElement>(null);
|
const videoElm = useRef<HTMLVideoElement>(null);
|
||||||
const mediaStream = useRTCStore(state => state.mediaStream);
|
const mediaStream = useRTCStore(state => state.mediaStream);
|
||||||
|
|
|
@ -9,13 +9,26 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { LuCornerDownLeft } from "react-icons/lu";
|
import { LuCornerDownLeft } from "react-icons/lu";
|
||||||
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
|
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
|
||||||
import { useClose } from "@headlessui/react";
|
import { useClose } from "@headlessui/react";
|
||||||
import { chars, keys, modifiers } from "@/keyboardMappings";
|
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
|
||||||
|
|
||||||
const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
||||||
return { keys, modifier };
|
return { keys, modifier };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PasteModal() {
|
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 TextAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const setPasteMode = useHidStore(state => state.setPasteModeEnabled);
|
const setPasteMode = useHidStore(state => state.setPasteModeEnabled);
|
||||||
const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
|
const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
|
||||||
|
@ -41,13 +54,18 @@ export default function PasteModal() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const char of text) {
|
for (const char of text) {
|
||||||
const { key, shift } = chars[char] ?? {};
|
const { key, shift, alt } = chars[char] ?? {};
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
|
|
||||||
|
// Build the modifier bitmask
|
||||||
|
const modifier =
|
||||||
|
(shift ? modifiers["ShiftLeft"] : 0) |
|
||||||
|
(alt ? modifiers["AltLeft"] : 0);
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
send(
|
send(
|
||||||
"keyboardReport",
|
"keyboardReport",
|
||||||
hidKeyboardPayload([keys[key]], shift ? modifiers["ShiftLeft"] : 0),
|
hidKeyboardPayload([keys[key]], modifier),
|
||||||
params => {
|
params => {
|
||||||
if ("error" in params) return reject(params.error);
|
if ("error" in params) return reject(params.error);
|
||||||
send("keyboardReport", hidKeyboardPayload([], 0), params => {
|
send("keyboardReport", hidKeyboardPayload([], 0), params => {
|
||||||
|
|
|
@ -25,6 +25,8 @@ import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog";
|
||||||
import { LocalDevice } from "@routes/devices.$id";
|
import { LocalDevice } from "@routes/devices.$id";
|
||||||
import { useRevalidator } from "react-router-dom";
|
import { useRevalidator } from "react-router-dom";
|
||||||
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
|
||||||
|
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
|
||||||
|
import { KeyboardLayout } from "@/keyboardMappings/KeyboardLayouts";
|
||||||
|
|
||||||
export function SettingsItem({
|
export function SettingsItem({
|
||||||
title,
|
title,
|
||||||
|
@ -77,6 +79,7 @@ export default function SettingsSidebar() {
|
||||||
const setSidebarView = useUiStore(state => state.setSidebarView);
|
const setSidebarView = useUiStore(state => state.setSidebarView);
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const [send] = useJsonRpc();
|
const [send] = useJsonRpc();
|
||||||
|
const [keyboardLayout, setKeyboardLayout] = useState("us");
|
||||||
const [streamQuality, setStreamQuality] = useState("1");
|
const [streamQuality, setStreamQuality] = useState("1");
|
||||||
const [autoUpdate, setAutoUpdate] = useState(true);
|
const [autoUpdate, setAutoUpdate] = useState(true);
|
||||||
const [devChannel, setDevChannel] = useState(false);
|
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) => {
|
const handleStreamQualityChange = (factor: string) => {
|
||||||
send("setStreamQualityFactor", { factor: Number(factor) }, resp => {
|
send("setStreamQualityFactor", { factor: Number(factor) }, resp => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
|
@ -274,6 +291,11 @@ export default function SettingsSidebar() {
|
||||||
setDevChannel(resp.result as boolean);
|
setDevChannel(resp.result as boolean);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
send("getKeyboardLayout", {}, resp => {
|
||||||
|
if ("error" in resp) return;
|
||||||
|
setKeyboardLayout(String(resp.result));
|
||||||
|
});
|
||||||
|
|
||||||
send("getStreamQualityFactor", {}, resp => {
|
send("getStreamQualityFactor", {}, resp => {
|
||||||
if ("error" in resp) return;
|
if ("error" in resp) return;
|
||||||
setStreamQuality(String(resp.result));
|
setStreamQuality(String(resp.result));
|
||||||
|
@ -509,6 +531,33 @@ export default function SettingsSidebar() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
|
<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">
|
<div className="pb-2 space-y-4">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
title="Video"
|
title="Video"
|
||||||
|
|
|
@ -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>;
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
@ -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,
|
||||||
|
};
|
|
@ -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>;
|
||||||
|
|
Loading…
Reference in New Issue