Skip to content

Commit 642f0a5

Browse files
committed
Shows what buttons are depressed for sticky keys
Now treats all the Shift/Control/Alt/Meta/AltGr keys as if they were sticky keys so users can click the button and hit the next key,
1 parent d1fd1a5 commit 642f0a5

File tree

4 files changed

+135
-84
lines changed

4 files changed

+135
-84
lines changed

ui/src/components/InfoBar.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/import { useEffect } from "react";
1+
import { useEffect } from "react";
22

33
import { cx } from "@/cva.config";
44
import {
@@ -40,15 +40,11 @@ export default function InfoBar() {
4040
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
4141
const keyboardLedSync = useSettingsStore(state => state.keyboardLedSync);
4242

43-
//const isCapsLockActive = useHidStore(state => state.isCapsLockActive);
44-
const isCapsLockActive = useHidStore(state => state.isCapsLockActive);
4543
const isShiftActive = useHidStore(state => state.isShiftActive);
4644
const isCtrlActive = useHidStore(state => state.isCtrlActive);
4745
const isAltActive = useHidStore(state => state.isAltActive);
4846
const isMetaActive = useHidStore(state => state.isMetaActive);
4947
const isAltGrActive = useHidStore(state => state.isAltGrActive);
50-
const isNumLockActive = useHidStore(state => state.isNumLockActive);
51-
const isScrollLockActive = useHidStore(state => state.isScrollLockActive);
5248

5349
const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse);
5450

ui/src/components/VirtualKeyboard.tsx

Lines changed: 133 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const AttachIcon = ({ className }: { className?: string }) => {
2727

2828
function KeyboardWrapper() {
2929
const [layoutName, setLayoutName] = useState("default");
30+
const [depressedButtons, setDepressedButtons] = useState("");
3031

3132
const keyboardRef = useRef<HTMLDivElement>(null);
3233
const showAttachedVirtualKeyboard = useUiStore(
@@ -54,6 +55,21 @@ function KeyboardWrapper() {
5455

5556
const setIsCapsLockActive = useHidStore(state => state.setIsCapsLockActive);
5657

58+
const isShiftActive = useHidStore(state => state.isShiftActive);
59+
const setIsShiftActive = useHidStore(state => state.setIsShiftActive);
60+
61+
const isCtrlActive = useHidStore(state => state.isCtrlActive);
62+
const setIsCtrlActive = useHidStore(state => state.setIsCtrlActive);
63+
64+
const isAltActive = useHidStore(state => state.isAltActive);
65+
const setIsAltActive = useHidStore(state => state.setIsAltActive);
66+
67+
const isMetaActive = useHidStore(state => state.isMetaActive);
68+
const setIsMetaActive = useHidStore(state => state.setIsMetaActive);
69+
70+
const isAltGrActive = useHidStore(state => state.isAltGrActive);
71+
const setIsAltGrActive = useHidStore(state => state.setIsAltGrActive);
72+
5773
const startDrag = useCallback((e: MouseEvent | TouchEvent) => {
5874
if (!keyboardRef.current) return;
5975
if (e instanceof TouchEvent && e.touches.length > 1) return;
@@ -123,80 +139,123 @@ function KeyboardWrapper() {
123139
};
124140
}, [endDrag, onDrag, startDrag]);
125141

126-
const onKeyDown = useCallback(
127-
(key: string) => {
128-
const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight";
129-
const isKeyCaps = key === "CapsLock";
130-
const cleanKey = key.replace(/[()]/g, "");
131-
const keyHasShiftModifier = key.includes("(");
132-
133-
// Handle toggle of layout for shift or caps lock
134-
const toggleLayout = () => {
135-
setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default"));
136-
};
137-
138-
if (key === "CtrlAltDelete") {
139-
sendKeyboardEvent(
140-
[keys["Delete"]],
141-
[modifiers["ControlLeft"], modifiers["AltLeft"]],
142-
);
143-
setTimeout(resetKeyboardState, 100);
144-
return;
145-
}
142+
useEffect(() => {
143+
// if you have the CapsLock "down", then the shift state is inverted
144+
const effectiveShift = isCapsLockActive ? false === isShiftActive : isShiftActive;
145+
setLayoutName(effectiveShift ? "shift" : "default");
146+
},
147+
[setLayoutName, isCapsLockActive, isShiftActive]
148+
);
146149

147-
if (key === "AltMetaEscape") {
148-
sendKeyboardEvent(
149-
[keys["Escape"]],
150-
[modifiers["MetaLeft"], modifiers["AltLeft"]],
151-
);
150+
// this causes the buttons to look depressed/clicked depending on the sticky state
151+
useEffect(() => {
152+
let buttons = "None "; // make sure we name at least one (fake) button
153+
if (isCapsLockActive) buttons += "CapsLock ";
154+
if (isShiftActive) buttons += "ShiftLeft ShiftRight ";
155+
if (isCtrlActive) buttons += "ControlLeft ControlRight ";
156+
if (isAltActive) buttons += "AltLeft AltRight ";
157+
if (isMetaActive) buttons += "MetaLeft MetaRight ";
158+
setDepressedButtons(buttons.trimEnd());
159+
},
160+
[setDepressedButtons, isCapsLockActive, isShiftActive, isCtrlActive, isAltActive, isMetaActive, isAltGrActive]
161+
);
152162

153-
setTimeout(resetKeyboardState, 100);
154-
return;
155-
}
163+
const onKeyPress = useCallback((key: string) => {
164+
// handle the fake combo keys first
165+
if (key === "CtrlAltDelete") {
166+
sendKeyboardEvent(
167+
[keys["Delete"]],
168+
[modifiers["ControlLeft"], modifiers["AltLeft"]],
169+
);
170+
setTimeout(resetKeyboardState, 100);
171+
return;
172+
}
156173

157-
if (key === "CtrlAltBackspace") {
158-
sendKeyboardEvent(
159-
[keys["Backspace"]],
160-
[modifiers["ControlLeft"], modifiers["AltLeft"]],
161-
);
174+
if (key === "AltMetaEscape") {
175+
sendKeyboardEvent(
176+
[keys["Escape"]],
177+
[modifiers["MetaLeft"], modifiers["AltLeft"]],
178+
);
162179

163-
setTimeout(resetKeyboardState, 100);
164-
return;
165-
}
180+
setTimeout(resetKeyboardState, 100);
181+
return;
182+
}
166183

167-
if (isKeyShift || isKeyCaps) {
168-
toggleLayout();
184+
if (key === "CtrlAltBackspace") {
185+
sendKeyboardEvent(
186+
[keys["Backspace"]],
187+
[modifiers["ControlLeft"], modifiers["AltLeft"]],
188+
);
169189

170-
if (isCapsLockActive) {
171-
if (!isKeyboardLedManagedByHost) {
172-
setIsCapsLockActive(false);
173-
}
174-
sendKeyboardEvent([keys["CapsLock"]], []);
175-
return;
176-
}
177-
}
190+
setTimeout(resetKeyboardState, 100);
191+
return;
192+
}
178193

179-
// Handle caps lock state change
180-
if (isKeyCaps && !isKeyboardLedManagedByHost) {
181-
setIsCapsLockActive(!isCapsLockActive);
182-
}
194+
// strip away the parens for shifted characters
195+
const cleanKey = key.replace(/[()]/g, "");
196+
197+
const passthrough = ["PrintScreen", "SystemRequest", "Pause", "Break", "ScrollLock", "Enter", "Space"].find((value) => value === cleanKey);
198+
199+
if (passthrough) {
200+
emitkeycode(cleanKey);
201+
return;
202+
}
203+
204+
// adjust the sticky state of the Shift/Ctrl/Alt/Meta/AltGr
205+
if (key === "CapsLock" && !isKeyboardLedManagedByHost)
206+
setIsCapsLockActive(!isCapsLockActive);
207+
else if (key === "ShiftLeft" || key === "ShiftRight")
208+
setIsShiftActive(!isShiftActive);
209+
else if (key === "ControlLeft" || key === "ControlRight")
210+
setIsCtrlActive(!isCtrlActive);
211+
else if (key === "AltLeft" || key === "AltRight")
212+
setIsAltActive(!isAltActive);
213+
else if (key === "MetaLeft" || key === "MetaRight")
214+
setIsMetaActive(!isMetaActive);
215+
else if (key === "AltGr")
216+
setIsAltGrActive(!isAltGrActive);
217+
218+
emitkeycode(cleanKey);
219+
220+
function emitkeycode(key: string) {
221+
const effectiveMods: number[] = [];
222+
223+
if (isShiftActive)
224+
effectiveMods.push(modifiers["ShiftLeft"]);
183225

184-
// Collect new active keys and modifiers
185-
const newKeys = keys[cleanKey] ? [keys[cleanKey]] : [];
186-
const newModifiers =
187-
keyHasShiftModifier && !isCapsLockActive ? [modifiers["ShiftLeft"]] : [];
226+
if (isCtrlActive)
227+
effectiveMods.push(modifiers["ControlLeft"]);
188228

189-
// Update current keys and modifiers
190-
sendKeyboardEvent(newKeys, newModifiers);
229+
if (isAltActive)
230+
effectiveMods.push(modifiers["AltLeft"]);
191231

192-
// If shift was used as a modifier and caps lock is not active, revert to default layout
193-
if (keyHasShiftModifier && !isCapsLockActive) {
194-
setLayoutName("default");
232+
if (isMetaActive)
233+
effectiveMods.push(modifiers["MetaLeft"]);
234+
235+
if (isAltGrActive) {
236+
effectiveMods.push(modifiers["MetaRight"]);
237+
effectiveMods.push(modifiers["CtrlLeft"]);
195238
}
196239

197-
setTimeout(resetKeyboardState, 100);
198-
},
199-
[isCapsLockActive, isKeyboardLedManagedByHost, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive],
240+
const keycode = keys[key];
241+
if (keycode) {
242+
// send the keycode with modifiers
243+
sendKeyboardEvent([keycode], effectiveMods);
244+
}
245+
246+
// release the key (if one pressed), but retain the modifiers
247+
setTimeout(() => sendKeyboardEvent([], effectiveMods), 50);
248+
}
249+
},
250+
[isKeyboardLedManagedByHost,
251+
setIsCapsLockActive, isCapsLockActive,
252+
setIsShiftActive, isShiftActive,
253+
setIsCtrlActive, isCtrlActive,
254+
setIsAltActive, isAltActive,
255+
setIsMetaActive, isMetaActive,
256+
setIsAltGrActive, isAltGrActive,
257+
sendKeyboardEvent, resetKeyboardState
258+
],
200259
);
201260

202261
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
@@ -276,12 +335,16 @@ function KeyboardWrapper() {
276335
<Keyboard
277336
baseClass="simple-keyboard-main"
278337
layoutName={layoutName}
279-
onKeyPress={onKeyDown}
338+
onKeyPress={onKeyPress}
280339
buttonTheme={[
281340
{
282341
class: "combination-key",
283342
buttons: "CtrlAltDelete AltMetaEscape CtrlAltBackspace",
284343
},
344+
{
345+
class: "depressed-key",
346+
buttons: depressedButtons
347+
},
285348
]}
286349
display={keyDisplayMap}
287350
layout={{
@@ -305,34 +368,31 @@ function KeyboardWrapper() {
305368
],
306369
}}
307370
disableButtonHold={true}
308-
syncInstanceInputs={true}
309-
debug={false}
310371
/>
311372

312373
<div className="controlArrows">
313374
<Keyboard
314375
baseClass="simple-keyboard-control"
315376
theme="simple-keyboard hg-theme-default hg-layout-default"
316377
layoutName={layoutName}
317-
onKeyPress={onKeyDown}
378+
onKeyPress={onKeyPress}
318379
display={keyDisplayMap}
319380
layout={{
320-
default: ["PrintScreen ScrollLock Pause", "Insert Home Pageup", "Delete End Pagedown"],
321-
shift: ["(PrintScreen) ScrollLock (Pause)", "Insert Home Pageup", "Delete End Pagedown"],
381+
default: ["PrintScreen ScrollLock Pause", "Insert Home PageUp", "Delete End PageDown"],
382+
shift: ["(PrintScreen) ScrollLock (Pause)", "Insert Home PageUp", "Delete End PageDown"],
322383
}}
323-
syncInstanceInputs={true}
324-
debug={false}
384+
disableButtonHold={true}
325385
/>
326386
<Keyboard
327387
baseClass="simple-keyboard-arrows"
328388
theme="simple-keyboard hg-theme-default hg-layout-default"
329-
onKeyPress={onKeyDown}
389+
onKeyPress={onKeyPress}
330390
display={keyDisplayMap}
331391
layout={{
332392
default: ["ArrowUp", "ArrowLeft ArrowDown ArrowRight"],
393+
shift: ["ArrowUp", "ArrowLeft ArrowDown ArrowRight"],
333394
}}
334-
syncInstanceInputs={true}
335-
debug={false}
395+
disableButtonHold={true}
336396
/>
337397
</div>
338398
</div>

ui/src/components/WebRTCVideo.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,16 +370,12 @@ export default function WebRTCVideo() {
370370
// return;
371371
// }
372372

373-
374373
if (!isKeyboardLedManagedByHost) {
375374
setIsNumLockActive(e.getModifierState("NumLock"));
376375
setIsCapsLockActive(e.getModifierState("CapsLock"));
377376
setIsScrollLockActive(e.getModifierState("ScrollLock"));
378377
}
379-
380-
//setIsNumLockActive(e.getModifierState("NumLock"));
381-
//setIsCapsLockActive(e.getModifierState("CapsLock"));
382-
//setIsScrollLockActive(e.getModifierState("ScrollLock"));
378+
383379
setIsShiftActive(e.getModifierState("Shift"))
384380
setIsCtrlActive(e.getModifierState("Control"))
385381
setIsAltActive(e.getModifierState("Alt"))

ui/src/components/popovers/PasteModal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { SettingsPageHeader } from "@components/SettingsPageheader";
1010
import { useJsonRpc } from "@/hooks/useJsonRpc";
1111
import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores";
1212
import { keys, modifiers } from "@/keyboardMappings";
13-
import { layouts, chars } from "@/keyboardLayouts";
1413
import notifications from "@/notifications";
1514

1615
const hidKeyboardPayload = (keys: number[], modifier: number) => {

0 commit comments

Comments
 (0)