From 2d26d24dda30ba3679c3259e920487fdf28865bf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 18 Dec 2017 10:58:50 -0800 Subject: [PATCH] GUACAMOLE-352: Allow additional elements to be added to a single Guacamole.Keyboard. --- .../src/main/webapp/modules/Keyboard.js | 302 +++++++++++------- 1 file changed, 195 insertions(+), 107 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/Keyboard.js b/guacamole-common-js/src/main/webapp/modules/Keyboard.js index 3cc28f84d..c25a1fd45 100644 --- a/guacamole-common-js/src/main/webapp/modules/Keyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/Keyboard.js @@ -25,9 +25,12 @@ var Guacamole = Guacamole || {}; * which represent keys as their corresponding X11 keysym. * * @constructor - * @param {Element} element The Element to use to provide keyboard events. + * @param {Element} [element] + * The Element to use to provide keyboard events. If omitted, at least one + * Element must be manually provided through the listenTo() function for + * the Guacamole.Keyboard instance to have any effect. */ -Guacamole.Keyboard = function(element) { +Guacamole.Keyboard = function Keyboard(element) { /** * Reference to this Guacamole.Keyboard. @@ -35,6 +38,25 @@ Guacamole.Keyboard = function(element) { */ var guac_keyboard = this; + /** + * An integer value which uniquely identifies this Guacamole.Keyboard + * instance with respect to other Guacamole.Keyboard instances. + * + * @private + * @type {Number} + */ + var guacKeyboardID = Guacamole.Keyboard._nextID++; + + /** + * The name of the property which is added to event objects via markEvent() + * to note that they have already been handled by this Guacamole.Keyboard. + * + * @private + * @constant + * @type {String} + */ + var EVENT_MARKER = '_GUAC_KEYBOARD_HANDLED_BY_' + guacKeyboardID; + /** * Fired whenever the user presses a key with the element associated * with this Guacamole.Keyboard in focus. @@ -1134,130 +1156,196 @@ Guacamole.Keyboard = function(element) { }; - // When key pressed - element.addEventListener("keydown", function(e) { - - // Only intercept if handler set - if (!guac_keyboard.onkeydown) return; - - var keyCode; - if (window.event) keyCode = window.event.keyCode; - else if (e.which) keyCode = e.which; - - // Fix modifier states - syncModifierStates(e); - - // Ignore (but do not prevent) the "composition" keycode sent by some - // browsers when an IME is in use (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html) - if (keyCode === 229) - return; - - // Log event - var keydownEvent = new KeydownEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e)); - eventLog.push(keydownEvent); - - // Interpret as many events as possible, prevent default if indicated - if (interpret_events()) - e.preventDefault(); - - }, true); - - // When key pressed - element.addEventListener("keypress", function(e) { - - // Only intercept if handler set - if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; - - var charCode; - if (window.event) charCode = window.event.keyCode; - else if (e.which) charCode = e.which; - - // Fix modifier states - syncModifierStates(e); - - // Log event - var keypressEvent = new KeypressEvent(charCode); - eventLog.push(keypressEvent); - - // Interpret as many events as possible, prevent default if indicated - if (interpret_events()) - e.preventDefault(); - - }, true); - - // When key released - element.addEventListener("keyup", function(e) { - - // Only intercept if handler set - if (!guac_keyboard.onkeyup) return; - - e.preventDefault(); - - var keyCode; - if (window.event) keyCode = window.event.keyCode; - else if (e.which) keyCode = e.which; - - // Fix modifier states - syncModifierStates(e); - - // Log event, call for interpretation - var keyupEvent = new KeyupEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e)); - eventLog.push(keyupEvent); - interpret_events(); - - }, true); - /** - * Handles the given "input" event, typing the data within the input text. - * If the event is complete (text is provided), handling of "compositionend" - * events is suspended, as such events may conflict with input events. + * Attempts to mark the given Event as having been handled by this + * Guacamole.Keyboard. If the Event has already been marked as handled, + * false is returned. * - * @private - * @param {InputEvent} e - * The "input" event to handle. + * @param {Event} e + * The Event to mark. + * + * @returns {Boolean} + * true if the given Event was successfully marked, false if the given + * Event was already marked. */ - var handleInput = function handleInput(e) { + var markEvent = function markEvent(e) { - // Only intercept if handler set - if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; + // Fail if event is already marked + if (e[EVENT_MARKER]) + return false; - // Type all content written - if (e.data && !e.isComposing) { - element.removeEventListener("compositionend", handleComposition, false); - guac_keyboard.type(e.data); - } + // Mark event otherwise + e[EVENT_MARKER] = true; + return true; }; /** - * Handles the given "compositionend" event, typing the data within the - * composed text. If the event is complete (composed text is provided), - * handling of "input" events is suspended, as such events may conflict - * with composition events. + * Attaches event listeners to the given Element, automatically translating + * received key, input, and composition events into simple keydown/keyup + * events signalled through this Guacamole.Keyboard's onkeydown and + * onkeyup handlers. * - * @private - * @param {CompositionEvent} e - * The "compositionend" event to handle. + * @param {Element} element + * The Element to attach event listeners to for the sake of handling + * key or input events. */ - var handleComposition = function handleComposition(e) { + this.listenTo = function listenTo(element) { - // Only intercept if handler set - if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; + // When key pressed + element.addEventListener("keydown", function(e) { - // Type all content written - if (e.data) { - element.removeEventListener("input", handleInput, false); - guac_keyboard.type(e.data); - } + // Only intercept if handler set + if (!guac_keyboard.onkeydown) return; + + // Ignore events which have already been handled + if (!markEvent(e)) return; + + var keyCode; + if (window.event) keyCode = window.event.keyCode; + else if (e.which) keyCode = e.which; + + // Fix modifier states + syncModifierStates(e); + + // Ignore (but do not prevent) the "composition" keycode sent by some + // browsers when an IME is in use (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html) + if (keyCode === 229) + return; + + // Log event + var keydownEvent = new KeydownEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e)); + eventLog.push(keydownEvent); + + // Interpret as many events as possible, prevent default if indicated + if (interpret_events()) + e.preventDefault(); + + }, true); + + // When key pressed + element.addEventListener("keypress", function(e) { + + // Only intercept if handler set + if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; + + // Ignore events which have already been handled + if (!markEvent(e)) return; + + var charCode; + if (window.event) charCode = window.event.keyCode; + else if (e.which) charCode = e.which; + + // Fix modifier states + syncModifierStates(e); + + // Log event + var keypressEvent = new KeypressEvent(charCode); + eventLog.push(keypressEvent); + + // Interpret as many events as possible, prevent default if indicated + if (interpret_events()) + e.preventDefault(); + + }, true); + + // When key released + element.addEventListener("keyup", function(e) { + + // Only intercept if handler set + if (!guac_keyboard.onkeyup) return; + + // Ignore events which have already been handled + if (!markEvent(e)) return; + + e.preventDefault(); + + var keyCode; + if (window.event) keyCode = window.event.keyCode; + else if (e.which) keyCode = e.which; + + // Fix modifier states + syncModifierStates(e); + + // Log event, call for interpretation + var keyupEvent = new KeyupEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e)); + eventLog.push(keyupEvent); + interpret_events(); + + }, true); + + /** + * Handles the given "input" event, typing the data within the input text. + * If the event is complete (text is provided), handling of "compositionend" + * events is suspended, as such events may conflict with input events. + * + * @private + * @param {InputEvent} e + * The "input" event to handle. + */ + var handleInput = function handleInput(e) { + + // Only intercept if handler set + if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; + + // Ignore events which have already been handled + if (!markEvent(e)) return; + + // Type all content written + if (e.data && !e.isComposing) { + element.removeEventListener("compositionend", handleComposition, true); + guac_keyboard.type(e.data); + } + + }; + + /** + * Handles the given "compositionend" event, typing the data within the + * composed text. If the event is complete (composed text is provided), + * handling of "input" events is suspended, as such events may conflict + * with composition events. + * + * @private + * @param {CompositionEvent} e + * The "compositionend" event to handle. + */ + var handleComposition = function handleComposition(e) { + + // Only intercept if handler set + if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; + + // Ignore events which have already been handled + if (!markEvent(e)) return; + + // Type all content written + if (e.data) { + element.removeEventListener("input", handleInput, true); + guac_keyboard.type(e.data); + } + + }; + + // Automatically type text entered into the wrapped field + element.addEventListener("input", handleInput, true); + element.addEventListener("compositionend", handleComposition, true); }; - // Automatically type text entered into the wrapped element - element.addEventListener("input", handleInput, false); - element.addEventListener("compositionend", handleComposition, false); + // Listen to given element, if any + if (element) + guac_keyboard.listenTo(element); }; +/** + * The unique numerical identifier to assign to the next Guacamole.Keyboard + * instance. + * + * @private + * @type {Number} + */ +Guacamole.Keyboard._nextID = 0; + /** * The state of all supported keyboard modifiers. * @constructor