mirror of https://github.com/jetkvm/kvm.git
Almost complete implementation of mapped virtual keyboard. Still to implement proper modifer key holding.
This commit is contained in:
parent
40b1c70be0
commit
fb3f5f44fc
|
@ -20,21 +20,40 @@ const AttachIcon = ({ className }: { className?: string }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function KeyboardWrapper() {
|
function KeyboardWrapper() {
|
||||||
// TODO implement virtual keyboard mapping
|
|
||||||
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
//const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
|
||||||
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
|
||||||
setKeys(useKeyboardMappingsStore.keys);
|
setKeys(useKeyboardMappingsStore.keys);
|
||||||
//setChars(useKeyboardMappingsStore.chars);
|
setChars(useKeyboardMappingsStore.chars);
|
||||||
setModifiers(useKeyboardMappingsStore.modifiers);
|
setModifiers(useKeyboardMappingsStore.modifiers);
|
||||||
|
setMappingsEnabled(useKeyboardMappingsStore.getMappingState());
|
||||||
});
|
});
|
||||||
return unsubscribeKeyboardStore; // Cleanup on unmount
|
return unsubscribeKeyboardStore; // Cleanup on unmount
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [layoutName, setLayoutName] = useState("default");
|
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 keyboardRef = useRef<HTMLDivElement>(null);
|
||||||
const showAttachedVirtualKeyboard = useUiStore(
|
const showAttachedVirtualKeyboard = useUiStore(
|
||||||
|
@ -121,16 +140,28 @@ function KeyboardWrapper() {
|
||||||
};
|
};
|
||||||
}, [endDrag, onDrag, startDrag]);
|
}, [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(
|
const onKeyDown = useCallback(
|
||||||
(key: string) => {
|
(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 isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight";
|
||||||
const isKeyCaps = key === "CapsLock";
|
const isKeyCaps = key === "CapsLock";
|
||||||
const cleanKey = key.replace(/[()]/g, "");
|
const keyHasShiftModifier = (key.includes("(") && key !== "(") || shift;
|
||||||
const keyHasShiftModifier = key.includes("(");
|
|
||||||
|
//TODO remove debug logs
|
||||||
|
console.log(layoutName)
|
||||||
|
|
||||||
// Handle toggle of layout for shift or caps lock
|
// Handle toggle of layout for shift or caps lock
|
||||||
const toggleLayout = () => {
|
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") {
|
if (key === "CtrlAltDelete") {
|
||||||
|
@ -152,10 +183,17 @@ function KeyboardWrapper() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKeyShift || isKeyCaps) {
|
if (isKeyShift || (!(layoutName == "shift" || layoutName == "mappedUpper") && isCapsLockActive)) {
|
||||||
toggleLayout();
|
toggleLayout();
|
||||||
|
}
|
||||||
|
|
||||||
if (isCapsLockActive) {
|
if (layoutName == "shift" || layoutName == "mappedUpper") {
|
||||||
|
if (!isCapsLockActive) {
|
||||||
|
toggleLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isKeyCaps && isCapsLockActive) {
|
||||||
|
toggleLayout();
|
||||||
setIsCapsLockActive(false);
|
setIsCapsLockActive(false);
|
||||||
sendKeyboardEvent([keys["CapsLock"]], []);
|
sendKeyboardEvent([keys["CapsLock"]], []);
|
||||||
return;
|
return;
|
||||||
|
@ -164,25 +202,38 @@ function KeyboardWrapper() {
|
||||||
|
|
||||||
// Handle caps lock state change
|
// Handle caps lock state change
|
||||||
if (isKeyCaps) {
|
if (isKeyCaps) {
|
||||||
|
toggleLayout();
|
||||||
setIsCapsLockActive(!isCapsLockActive);
|
setIsCapsLockActive(!isCapsLockActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO remove debug logs
|
||||||
|
console.log(cleanKey)
|
||||||
|
console.log(chars[cleanKey])
|
||||||
|
|
||||||
|
console.log(mappedKey)
|
||||||
|
|
||||||
// Collect new active keys and modifiers
|
// Collect new active keys and modifiers
|
||||||
const newKeys = keys[cleanKey] ? [keys[cleanKey]] : [];
|
const newKeys = keys[mappedKey ?? cleanKey] ? [keys[mappedKey ?? cleanKey]] : [];
|
||||||
const newModifiers =
|
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
|
// 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 shift was used as a modifier and caps lock is not active, revert to default layout
|
||||||
if (keyHasShiftModifier && !isCapsLockActive) {
|
if (keyHasShiftModifier && !isCapsLockActive) {
|
||||||
setLayoutName("default");
|
setLayoutName(mappingsEnabled ? "mappedLower" : "default");
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(resetKeyboardState, 100);
|
setTimeout(resetKeyboardState, 100);
|
||||||
},
|
},
|
||||||
[isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive],
|
[isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive, mappingsEnabled, chars, keys, modifiers, layoutName],
|
||||||
);
|
);
|
||||||
|
|
||||||
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
|
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
|
||||||
|
@ -398,6 +449,115 @@ function KeyboardWrapper() {
|
||||||
F10: "F10",
|
F10: "F10",
|
||||||
F11: "F11",
|
F11: "F11",
|
||||||
F12: "F12",
|
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={{
|
layout={{
|
||||||
default: [
|
default: [
|
||||||
|
@ -418,6 +578,25 @@ function KeyboardWrapper() {
|
||||||
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
|
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
|
||||||
"ControlLeft AltLeft MetaLeft Space MetaRight AltRight",
|
"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}
|
disableButtonHold={true}
|
||||||
mergeDisplay={true}
|
mergeDisplay={true}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./Vide
|
||||||
// TODO Implement keyboard lock API to resolve #127
|
// TODO Implement keyboard lock API to resolve #127
|
||||||
// https://developer.chrome.com/docs/capabilities/web-apis/keyboard-lock
|
// 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.
|
// 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() {
|
export default function WebRTCVideo() {
|
||||||
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
|
||||||
|
|
|
@ -580,6 +580,10 @@ class KeyboardMappingsStore {
|
||||||
this._notifySubscribers();
|
this._notifySubscribers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMappingState() {
|
||||||
|
return this._mappingsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
getLayout() {
|
getLayout() {
|
||||||
return this._layout;
|
return this._layout;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue