From e53445067e23b9bfe8e23f7be7deac5c61a1d0e0 Mon Sep 17 00:00:00 2001 From: William Johnstone Date: Sat, 12 Apr 2025 17:01:43 +0100 Subject: [PATCH] Update macros to use new keyboard implementation, add keyboard settings to new settings page, update PasteModal to use new keyboard implemention, dropped spanish mappings. --- ui/src/components/MacroStepCard.tsx | 7 +- ui/src/components/VirtualKeyboard.tsx | 11 - ui/src/components/WebRTCVideo.tsx | 19 -- ui/src/components/popovers/PasteModal.tsx | 1 - ui/src/hooks/stores.ts | 2 - ui/src/hooks/useKeyboard.ts | 15 +- ui/src/keyboardMappings/KeyboardLayouts.ts | 193 +++++++++++++++++- ui/src/keyboardMappings/layouts/es.ts | 68 ------ ui/src/routes/devices.$id.settings.macros.tsx | 3 +- ui/src/routes/devices.$id.settings.mouse.tsx | 90 +++++++- ui/src/routes/devices.$id.settings.tsx | 2 +- 11 files changed, 293 insertions(+), 118 deletions(-) delete mode 100644 ui/src/keyboardMappings/layouts/es.ts diff --git a/ui/src/components/MacroStepCard.tsx b/ui/src/components/MacroStepCard.tsx index 8642c28..61ba2bc 100644 --- a/ui/src/components/MacroStepCard.tsx +++ b/ui/src/components/MacroStepCard.tsx @@ -4,21 +4,22 @@ import { Button } from "@/components/Button"; import { Combobox } from "@/components/Combobox"; import { SelectMenuBasic } from "@/components/SelectMenuBasic"; import Card from "@/components/Card"; -import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings"; +import { keyDisplayMap } from "@/keyboardMappings/KeyboardLayouts"; +import { keysUS, modifiersUS } from '../keyboardMappings/layouts/us'; import { MAX_KEYS_PER_STEP, DEFAULT_DELAY } from "@/constants/macros"; import FieldLabel from "@/components/FieldLabel"; // Filter out modifier keys since they're handled in the modifiers section const modifierKeyPrefixes = ['Alt', 'Control', 'Shift', 'Meta']; -const keyOptions = Object.keys(keys) +const keyOptions = Object.keys(keysUS) .filter(key => !modifierKeyPrefixes.some(prefix => key.startsWith(prefix))) .map(key => ({ value: key, label: keyDisplayMap[key] || key, })); -const modifierOptions = Object.keys(modifiers).map(modifier => ({ +const modifierOptions = Object.keys(modifiersUS).map(modifier => ({ value: modifier, label: modifier.replace(/^(Control|Alt|Shift|Meta)(Left|Right)$/, "$1 $2"), })); diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx index b0e3194..da302db 100644 --- a/ui/src/components/VirtualKeyboard.tsx +++ b/ui/src/components/VirtualKeyboard.tsx @@ -157,9 +157,6 @@ function KeyboardWrapper() { const isKeyCaps = key === "CapsLock"; const keyHasShiftModifier = (key.includes("(") && key !== "(") || shift; - //TODO remove debug logs - console.log(layoutName) - // Handle toggle of layout for shift or caps lock const toggleLayout = () => { if (mappingsEnabled) { @@ -211,12 +208,6 @@ function KeyboardWrapper() { setIsCapsLockActive(!isCapsLockActive); } - //TODO remove debug logs - console.log(cleanKey) - console.log(chars[cleanKey]) - - console.log(mappedKey) - // Collect new active keys and modifiers const newKeys = keys[mappedKey ?? cleanKey] ? [keys[mappedKey ?? cleanKey]] : []; const newModifiers = @@ -226,8 +217,6 @@ function KeyboardWrapper() { (altRight? modifiers['AltRight'] : 0), ].filter(Boolean); - console.log(newModifiers); - // Update current keys and modifiers sendKeyboardEvent(newKeys, [...new Set(newModifiers)]); diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index 95d2cd8..f38d74e 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -262,9 +262,6 @@ export default function WebRTCVideo() { (e: KeyboardEvent, activeModifiers: number[], mappedKeyModifers: { shift: boolean; altLeft: boolean; altRight: boolean; }) => { const { shiftKey, ctrlKey, altKey, metaKey } = e; - // TODO remove debug logging - console.log(shiftKey + " " +ctrlKey + " " +altKey + " " +metaKey + " " +mappedKeyModifers.shift + " "+mappedKeyModifers.altLeft + " "+mappedKeyModifers.altRight + " ") - const filteredModifiers = activeModifiers.filter(Boolean); // Example: activeModifiers = [0x01, 0x02, 0x04, 0x08] // Assuming 0x01 = ControlLeft, 0x02 = ShiftLeft, 0x04 = AltLeft, 0x08 = MetaLeft @@ -322,10 +319,7 @@ export default function WebRTCVideo() { e.preventDefault(); const prev = useHidStore.getState(); const code = e.code; - console.log("MAPPING ENABLED: " + settings.keyboardMappingEnabled) var localisedKey = settings.keyboardMappingEnabled ? e.key : code; - console.log(e); - console.log("Localised Key: " + localisedKey); // if (document.activeElement?.id !== "videoFocusTrap") {hH // console.log("KEYUP: Not focusing on the video", document.activeElement); @@ -346,15 +340,9 @@ export default function WebRTCVideo() { 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[mappedKey]].filter(Boolean); @@ -401,7 +389,6 @@ export default function WebRTCVideo() { const keyUpHandler = useCallback( (e: KeyboardEvent) => { e.preventDefault(); - console.log(e) const prev = useHidStore.getState(); setIsNumLockActive(e.getModifierState("NumLock")); @@ -421,7 +408,6 @@ export default function WebRTCVideo() { // 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; @@ -457,14 +443,11 @@ export default function WebRTCVideo() { .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, { @@ -499,7 +482,6 @@ export default function WebRTCVideo() { // 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); @@ -533,7 +515,6 @@ export default function WebRTCVideo() { ); */ - console.log(e.key); sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]); }, [ diff --git a/ui/src/components/popovers/PasteModal.tsx b/ui/src/components/popovers/PasteModal.tsx index fbdab04..e17dd45 100644 --- a/ui/src/components/popovers/PasteModal.tsx +++ b/ui/src/components/popovers/PasteModal.tsx @@ -9,7 +9,6 @@ import { TextAreaWithLabel } from "@components/TextArea"; import { SettingsPageHeader } from "@components/SettingsPageheader"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useHidStore, useRTCStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores"; -import { chars, keys, modifiers } from "@/keyboardMappings"; import notifications from "@/notifications"; const hidKeyboardPayload = (keys: number[], modifier: number) => { diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index 9fcd074..5294a5d 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -497,8 +497,6 @@ export const useHidStore = create(set => ({ activeKeys: [], activeModifiers: [], updateActiveKeysAndModifiers: ({ keys, modifiers }) => { - // TODO remove debug logs - console.log("keys: " + keys + "modifiers: " + modifiers) return set({ activeKeys: keys, activeModifiers: modifiers }); }, diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index 0ce1eef..acc25c8 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -1,8 +1,8 @@ -import { useCallback } from "react"; +import { useCallback, useState, useEffect } from "react"; import { useHidStore, useRTCStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; -import { keys, modifiers } from "@/keyboardMappings"; +import { useKeyboardMappingsStore } from "@/hooks/stores"; export default function useKeyboard() { const [send] = useJsonRpc(); @@ -12,6 +12,17 @@ export default function useKeyboard() { state => state.updateActiveKeysAndModifiers, ); + 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 sendKeyboardEvent = useCallback( (keys: number[], modifiers: number[]) => { if (rpcDataChannel?.readyState !== "open") return; diff --git a/ui/src/keyboardMappings/KeyboardLayouts.ts b/ui/src/keyboardMappings/KeyboardLayouts.ts index 920eeb4..e5e0ad7 100644 --- a/ui/src/keyboardMappings/KeyboardLayouts.ts +++ b/ui/src/keyboardMappings/KeyboardLayouts.ts @@ -2,7 +2,6 @@ import { keysUKApple, charsUKApple, modifiersUKApple } from './layouts/uk_apple' import { keysUK, charsUK, modifiersUK } from './layouts/uk'; import { keysUS, charsUS, modifiersUS } from './layouts/us'; import { keysDE_T1, charsDE_T1, modifiersDE_T1 } from './layouts/de_t1'; -import { keysES, charsES, modifiersES } from './layouts/es'; export function getKeyboardMappings(layout: string) { switch (layout) { @@ -24,12 +23,6 @@ export function getKeyboardMappings(layout: string) { chars: charsDE_T1, modifiers: modifiersDE_T1, }; - case "es-ES": - return { - keys: keysES, - chars: charsES, - modifiers: modifiersES, - }; case "en-US": default: return { @@ -38,4 +31,188 @@ export function getKeyboardMappings(layout: string) { modifiers: modifiersUS, }; } -} \ No newline at end of file +} + +export const modifierDisplayMap: Record = { + ControlLeft: "Left Ctrl", + ControlRight: "Right Ctrl", + ShiftLeft: "Left Shift", + ShiftRight: "Right Shift", + AltLeft: "Left Alt", + AltRight: "Right Alt", + MetaLeft: "Left Meta", + MetaRight: "Right Meta", +} as Record; + +export const keyDisplayMap: Record = { + CtrlAltDelete: "Ctrl + Alt + Delete", + AltMetaEscape: "Alt + Meta + Escape", + Escape: "esc", + Tab: "tab", + Backspace: "backspace", + Enter: "enter", + CapsLock: "caps lock", + ShiftLeft: "shift", + ShiftRight: "shift", + ControlLeft: "ctrl", + AltLeft: "alt", + AltRight: "alt", + MetaLeft: "meta", + MetaRight: "meta", + Space: " ", + Home: "home", + PageUp: "pageup", + Delete: "delete", + End: "end", + PageDown: "pagedown", + ArrowLeft: "←", + ArrowRight: "→", + ArrowUp: "↑", + ArrowDown: "↓", + + // Letters + KeyA: "a", KeyB: "b", KeyC: "c", KeyD: "d", KeyE: "e", + KeyF: "f", KeyG: "g", KeyH: "h", KeyI: "i", KeyJ: "j", + KeyK: "k", KeyL: "l", KeyM: "m", KeyN: "n", KeyO: "o", + KeyP: "p", KeyQ: "q", KeyR: "r", KeyS: "s", KeyT: "t", + KeyU: "u", KeyV: "v", KeyW: "w", KeyX: "x", KeyY: "y", + KeyZ: "z", + + // Numbers + Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4", Digit5: "5", + Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9", Digit0: "0", + + // Symbols + Minus: "-", + Equal: "=", + BracketLeft: "[", + BracketRight: "]", + Backslash: "\\", + Semicolon: ";", + Quote: "'", + Comma: ",", + Period: ".", + Slash: "/", + Backquote: "`", + IntlBackslash: "\\", + + // Function keys + F1: "F1", F2: "F2", F3: "F3", F4: "F4", + F5: "F5", F6: "F6", F7: "F7", F8: "F8", + F9: "F9", F10: "F10", F11: "F11", F12: "F12", + + // Numpad + Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2", + Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5", + Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8", + Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -", + NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .", + NumpadEnter: "Num Enter", + + // Mappings for Keyboard Layout Mapping + "q": "q", + "w": "w", + "e": "e", + "r": "r", + "t": "t", + "y": "y", + "u": "u", + "i": "i", + "o": "o", + "p": "p", + "a": "a", + "s": "s", + "d": "d", + "f": "f", + "g": "g", + "h": "h", + "j": "j", + "k": "k", + "l": "l", + "z": "z", + "x": "x", + "c": "c", + "v": "v", + "b": "b", + "n": "n", + "m": "m", + + "Q": "Q", + "W": "W", + "E": "E", + "R": "R", + "T": "T", + "Y": "Y", + "U": "U", + "I": "I", + "O": "O", + "P": "P", + "A": "A", + "S": "S", + "D": "D", + "F": "F", + "G": "G", + "H": "H", + "J": "J", + "K": "K", + "L": "L", + "Z": "Z", + "X": "X", + "C": "C", + "V": "V", + "B": "B", + "N": "N", + "M": "M", + + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + "6": "6", + "7": "7", + "8": "8", + "9": "9", + "0": "0", + + "!": "!", + "@": "@", + "#": "#", + "$": "$", + "%": "%", + "^": "^", + "&": "&", + "*": "*", + "(": "(", + ")": ")", + + "-": "-", + "_": "_", + + "[": "[", + "]": "]", + "{": "{", + "}": "}", + + "|": "|", + + ";": ";", + ":": ":", + + "'": "'", + "\"": "\"", + + ",": ",", + "<": "<", + + ".": ".", + ">": ">", + + "/": "/", + "?": "?", + + "`": "`", + "~": "~", + + "\\": "\\" +}; \ No newline at end of file diff --git a/ui/src/keyboardMappings/layouts/es.ts b/ui/src/keyboardMappings/layouts/es.ts deleted file mode 100644 index ada24f7..0000000 --- a/ui/src/keyboardMappings/layouts/es.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { charsUS, keysUS, modifiersUS } from "./us"; - -export const keysES = { - ...keysUS, -} as Record; - -export const charsES = { - ...charsUS, - - "ñ": { key: "Semicolon", shift: false }, - "Ñ": { key: "Semicolon", shift: true }, - - "º": { key: "Backquote", shift: false }, - "ª": { key: "Backquote", shift: true }, - - "¡": { key: "Equals", shift: false}, - - "¿": { key: "Slash", shift: false, altRight: true }, - "?": { key: "Slash", shift: true }, - - "|": { key: "Digit1", shift: false, altRight: true }, - - "@": { key: "Digit2", shift: false, altRight: true }, - "\"": { key: "Digit2", shift: true }, - - "·": { key: "Digit3", shift: false, altRight: true }, - "#": { key: "Digit3", shift: true }, - - "$": { key: "Digit4", shift: true }, - "€": { key: "Digit5", shift: false, altRight: true }, - - "&": { key: "Digit6", shift: true }, - - "/": { key: "Digit7", shift: true }, - "(": { key: "Digit8", shift: true }, - ")": { key: "Digit9", shift: true }, - "=": { key: "Digit0", shift: true }, - - "'": { key: "Quote", shift: false }, - "?": { key: "Quote", shift: true }, - - "-": { key: "Minus", shift: false }, - "_": { key: "Minus", shift: true }, - - "`": { key: "IntlBackslash", shift: false }, - "^": { key: "IntlBackslash", shift: true }, - "[": { key: "IntlBackslash", shift: false, altRight: true }, - "{": { key: "IntlBackslash", shift: true, altRight: true }, - - "+": { key: "Equal", shift: true }, - "]": { key: "Equal", shift: false, altRight: true }, - "}": { key: "Equal", shift: true, altRight: true }, - - "<": { key: "Backslash", shift: false }, - ">": { key: "Backslash", shift: true }, - - - ",": { key: "Comma", shift: false }, - ";": { key: "Comma", shift: true }, - - ".": { key: "Period", shift: false }, - ":": { key: "Period", shift: true }, - -} as Record; - -export const modifiersES = { - ...modifiersUS, -} as Record; \ No newline at end of file diff --git a/ui/src/routes/devices.$id.settings.macros.tsx b/ui/src/routes/devices.$id.settings.macros.tsx index f809f57..a48db1f 100644 --- a/ui/src/routes/devices.$id.settings.macros.tsx +++ b/ui/src/routes/devices.$id.settings.macros.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/Button"; import EmptyCard from "@/components/EmptyCard"; import Card from "@/components/Card"; import { MAX_TOTAL_MACROS, COPY_SUFFIX, DEFAULT_DELAY } from "@/constants/macros"; -import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings"; +import { keyDisplayMap, modifierDisplayMap } from "@/keyboardMappings/KeyboardLayouts"; import notifications from "@/notifications"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import LoadingSpinner from "@/components/LoadingSpinner"; @@ -27,6 +27,7 @@ export default function SettingsMacrosRoute() { const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [macroToDelete, setMacroToDelete] = useState(null); + const isMaxMacrosReached = useMemo(() => macros.length >= MAX_TOTAL_MACROS, [macros.length] diff --git a/ui/src/routes/devices.$id.settings.mouse.tsx b/ui/src/routes/devices.$id.settings.mouse.tsx index d6223d0..9ff2e54 100644 --- a/ui/src/routes/devices.$id.settings.mouse.tsx +++ b/ui/src/routes/devices.$id.settings.mouse.tsx @@ -5,7 +5,7 @@ import MouseIcon from "@/assets/mouse-icon.svg"; import PointingFinger from "@/assets/pointing-finger.svg"; import { GridCard } from "@/components/Card"; import { Checkbox } from "@/components/Checkbox"; -import { useDeviceSettingsStore, useSettingsStore } from "@/hooks/stores"; +import { useDeviceSettingsStore, useSettingsStore, useKeyboardMappingsStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import notifications from "@/notifications"; import { SettingsPageHeader } from "@components/SettingsPageheader"; @@ -36,6 +36,39 @@ export default function SettingsKeyboardMouseRoute() { const [send] = useJsonRpc(); + const [keyboardLayout, setKeyboardLayout] = useState("en-US"); + const [kbMappingEnabled, setKeyboardMapping] = useState(false); + + const keyboardMappingEnabled = useSettingsStore(state => state.keyboardMappingEnabled); + const setkeyboardMappingEnabled = useSettingsStore(state => state.setkeyboardMappingEnabled); + + 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 handleKeyboardMappingChange = (enabled: boolean) => { + send("setKeyboardMappingState", { enabled }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to set keyboard maping state state: ${resp.error.data || "Unknown error"}`, + ); + return; + } + setkeyboardMappingEnabled(enabled); + useKeyboardMappingsStore.setMappingsState(enabled); + setKeyboardMapping(enabled); + }); + }; + useEffect(() => { send("getJigglerState", {}, resp => { if ("error" in resp) return; @@ -48,7 +81,21 @@ export default function SettingsKeyboardMouseRoute() { setScrollSensitivity(resp.result as ScrollSensitivity); }); } - }, [isScrollSensitivityEnabled, send, setScrollSensitivity]); + + send("getKeyboardLayout", {}, resp => { + if ("error" in resp) return; + setKeyboardLayout(String(resp.result)); + useKeyboardMappingsStore.setLayout(String(resp.result)) + }); + + send("getKeyboardMappingState", {}, resp => { + if ("error" in resp) return; + setKeyboardMapping(resp.result as boolean); + setkeyboardMappingEnabled(resp.result as boolean); + useKeyboardMappingsStore.setMappingsState(resp.result as boolean); + }); + + }, [isScrollSensitivityEnabled, send, setScrollSensitivity, setkeyboardMappingEnabled, keyboardMappingEnabled, keyboardLayout, setKeyboardLayout]); const handleJigglerChange = (enabled: boolean) => { send("setJigglerState", { enabled }, resp => { @@ -78,6 +125,7 @@ export default function SettingsKeyboardMouseRoute() { [send, setScrollSensitivity], ); + return (
+
+ +
+ + { + handleKeyboardMappingChange(e.target.checked); + }} + /> + + + handleKeyboardLayoutChange(e.target.value)} + /> + +
+
); } diff --git a/ui/src/routes/devices.$id.settings.tsx b/ui/src/routes/devices.$id.settings.tsx index c0b4181..07eb25e 100644 --- a/ui/src/routes/devices.$id.settings.tsx +++ b/ui/src/routes/devices.$id.settings.tsx @@ -148,7 +148,7 @@ export default function SettingsRoute() { >
-

Mouse

+

Mouse & Keyboard