@@ -285,8 +285,10 @@ Guacamole.Keyboard = function Keyboard(element) {
285285 this . keysym = keysym_from_key_identifier ( this . keyIdentifier , this . location , this . modifiers . shift ) ;
286286
287287 // If a key is pressed while meta is held down, the keyup will
288- // never be sent in Chrome (bug #108404)
289- if ( this . modifiers . meta && this . keysym !== 0xFFE7 && this . keysym !== 0xFFE8 )
288+ // never be sent in Chrome (bug #108404). Modifier keys are excluded
289+ // from this workaround as they have reliable keyup events and need
290+ // to be held down simultaneously with Meta.
291+ if ( this . modifiers . meta && ! isModifierKey ( this . keysym ) )
290292 this . keyupReliable = false ;
291293
292294 // We cannot rely on receiving keyup for Caps Lock on certain platforms
@@ -578,6 +580,37 @@ Guacamole.Keyboard = function Keyboard(element) {
578580 "ZenkakuHankaku" : [ 0xFF2A ]
579581 } ;
580582
583+ /**
584+ * All modifier key keysyms, grouped by modifier type.
585+ *
586+ * @private
587+ * @type {!Object.<string, number[]> }
588+ */
589+ var modifierKeysymsByType = {
590+ shift : [ 0xFFE1 , 0xFFE2 ] , // Left shift, Right shift
591+ ctrl : [ 0xFFE3 , 0xFFE4 ] , // Left ctrl, Right ctrl
592+ alt : [ 0xFFE9 , 0xFFEA , 0xFE03 ] , // Left alt, Right alt, AltGr
593+ meta : [ 0xFFE7 , 0xFFE8 ] , // Left meta, Right meta
594+ hyper : [ 0xFFEB , 0xFFEC ] // Left super/hyper, Right super/hyper
595+ } ;
596+
597+ /**
598+ * All modifier key keysyms for quick lookup.
599+ *
600+ * @private
601+ * @type {!Object.<number, boolean> }
602+ */
603+ var modifierKeysyms = ( function ( ) {
604+ var lookup = { } ;
605+ for ( var modifier in modifierKeysymsByType ) {
606+ var keysyms = modifierKeysymsByType [ modifier ] ;
607+ for ( var i = 0 ; i < keysyms . length ; i ++ ) {
608+ lookup [ keysyms [ i ] ] = true ;
609+ }
610+ }
611+ return lookup ;
612+ } ) ( ) ;
613+
581614 /**
582615 * All keysyms which should not repeat when held down.
583616 *
@@ -706,6 +739,34 @@ Guacamole.Keyboard = function Keyboard(element) {
706739
707740 } ;
708741
742+ /**
743+ * Returns true if the given keysym corresponds to a Meta key (left or
744+ * right Meta/Command/Windows key).
745+ *
746+ * @param {!number } keysym
747+ * The keysym to check.
748+ *
749+ * @returns {!boolean }
750+ * true if the given keysym corresponds to a Meta key, false otherwise.
751+ */
752+ var isMetaKey = function isMetaKey ( keysym ) {
753+ return modifierKeysymsByType . meta . indexOf ( keysym ) !== - 1 ;
754+ } ;
755+
756+ /**
757+ * Returns true if the given keysym corresponds to a modifier key
758+ * (Shift, Ctrl, Alt, Meta, Hyper, AltGr).
759+ *
760+ * @param {!number } keysym
761+ * The keysym to check.
762+ *
763+ * @returns {!boolean }
764+ * true if the given keysym corresponds to a modifier key, false otherwise.
765+ */
766+ var isModifierKey = function isModifierKey ( keysym ) {
767+ return modifierKeysyms [ keysym ] === true ;
768+ } ;
769+
709770 function keysym_from_key_identifier ( identifier , location , shifted ) {
710771
711772 if ( ! identifier )
@@ -926,6 +987,39 @@ Guacamole.Keyboard = function Keyboard(element) {
926987
927988 } ;
928989
990+ /**
991+ * Handles a mouse event to resolve deferred Meta key events. When a Meta
992+ * key is pressed, it is deferred to determine if it's being used as a
993+ * modifier or as a standalone key. A mouse click with Meta held provides
994+ * the context needed to resolve this, enabling Cmd+Click functionality.
995+ *
996+ * @param {Guacamole.Mouse.Event } mouseEvent
997+ * The mouse event that occurred.
998+ */
999+ this . handleMouseEvent = function ( mouseEvent ) {
1000+
1001+ // Only process mouse events that have meta modifier pressed
1002+ if ( ! mouseEvent . modifiers . meta )
1003+ return ;
1004+
1005+ // Check if there's a pending Meta key waiting for context
1006+ var hasPendingMeta = eventLog . length > 0 &&
1007+ eventLog [ 0 ] instanceof KeydownEvent &&
1008+ isMetaKey ( eventLog [ 0 ] . keysym ) ;
1009+
1010+ // Only add mouse event if there's a pending Meta key
1011+ if ( hasPendingMeta ) {
1012+ // Push mouse event onto the event log to provide context for the
1013+ // deferred Meta key. The mouse event will be silently dropped when
1014+ // processed as it's not a KeyEvent type.
1015+ eventLog . push ( mouseEvent ) ;
1016+
1017+ // Process the event log, which will now resolve the deferred Meta
1018+ // key using the mouse event's modifier state as context
1019+ interpret_events ( ) ;
1020+ }
1021+ } ;
1022+
9291023 /**
9301024 * Resynchronizes the remote state of the given modifier with its
9311025 * corresponding local modifier state, as dictated by
@@ -1004,36 +1098,12 @@ Guacamole.Keyboard = function Keyboard(element) {
10041098 */
10051099 var syncModifierStates = function syncModifierStates ( keyEvent ) {
10061100
1007- // Resync state of alt
1008- updateModifierState ( 'alt' , [
1009- 0xFFE9 , // Left alt
1010- 0xFFEA , // Right alt
1011- 0xFE03 // AltGr
1012- ] , keyEvent ) ;
1013-
1014- // Resync state of shift
1015- updateModifierState ( 'shift' , [
1016- 0xFFE1 , // Left shift
1017- 0xFFE2 // Right shift
1018- ] , keyEvent ) ;
1019-
1020- // Resync state of ctrl
1021- updateModifierState ( 'ctrl' , [
1022- 0xFFE3 , // Left ctrl
1023- 0xFFE4 // Right ctrl
1024- ] , keyEvent ) ;
1025-
1026- // Resync state of meta
1027- updateModifierState ( 'meta' , [
1028- 0xFFE7 , // Left meta
1029- 0xFFE8 // Right meta
1030- ] , keyEvent ) ;
1031-
1032- // Resync state of hyper
1033- updateModifierState ( 'hyper' , [
1034- 0xFFEB , // Left super/hyper
1035- 0xFFEC // Right super/hyper
1036- ] , keyEvent ) ;
1101+ // Resync state of all modifiers
1102+ updateModifierState ( 'alt' , modifierKeysymsByType . alt , keyEvent ) ;
1103+ updateModifierState ( 'shift' , modifierKeysymsByType . shift , keyEvent ) ;
1104+ updateModifierState ( 'ctrl' , modifierKeysymsByType . ctrl , keyEvent ) ;
1105+ updateModifierState ( 'meta' , modifierKeysymsByType . meta , keyEvent ) ;
1106+ updateModifierState ( 'hyper' , modifierKeysymsByType . hyper , keyEvent ) ;
10371107
10381108 // Update state
10391109 guac_keyboard . modifiers = keyEvent . modifiers ;
@@ -1152,7 +1222,7 @@ Guacamole.Keyboard = function Keyboard(element) {
11521222 // Defer handling of Meta until it is known to be functioning as a
11531223 // modifier (it may otherwise actually be an alternative method for
11541224 // pressing a single key, such as Meta+Left for Home on ChromeOS)
1155- if ( first . keysym === 0xFFE7 || first . keysym === 0xFFE8 ) {
1225+ if ( isMetaKey ( first . keysym ) ) {
11561226
11571227 // Defer handling until further events exist to provide context
11581228 if ( eventLog . length === 1 )
0 commit comments