mirror of https://github.com/jetkvm/kvm.git
Compare commits
6 Commits
fc3a3388bd
...
9b8f3ea79e
Author | SHA1 | Date |
---|---|---|
|
9b8f3ea79e | |
|
8732a6aff8 | |
|
f1de6639ef | |
|
5f1e53f24a | |
|
7c40e2e011 | |
|
0e855adc35 |
|
@ -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"`
|
||||
|
@ -30,6 +31,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
|
||||
|
|
|
@ -71,6 +71,7 @@ export LD_LIBRARY_PATH=/oem/usr/lib:\$LD_LIBRARY_PATH
|
|||
# Kill any existing instances of the application
|
||||
killall jetkvm_app || true
|
||||
killall jetkvm_app_debug || true
|
||||
killall jetkvm_native || true
|
||||
|
||||
# Navigate to the directory where the binary will be stored
|
||||
cd "$REMOTE_PATH"
|
||||
|
|
14
jsonrpc.go
14
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) {
|
||||
|
@ -529,6 +541,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},
|
||||
|
|
|
@ -5,11 +5,22 @@ import {
|
|||
useRTCStore,
|
||||
useSettingsStore,
|
||||
useVideoStore,
|
||||
useKeyboardMappingsStore,
|
||||
} from "@/hooks/stores";
|
||||
import { useEffect } from "react";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function InfoBar() {
|
||||
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||
setKeys(useKeyboardMappingsStore.keys);
|
||||
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||
});
|
||||
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||
}, []);
|
||||
|
||||
const activeKeys = useHidStore(state => state.activeKeys);
|
||||
const activeModifiers = useHidStore(state => state.activeModifiers);
|
||||
const mouseX = useMouseStore(state => state.mouseX);
|
||||
|
|
|
@ -4,10 +4,9 @@ import { Button } from "@components/Button";
|
|||
import Card from "@components/Card";
|
||||
import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
||||
import "react-simple-keyboard/build/css/index.css";
|
||||
import { useHidStore, useUiStore } from "@/hooks/stores";
|
||||
import { useHidStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { cx } from "@/cva.config";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import useKeyboard from "@/hooks/useKeyboard";
|
||||
import DetachIconRaw from "@/assets/detach-icon.svg";
|
||||
import AttachIconRaw from "@/assets/attach-icon.svg";
|
||||
|
@ -21,6 +20,20 @@ const AttachIcon = ({ className }: { className?: string }) => {
|
|||
};
|
||||
|
||||
function KeyboardWrapper() {
|
||||
// TODO implement virtual keyboard mapping
|
||||
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||
//const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||
setKeys(useKeyboardMappingsStore.keys);
|
||||
//setChars(useKeyboardMappingsStore.chars);
|
||||
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||
});
|
||||
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||
}, []);
|
||||
|
||||
const [layoutName, setLayoutName] = useState("default");
|
||||
|
||||
const keyboardRef = useRef<HTMLDivElement>(null);
|
||||
|
|
|
@ -6,8 +6,8 @@ import {
|
|||
useSettingsStore,
|
||||
useUiStore,
|
||||
useVideoStore,
|
||||
useKeyboardMappingsStore,
|
||||
} from "@/hooks/stores";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import { useResizeObserver } from "@/hooks/useResizeObserver";
|
||||
import { cx } from "@/cva.config";
|
||||
import VirtualKeyboard from "@components/VirtualKeyboard";
|
||||
|
@ -18,6 +18,22 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
|
|||
import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./VideoOverlay";
|
||||
|
||||
export default function WebRTCVideo() {
|
||||
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||
const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||
|
||||
// This map is used to maintain consistency between localised key mappings
|
||||
const activeKeyState = useRef<Map<string, { mappedKey: string; modifiers: { shift: boolean, altLeft?: boolean, altRight?: boolean}; }>>(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||
setKeys(useKeyboardMappingsStore.keys);
|
||||
setChars(useKeyboardMappingsStore.chars);
|
||||
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||
});
|
||||
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||
}, []);
|
||||
|
||||
// Video and stream related refs and states
|
||||
const videoElm = useRef<HTMLVideoElement>(null);
|
||||
const mediaStream = useRTCStore(state => state.mediaStream);
|
||||
|
@ -148,6 +164,7 @@ export default function WebRTCVideo() {
|
|||
if (blockWheelEvent) return;
|
||||
e.preventDefault();
|
||||
|
||||
// TODO this should be user controllable
|
||||
// Define a scaling factor to adjust scrolling sensitivity
|
||||
const scrollSensitivity = 0.8; // Adjust this value to change scroll speed
|
||||
|
||||
|
@ -163,9 +180,11 @@ export default function WebRTCVideo() {
|
|||
// Invert the scroll value to match expected behavior
|
||||
const invertedScroll = -roundedScroll;
|
||||
|
||||
// TODO remove debug logs
|
||||
console.log("wheelReport", { wheelY: invertedScroll });
|
||||
send("wheelReport", { wheelY: invertedScroll });
|
||||
|
||||
// TODO this is making scrolling feel slow and sluggish, also throwing a violation in chrome
|
||||
setBlockWheelEvent(true);
|
||||
setTimeout(() => setBlockWheelEvent(false), 50);
|
||||
},
|
||||
|
@ -176,13 +195,16 @@ export default function WebRTCVideo() {
|
|||
sendMouseMovement(0, 0, 0);
|
||||
}, [sendMouseMovement]);
|
||||
|
||||
// TODO this needs reworked ot work with mappings
|
||||
// Keyboard-related
|
||||
const handleModifierKeys = useCallback(
|
||||
(e: KeyboardEvent, activeModifiers: number[]) => {
|
||||
(e: KeyboardEvent, activeModifiers: number[], mappedKeyModifers: { shift: boolean; altLeft: boolean; altRight: boolean; }) => {
|
||||
const { shiftKey, ctrlKey, altKey, metaKey } = e;
|
||||
|
||||
const filteredModifiers = activeModifiers.filter(Boolean);
|
||||
// TODO remove debug logging
|
||||
console.log(shiftKey + " " +ctrlKey + " " +altKey + " " +metaKey + " " +mappedKeyModifers.shift + " "+mappedKeyModifers.altLeft + " "+mappedKeyModifers.altRight + " ")
|
||||
|
||||
const filteredModifiers = activeModifiers.filter(Boolean);3
|
||||
// Example: activeModifiers = [0x01, 0x02, 0x04, 0x08]
|
||||
// Assuming 0x01 = ControlLeft, 0x02 = ShiftLeft, 0x04 = AltLeft, 0x08 = MetaLeft
|
||||
return (
|
||||
|
@ -193,6 +215,7 @@ export default function WebRTCVideo() {
|
|||
.filter(
|
||||
modifier =>
|
||||
shiftKey ||
|
||||
mappedKeyModifers.shift ||
|
||||
(modifier !== modifiers["ShiftLeft"] &&
|
||||
modifier !== modifiers["ShiftRight"]),
|
||||
)
|
||||
|
@ -211,6 +234,8 @@ export default function WebRTCVideo() {
|
|||
.filter(
|
||||
modifier =>
|
||||
altKey ||
|
||||
mappedKeyModifers.altLeft ||
|
||||
mappedKeyModifers.altRight ||
|
||||
(modifier !== modifiers["AltLeft"] && modifier !== modifiers["AltRight"]),
|
||||
)
|
||||
// Meta: Keep if Meta is pressed or if the key isn't a Meta key
|
||||
|
@ -231,37 +256,59 @@ export default function WebRTCVideo() {
|
|||
e.preventDefault();
|
||||
const prev = useHidStore.getState();
|
||||
let code = e.code;
|
||||
const key = e.key;
|
||||
const localisedKey = e.key;
|
||||
console.log(e);
|
||||
console.log("Localised Key: " + localisedKey);
|
||||
|
||||
// if (document.activeElement?.id !== "videoFocusTrap") {
|
||||
// if (document.activeElement?.id !== "videoFocusTrap") {hH
|
||||
// console.log("KEYUP: Not focusing on the video", document.activeElement);
|
||||
// return;
|
||||
// }
|
||||
console.log(document.activeElement);
|
||||
//
|
||||
// console.log(document.activeElement);
|
||||
|
||||
setIsNumLockActive(e.getModifierState("NumLock"));
|
||||
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
||||
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
||||
|
||||
if (code == "IntlBackslash" && ["`", "~"].includes(key)) {
|
||||
/*if (code == "IntlBackslash" && ["`", "~"].includes(key)) {
|
||||
code = "Backquote";
|
||||
} else if (code == "Backquote" && ["§", "±"].includes(key)) {
|
||||
code = "IntlBackslash";
|
||||
}
|
||||
}*/
|
||||
|
||||
const { key: mappedKey, shift, altLeft, altRight } = chars[localisedKey] ?? { key: code };
|
||||
//if (!key) continue;
|
||||
console.log("Mapped Key: " + mappedKey)
|
||||
console.log("Current KB Layout:" + useKeyboardMappingsStore.getLayout());
|
||||
console.log(chars[localisedKey]);
|
||||
|
||||
console.log("Shift: " + shift + ", altLeft: " + altLeft + ", altRight: " + altRight)
|
||||
|
||||
// Add the mapped key to keyState
|
||||
activeKeyState.current.set(e.code, { mappedKey, modifiers: {shift, altLeft, altRight}});
|
||||
console.log(activeKeyState)
|
||||
|
||||
// Add the key to the active keys
|
||||
const newKeys = [...prev.activeKeys, keys[code]].filter(Boolean);
|
||||
const newKeys = [...prev.activeKeys, keys[mappedKey]].filter(Boolean);
|
||||
|
||||
// TODO I feel this may not be applying the modifiers correctly, specifically altRight
|
||||
// Add the modifier to the active modifiers
|
||||
const newModifiers = handleModifierKeys(e, [
|
||||
...prev.activeModifiers,
|
||||
modifiers[code],
|
||||
]);
|
||||
(shift? modifiers['ShiftLeft'] : 0),
|
||||
(altLeft? modifiers['AltLeft'] : 0),
|
||||
(altRight? modifiers['AltRight'] : 0),],
|
||||
{shift: shift, altLeft: altLeft? true : false, altRight: altRight ? true : false}
|
||||
);
|
||||
|
||||
// When pressing the meta key + another key, the key will never trigger a keyup
|
||||
// event, so we need to clear the keys after a short delay
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=28089
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1299553
|
||||
// TODO add this to the activekey state
|
||||
// TODO set this to remove from activekeystate as well
|
||||
if (e.metaKey) {
|
||||
setTimeout(() => {
|
||||
const prev = useHidStore.getState();
|
||||
|
@ -277,12 +324,16 @@ export default function WebRTCVideo() {
|
|||
setIsScrollLockActive,
|
||||
handleModifierKeys,
|
||||
sendKeyboardEvent,
|
||||
chars,
|
||||
keys,
|
||||
modifiers,
|
||||
],
|
||||
);
|
||||
|
||||
const keyUpHandler = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
console.log(e)
|
||||
const prev = useHidStore.getState();
|
||||
|
||||
// if (document.activeElement?.id !== "videoFocusTrap") {
|
||||
|
@ -294,15 +345,132 @@ export default function WebRTCVideo() {
|
|||
setIsCapsLockActive(e.getModifierState("CapsLock"));
|
||||
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
||||
|
||||
// Check if the released key is a modifier (e.g., Shift, Alt, Control)
|
||||
const isModifierKey =
|
||||
e.code === "ShiftLeft" ||
|
||||
e.code === "ShiftRight" ||
|
||||
e.code === "AltLeft" ||
|
||||
e.code === "AltRight" ||
|
||||
e.code === "ControlLeft" ||
|
||||
e.code === "ControlRight";
|
||||
|
||||
var newKeys = prev.activeKeys;
|
||||
|
||||
// Handle modifier release
|
||||
if (isModifierKey) {
|
||||
console.log("ITS A MODIFER")
|
||||
// Update all affected keys when this modifier is released
|
||||
activeKeyState.current.forEach((value, code) => {
|
||||
const { mappedKey, modifiers: mappedModifiers} = value;
|
||||
|
||||
// Remove the released modifier from the modifier bitmask
|
||||
//const updatedModifiers = modifiers & ~modifiers[e.code];
|
||||
|
||||
// Recalculate the remapped key based on the updated modifiers
|
||||
//const updatedMappedKey = chars[originalKey]?.key || originalKey;
|
||||
|
||||
var removeCurrentKey = false;
|
||||
|
||||
// Shift Handling
|
||||
if (mappedModifiers.shift && (e.code === "ShiftLeft" || e.code === "ShiftRight")) {
|
||||
activeKeyState.current.delete(code);
|
||||
removeCurrentKey = true;
|
||||
};
|
||||
// Left Alt handling
|
||||
if (mappedModifiers.altLeft && e.code === "AltLeft") {
|
||||
activeKeyState.current.delete(code);
|
||||
removeCurrentKey = true;
|
||||
};
|
||||
// Right Alt handling
|
||||
if (mappedModifiers.altRight && e.code === "AltRight") {
|
||||
activeKeyState.current.delete(code);
|
||||
removeCurrentKey = true;
|
||||
};
|
||||
|
||||
if (removeCurrentKey) {
|
||||
newKeys = newKeys
|
||||
.filter(k => k !== keys[mappedKey]) // Remove the previously mapped key
|
||||
//.concat(keys[updatedMappedKey]) // Add the new remapped key, don't need to do this.
|
||||
.filter(Boolean);
|
||||
};
|
||||
});
|
||||
console.log("prev.activemodifers: " + prev.activeModifiers)
|
||||
console.log("prev.activemodifers.filtered: " + prev.activeModifiers.filter(k => k !== modifiers[e.code]))
|
||||
const newModifiers = handleModifierKeys(
|
||||
e,
|
||||
prev.activeModifiers.filter(k => k !== modifiers[e.code]),
|
||||
{shift: false, altLeft: false, altRight: false}
|
||||
);
|
||||
console.log("New modifiers in keyup: " + newModifiers)
|
||||
|
||||
// Update the keyState
|
||||
/*activeKeyState.current.delete(code);/*.set(code, {
|
||||
mappedKey: updatedMappedKey,
|
||||
modifiers: updatedModifiers,
|
||||
originalKey,
|
||||
});*/
|
||||
|
||||
// Remove the modifer key from keyState
|
||||
activeKeyState.current.delete(e.code);
|
||||
|
||||
// This is required to filter out the alt keys as well as the modifier.
|
||||
newKeys = newKeys
|
||||
.filter(k => k !== keys[e.code]) // Remove the previously mapped key
|
||||
//.concat(keys[updatedMappedKey]) // Add the new remapped key, don't need to do this.
|
||||
.filter(Boolean);
|
||||
|
||||
// Send the updated HID payload
|
||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||
|
||||
return; // Exit as we've already handled the modifier release
|
||||
}
|
||||
|
||||
// Retrieve the mapped key and modifiers from keyState
|
||||
const keyInfo = activeKeyState.current.get(e.code);
|
||||
if (!keyInfo) return; // Ignore if no record exists
|
||||
|
||||
const { mappedKey, modifiers: modifier } = keyInfo;
|
||||
|
||||
// Remove the key from keyState
|
||||
activeKeyState.current.delete(e.code);
|
||||
|
||||
// Filter out the key that was just released
|
||||
newKeys = newKeys.filter(k => k !== keys[mappedKey]).filter(Boolean);
|
||||
console.log(activeKeyState)
|
||||
|
||||
// Filter out the associated modifier
|
||||
//const newModifiers = prev.activeModifiers.filter(k => k !== modifier).filter(Boolean);
|
||||
const newModifiers = handleModifierKeys(
|
||||
e,
|
||||
prev.activeModifiers.filter(k => {
|
||||
if (modifier.shift && k == modifiers["ShiftLeft"]) return false;
|
||||
if (modifier.altLeft && k == modifiers["AltLeft"]) return false;
|
||||
if (modifier.altRight && k == modifiers["AltRight"]) return false;
|
||||
return true;
|
||||
}),
|
||||
{shift: modifier.shift, altLeft: modifier.altLeft? true : false, altRight: modifier.altRight ? true : false}
|
||||
);
|
||||
/*
|
||||
const { key: mappedKey/*, shift, altLeft, altRight*//* } = chars[e.key] ?? { key: e.code };
|
||||
//if (!key) continue;
|
||||
console.log("Mapped Key: " + mappedKey)
|
||||
// Build the modifier bitmask
|
||||
/*const modifier =
|
||||
(shift ? modifiers["ShiftLeft"] : 0) |
|
||||
(altLeft ? modifiers["AltLeft"] : 0) |
|
||||
(altRight ? modifiers["AltRight"] : 0); // This is important for a lot of keyboard layouts, right and left alt have different functions*//*
|
||||
|
||||
// Filtering out the key that was just released (keys[e.code])
|
||||
const newKeys = prev.activeKeys.filter(k => k !== keys[e.code]).filter(Boolean);
|
||||
const newKeys = prev.activeKeys.filter(k => k !== keys[mappedKey]).filter(Boolean);
|
||||
|
||||
// Filter out the modifier that was just released
|
||||
const newModifiers = handleModifierKeys(
|
||||
e,
|
||||
prev.activeModifiers.filter(k => k !== modifiers[e.code]),
|
||||
);
|
||||
*/
|
||||
|
||||
console.log(e.key);
|
||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||
},
|
||||
[
|
||||
|
@ -311,6 +479,9 @@ export default function WebRTCVideo() {
|
|||
setIsScrollLockActive,
|
||||
handleModifierKeys,
|
||||
sendKeyboardEvent,
|
||||
chars,
|
||||
keys,
|
||||
modifiers,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -331,6 +502,7 @@ export default function WebRTCVideo() {
|
|||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
activeKeyState.current.clear();
|
||||
};
|
||||
},
|
||||
[keyDownHandler, keyUpHandler, resetKeyboardState, sendKeyboardEvent],
|
||||
|
|
|
@ -3,19 +3,31 @@ import { GridCard } from "@components/Card";
|
|||
import { TextAreaWithLabel } from "@components/TextArea";
|
||||
import { SectionHeader } from "@components/SectionHeader";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores";
|
||||
import { useHidStore, useRTCStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores";
|
||||
import notifications from "../../notifications";
|
||||
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";
|
||||
|
||||
const hidKeyboardPayload = (keys: number[], modifier: number) => {
|
||||
return { keys, modifier };
|
||||
};
|
||||
|
||||
export default function PasteModal() {
|
||||
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||
const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||
setKeys(useKeyboardMappingsStore.keys);
|
||||
setChars(useKeyboardMappingsStore.chars);
|
||||
setModifiers(useKeyboardMappingsStore.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 +53,19 @@ export default function PasteModal() {
|
|||
|
||||
try {
|
||||
for (const char of text) {
|
||||
const { key, shift } = chars[char] ?? {};
|
||||
const { key, shift, altLeft, altRight } = chars[char] ?? {};
|
||||
if (!key) continue;
|
||||
|
||||
// Build the modifier bitmask
|
||||
const modifier =
|
||||
(shift ? modifiers["ShiftLeft"] : 0) |
|
||||
(altLeft ? modifiers["AltLeft"] : 0) |
|
||||
(altRight ? modifiers["AltRight"] : 0); // This is important for a lot of keyboard layouts, right and left alt have different functions
|
||||
|
||||
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 => {
|
||||
|
@ -123,7 +141,7 @@ export default function PasteModal() {
|
|||
<div className="flex items-center mt-2 gap-x-2">
|
||||
<ExclamationCircleIcon className="w-4 h-4 text-red-500 dark:text-red-400" />
|
||||
<span className="text-xs text-red-500 dark:text-red-400">
|
||||
The following characters won't be pasted:{" "}
|
||||
The following characters won't be pasted as the current keyboard layout does not contain a valid mapping:{" "}
|
||||
{invalidChars.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
useSettingsStore,
|
||||
useUiStore,
|
||||
useUpdateStore,
|
||||
useKeyboardMappingsStore,
|
||||
} from "@/hooks/stores";
|
||||
import { Checkbox } from "@components/Checkbox";
|
||||
import { Button, LinkButton } from "@components/Button";
|
||||
|
@ -77,6 +78,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 +148,19 @@ 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;
|
||||
}
|
||||
useKeyboardMappingsStore.setLayout(keyboardLayout)
|
||||
setKeyboardLayout(keyboardLayout);
|
||||
});
|
||||
};
|
||||
|
||||
const handleStreamQualityChange = (factor: string) => {
|
||||
send("setStreamQualityFactor", { factor: Number(factor) }, resp => {
|
||||
if ("error" in resp) {
|
||||
|
@ -274,6 +289,12 @@ export default function SettingsSidebar() {
|
|||
setDevChannel(resp.result as boolean);
|
||||
});
|
||||
|
||||
send("getKeyboardLayout", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setKeyboardLayout(String(resp.result));
|
||||
useKeyboardMappingsStore.setLayout(String(resp.result))
|
||||
});
|
||||
|
||||
send("getStreamQualityFactor", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setStreamQuality(String(resp.result));
|
||||
|
@ -509,6 +530,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"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist } from "zustand/middleware";
|
||||
import { getKeyboardMappings } from "@/keyboardMappings/KeyboardLayouts";
|
||||
|
||||
// Utility function to append stats to a Map
|
||||
const appendStatToMap = <T extends { timestamp: number }>(
|
||||
|
@ -386,6 +387,8 @@ export const useHidStore = create<HidState>(set => ({
|
|||
activeKeys: [],
|
||||
activeModifiers: [],
|
||||
updateActiveKeysAndModifiers: ({ keys, modifiers }) => {
|
||||
// TODO remove debug logs
|
||||
console.log("keys: " + keys + "modifiers: " + modifiers)
|
||||
return set({ activeKeys: keys, activeModifiers: modifiers });
|
||||
},
|
||||
|
||||
|
@ -528,3 +531,39 @@ export const useLocalAuthModalStore = create<LocalAuthModalState>(set => ({
|
|||
setModalView: view => set({ modalView: view }),
|
||||
setErrorMessage: message => set({ errorMessage: message }),
|
||||
}));
|
||||
|
||||
class KeyboardMappingsStore {
|
||||
private _layout: string = 'us';
|
||||
private _subscribers: (() => void)[] = [];
|
||||
|
||||
public keys = getKeyboardMappings(this._layout).keys;
|
||||
public chars = getKeyboardMappings(this._layout).chars;
|
||||
public modifiers = getKeyboardMappings(this._layout).modifiers;
|
||||
|
||||
setLayout(newLayout: string) {
|
||||
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 useKeyboardMappingsStore = new KeyboardMappingsStore();
|
|
@ -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,20 @@
|
|||
import {keysUKApple, charsUKApple, modifiersUKApple } from './layouts/uk_apple';
|
||||
import {keysUS, charsUS, modifiersUS } from './layouts/us';
|
||||
|
||||
export function getKeyboardMappings(layout: string) {
|
||||
switch (layout) {
|
||||
case "uk_apple":
|
||||
return {
|
||||
keys: keysUKApple,
|
||||
chars: charsUKApple,
|
||||
modifiers: modifiersUKApple,
|
||||
};
|
||||
case "us":
|
||||
default:
|
||||
return {
|
||||
keys: keysUS,
|
||||
chars: charsUS,
|
||||
modifiers: modifiersUS,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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, altLeft: true },
|
||||
"£": { key: "Digit3", shift: true },
|
||||
"@": { key: "Digit2", shift: true },
|
||||
"\"": { key: "Quote", shift: true },
|
||||
} as Record<string, { key: string; shift: boolean; altLeft?: boolean; altRight?: boolean; }>;
|
||||
|
||||
// Modifiers are typically the same between UK and US layouts
|
||||
export const modifiersUKApple = {
|
||||
...modifiersUS,
|
||||
} as Record<string, number>;
|
|
@ -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; shift: boolean; altLeft?: boolean; altRight?: 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