Almost complete implementation of mapped virtual keyboard. Still to implement proper modifer key holding.

This commit is contained in:
William Johnstone 2025-02-27 01:33:22 +00:00
parent 40b1c70be0
commit fb3f5f44fc
No known key found for this signature in database
GPG Key ID: 89703D0D4B3BB0FE
3 changed files with 197 additions and 13 deletions

View File

@ -20,21 +20,40 @@ 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 [chars, setChars] = useState(useKeyboardMappingsStore.chars);
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
useEffect(() => {
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
setKeys(useKeyboardMappingsStore.keys);
//setChars(useKeyboardMappingsStore.chars);
setChars(useKeyboardMappingsStore.chars);
setModifiers(useKeyboardMappingsStore.modifiers);
setMappingsEnabled(useKeyboardMappingsStore.getMappingState());
});
return unsubscribeKeyboardStore; // Cleanup on unmount
}, []);
const [layoutName, setLayoutName] = useState("default");
const [mappingsEnabled, setMappingsEnabled] = useState(useKeyboardMappingsStore.getMappingState());
useEffect(() => {
if (mappingsEnabled) {
if (layoutName == "default" ) {
setLayoutName("mappedLower")
}
if (layoutName == "shift") {
setLayoutName("mappedUpper")
}
} else {
if (layoutName == "mappedLower") {
setLayoutName("default")
}
if (layoutName == "mappedUpper") {
setLayoutName("shift")
}
}
}, [mappingsEnabled, layoutName]);
const keyboardRef = useRef<HTMLDivElement>(null);
const showAttachedVirtualKeyboard = useUiStore(
@ -121,16 +140,28 @@ function KeyboardWrapper() {
};
}, [endDrag, onDrag, startDrag]);
// TODO implement meta key and meta key modifer
// TODO implement hold functionality for key combos. (add a hold button, add all keys to an array, when released send as one)
const onKeyDown = useCallback(
(key: string) => {
const cleanKey = key.replace(/[()]/g, "");
// Mappings
const { key: mappedKey, shift, altLeft, altRight } = chars[cleanKey] ?? {};
const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight";
const isKeyCaps = key === "CapsLock";
const cleanKey = key.replace(/[()]/g, "");
const keyHasShiftModifier = key.includes("(");
const keyHasShiftModifier = (key.includes("(") && key !== "(") || shift;
//TODO remove debug logs
console.log(layoutName)
// Handle toggle of layout for shift or caps lock
const toggleLayout = () => {
setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default"));
if (mappingsEnabled) {
setLayoutName(prevLayout => (prevLayout === "mappedLower" ? "mappedUpper" : "mappedLower"));
} else {
setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default"));
}
};
if (key === "CtrlAltDelete") {
@ -152,10 +183,17 @@ function KeyboardWrapper() {
return;
}
if (isKeyShift || isKeyCaps) {
if (isKeyShift || (!(layoutName == "shift" || layoutName == "mappedUpper") && isCapsLockActive)) {
toggleLayout();
}
if (isCapsLockActive) {
if (layoutName == "shift" || layoutName == "mappedUpper") {
if (!isCapsLockActive) {
toggleLayout();
}
if (isKeyCaps && isCapsLockActive) {
toggleLayout();
setIsCapsLockActive(false);
sendKeyboardEvent([keys["CapsLock"]], []);
return;
@ -164,25 +202,38 @@ function KeyboardWrapper() {
// Handle caps lock state change
if (isKeyCaps) {
toggleLayout();
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[cleanKey] ? [keys[cleanKey]] : [];
const newKeys = keys[mappedKey ?? cleanKey] ? [keys[mappedKey ?? cleanKey]] : [];
const newModifiers =
keyHasShiftModifier && !isCapsLockActive ? [modifiers["ShiftLeft"]] : [];
[
((shift || isKeyShift)? modifiers['ShiftLeft'] : 0),
(altLeft? modifiers['AltLeft'] : 0),
(altRight? modifiers['AltRight'] : 0),
].filter(Boolean);
console.log(newModifiers);
// Update current keys and modifiers
sendKeyboardEvent(newKeys, newModifiers);
sendKeyboardEvent(newKeys, [...new Set(newModifiers)]);
// If shift was used as a modifier and caps lock is not active, revert to default layout
if (keyHasShiftModifier && !isCapsLockActive) {
setLayoutName("default");
setLayoutName(mappingsEnabled ? "mappedLower" : "default");
}
setTimeout(resetKeyboardState, 100);
},
[isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive],
[isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive, mappingsEnabled, chars, keys, modifiers, layoutName],
);
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
@ -398,6 +449,115 @@ function KeyboardWrapper() {
F10: "F10",
F11: "F11",
F12: "F12",
"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",
"!": "!",
"@": "@",
"#": "#",
"$": "$",
"%": "%",
"^": "^",
"&": "&",
"*": "*",
"(": "(",
")": ")",
"-": "-",
"_": "_",
"=": "=",
"+": "+",
"[": "[",
"]": "]",
"{": "{",
"}": "}",
"|": "|",
";": ";",
":": ":",
"'": "'",
"\"": "\"",
",": ",",
"<": "<",
".": ".",
">": ">",
"/": "/",
"?": "?",
"`": "`",
"~": "~",
"\\": "\\"
}}
layout={{
default: [
@ -418,6 +578,25 @@ function KeyboardWrapper() {
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight",
],
mappedLower: [
"CtrlAltDelete AltMetaEscape",
"Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12",
"` 1 2 3 4 5 6 7 8 9 0 - = Backspace",
"Tab q w e r t y u i o p [ ] \\",
"CapsLock a s d f g h j k l ; ' Enter",
"ShiftLeft z x c v b n m , . / ShiftRight",
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight"
],
mappedUpper: [
"CtrlAltDelete AltMetaEscape",
"Escape F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12",
"~ ! @ # $ % ^ & * ( ) _ + Backspace",
"Tab Q W E R T Y U I O P { } |",
"CapsLock A S D F G H J K L : \" Enter",
"ShiftLeft Z X C V B N M < > ? ShiftRight",
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight"
],
}}
disableButtonHold={true}
mergeDisplay={true}

View File

@ -20,6 +20,7 @@ import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./Vide
// TODO Implement keyboard lock API to resolve #127
// https://developer.chrome.com/docs/capabilities/web-apis/keyboard-lock
// An appropriate error message will need to be displayed in order to alert users to browser compatibility issues.
// This requires TLS, waiting on TLS support.
export default function WebRTCVideo() {
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);

View File

@ -580,6 +580,10 @@ class KeyboardMappingsStore {
this._notifySubscribers();
}
getMappingState() {
return this._mappingsEnabled;
}
getLayout() {
return this._layout;
}