@@ -27,6 +27,7 @@ const AttachIcon = ({ className }: { className?: string }) => {
27
27
28
28
function KeyboardWrapper ( ) {
29
29
const [ layoutName , setLayoutName ] = useState ( "default" ) ;
30
+ const [ depressedButtons , setDepressedButtons ] = useState ( "" ) ;
30
31
31
32
const keyboardRef = useRef < HTMLDivElement > ( null ) ;
32
33
const showAttachedVirtualKeyboard = useUiStore (
@@ -54,6 +55,21 @@ function KeyboardWrapper() {
54
55
55
56
const setIsCapsLockActive = useHidStore ( state => state . setIsCapsLockActive ) ;
56
57
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
+
57
73
const startDrag = useCallback ( ( e : MouseEvent | TouchEvent ) => {
58
74
if ( ! keyboardRef . current ) return ;
59
75
if ( e instanceof TouchEvent && e . touches . length > 1 ) return ;
@@ -123,80 +139,123 @@ function KeyboardWrapper() {
123
139
} ;
124
140
} , [ endDrag , onDrag , startDrag ] ) ;
125
141
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
+ ) ;
146
149
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
+ ) ;
152
162
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
+ }
156
173
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
+ ) ;
162
179
163
- setTimeout ( resetKeyboardState , 100 ) ;
164
- return ;
165
- }
180
+ setTimeout ( resetKeyboardState , 100 ) ;
181
+ return ;
182
+ }
166
183
167
- if ( isKeyShift || isKeyCaps ) {
168
- toggleLayout ( ) ;
184
+ if ( key === "CtrlAltBackspace" ) {
185
+ sendKeyboardEvent (
186
+ [ keys [ "Backspace" ] ] ,
187
+ [ modifiers [ "ControlLeft" ] , modifiers [ "AltLeft" ] ] ,
188
+ ) ;
169
189
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
+ }
178
193
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" ] ) ;
183
225
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" ] ) ;
188
228
189
- // Update current keys and modifiers
190
- sendKeyboardEvent ( newKeys , newModifiers ) ;
229
+ if ( isAltActive )
230
+ effectiveMods . push ( modifiers [ "AltLeft" ] ) ;
191
231
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" ] ) ;
195
238
}
196
239
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
+ ] ,
200
259
) ;
201
260
202
261
const virtualKeyboard = useHidStore ( state => state . isVirtualKeyboardEnabled ) ;
@@ -276,12 +335,16 @@ function KeyboardWrapper() {
276
335
< Keyboard
277
336
baseClass = "simple-keyboard-main"
278
337
layoutName = { layoutName }
279
- onKeyPress = { onKeyDown }
338
+ onKeyPress = { onKeyPress }
280
339
buttonTheme = { [
281
340
{
282
341
class : "combination-key" ,
283
342
buttons : "CtrlAltDelete AltMetaEscape CtrlAltBackspace" ,
284
343
} ,
344
+ {
345
+ class : "depressed-key" ,
346
+ buttons : depressedButtons
347
+ } ,
285
348
] }
286
349
display = { keyDisplayMap }
287
350
layout = { {
@@ -305,34 +368,31 @@ function KeyboardWrapper() {
305
368
] ,
306
369
} }
307
370
disableButtonHold = { true }
308
- syncInstanceInputs = { true }
309
- debug = { false }
310
371
/>
311
372
312
373
< div className = "controlArrows" >
313
374
< Keyboard
314
375
baseClass = "simple-keyboard-control"
315
376
theme = "simple-keyboard hg-theme-default hg-layout-default"
316
377
layoutName = { layoutName }
317
- onKeyPress = { onKeyDown }
378
+ onKeyPress = { onKeyPress }
318
379
display = { keyDisplayMap }
319
380
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 " ] ,
322
383
} }
323
- syncInstanceInputs = { true }
324
- debug = { false }
384
+ disableButtonHold = { true }
325
385
/>
326
386
< Keyboard
327
387
baseClass = "simple-keyboard-arrows"
328
388
theme = "simple-keyboard hg-theme-default hg-layout-default"
329
- onKeyPress = { onKeyDown }
389
+ onKeyPress = { onKeyPress }
330
390
display = { keyDisplayMap }
331
391
layout = { {
332
392
default : [ "ArrowUp" , "ArrowLeft ArrowDown ArrowRight" ] ,
393
+ shift : [ "ArrowUp" , "ArrowLeft ArrowDown ArrowRight" ] ,
333
394
} }
334
- syncInstanceInputs = { true }
335
- debug = { false }
395
+ disableButtonHold = { true }
336
396
/>
337
397
</ div >
338
398
</ div >
0 commit comments