@@ -27,6 +27,7 @@ const AttachIcon = ({ className }: { className?: string }) => {
2727
2828function 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 >
0 commit comments