@@ -306,27 +306,53 @@ Guacamole.OnScreenKeyboard = function(layout) {
306306 // Retrieve originally-pressed keysym, if modifier was already pressed
307307 var originalKeysym = modifierKeysyms [ key . modifier ] ;
308308
309- // Activate modifier if not pressed
310- if ( originalKeysym === undefined ) {
311-
312- addClass ( keyboard , modifierClass ) ;
313- modifierKeysyms [ key . modifier ] = key . keysym ;
314-
315- // Send key event only if keysym is meaningful
309+ // Toggle modifiers send both keydown and keyup on each press
310+ if ( key . toggle ) {
311+
312+ // Toggle the modifier visual state
313+ if ( originalKeysym === undefined ) {
314+ addClass ( keyboard , modifierClass ) ;
315+ modifierKeysyms [ key . modifier ] = key . keysym ;
316+ }
317+ else {
318+ removeClass ( keyboard , modifierClass ) ;
319+ delete modifierKeysyms [ key . modifier ] ;
320+ }
321+
322+ // Send both keydown and keyup to perform the toggle
316323 if ( key . keysym && osk . onkeydown )
317324 osk . onkeydown ( key . keysym ) ;
325+ if ( key . keysym && osk . onkeyup )
326+ osk . onkeyup ( key . keysym ) ;
318327
319328 }
320329
321- // Deactivate if not pressed
330+ // Sticky modifiers: activate on first press, deactivate on second
322331 else {
323332
324- removeClass ( keyboard , modifierClass ) ;
325- delete modifierKeysyms [ key . modifier ] ;
326-
327- // Send key event only if original keysym is meaningful
328- if ( originalKeysym && osk . onkeyup )
329- osk . onkeyup ( originalKeysym ) ;
333+ // Activate modifier if not pressed
334+ if ( originalKeysym === undefined ) {
335+
336+ addClass ( keyboard , modifierClass ) ;
337+ modifierKeysyms [ key . modifier ] = key . keysym ;
338+
339+ // Send key event only if keysym is meaningful
340+ if ( key . keysym && osk . onkeydown )
341+ osk . onkeydown ( key . keysym ) ;
342+
343+ }
344+
345+ // Deactivate if pressed
346+ else {
347+
348+ removeClass ( keyboard , modifierClass ) ;
349+ delete modifierKeysyms [ key . modifier ] ;
350+
351+ // Send key event only if original keysym is meaningful
352+ if ( originalKeysym && osk . onkeyup )
353+ osk . onkeyup ( originalKeysym ) ;
354+
355+ }
330356
331357 }
332358
@@ -928,11 +954,22 @@ Guacamole.OnScreenKeyboard.Key = function(template, name) {
928954 * the names of keys; both the "RightShift" and "LeftShift" keys may set
929955 * the "shift" modifier, for example. By default, the key will affect no
930956 * modifiers.
931- *
957+ *
932958 * @type {string }
933959 */
934960 this . modifier = template . modifier ;
935961
962+ /**
963+ * Whether this key is a toggle modifier. Toggle modifiers send both
964+ * keydown and keyup events when pressed, so each press performs one
965+ * toggle of the modifier state (e.g., Caps Lock, Num Lock, Scroll Lock).
966+ * Non-toggle modifiers remain sticky and are released by a subsequent
967+ * press. By default, modifiers are not toggles.
968+ *
969+ * @type {boolean }
970+ */
971+ this . toggle = template . toggle || false ;
972+
936973 /**
937974 * An array containing the names of each modifier required for this key to
938975 * have an effect. For example, a lowercase letter may require nothing,
0 commit comments