/* * Copyright (C) 2013 Glyptodon LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ var Guacamole = Guacamole || {}; /** * Dynamic on-screen keyboard. Given the URL to an XML keyboard layout file, * this object will download and use the XML to construct a clickable on-screen * keyboard with its own key events. * * @constructor * @param {String} url The URL of an XML keyboard layout file. */ Guacamole.OnScreenKeyboard = function(url) { var on_screen_keyboard = this; /** * State of all modifiers. This is the bitwise OR of all active modifier * values. * * @private */ var modifiers = 0; /** * Map of currently-set modifiers to the keysym associated with their * original press. When the modifier is cleared, this keysym must be * released. * * @type Object. */ var modifier_keysyms = {}; var scaledElements = []; var modifier_masks = {}; var next_mask = 1; /** * Adds a class to an element. * * @private * @function * @param {Element} element The element to add a class to. * @param {String} classname The name of the class to add. */ var addClass; /** * Removes a class from an element. * * @private * @function * @param {Element} element The element to remove a class from. * @param {String} classname The name of the class to remove. */ var removeClass; /** * The number of mousemove events to require before re-enabling mouse * event handling after receiving a touch event. */ this.touchMouseThreshold = 3; /** * Counter of mouse events to ignore. This decremented by mousemove, and * while non-zero, mouse events will have no effect. * @private */ var ignore_mouse = 0; // Ignore all pending mouse events when touch events are the apparent source function ignorePendingMouseEvents() { ignore_mouse = on_screen_keyboard.touchMouseThreshold; } // If Node.classList is supported, implement addClass/removeClass using that if (Node.classList) { /** @ignore */ addClass = function(element, classname) { element.classList.add(classname); }; /** @ignore */ removeClass = function(element, classname) { element.classList.remove(classname); }; } // Otherwise, implement own else { /** @ignore */ addClass = function(element, classname) { // Simply add new class element.className += " " + classname; }; /** @ignore */ removeClass = function(element, classname) { // Filter out classes with given name element.className = element.className.replace(/([^ ]+)[ ]*/g, function(match, testClassname, spaces, offset, string) { // If same class, remove if (testClassname == classname) return ""; // Otherwise, allow return match; } ); }; } // Returns a unique power-of-two value for the modifier with the // given name. The same value will be returned for the same modifier. function getModifierMask(name) { var value = modifier_masks[name]; if (!value) { // Get current modifier, advance to next value = next_mask; next_mask <<= 1; // Store value of this modifier modifier_masks[name] = value; } return value; } function ScaledElement(element, width, height, scaleFont) { this.width = width; this.height = height; this.scale = function(pixels) { element.style.width = (width * pixels) + "px"; element.style.height = (height * pixels) + "px"; if (scaleFont) { element.style.lineHeight = (height * pixels) + "px"; element.style.fontSize = pixels + "px"; } } } // For each child of element, call handler defined in next function parseChildren(element, next) { var children = element.childNodes; for (var i=0; i= 0x0000 && charCode <= 0x00FF) real_keysym = charCode; else if (charCode >= 0x0100 && charCode <= 0x10FFFF) real_keysym = 0x01000000 | charCode; } // Create cap var cap = new Guacamole.OnScreenKeyboard.Cap(content, real_keysym); if (e.getAttribute("modifier")) cap.modifier = e.getAttribute("modifier"); // Create cap element var cap_element = document.createElement("div"); cap_element.className = "guac-keyboard-cap"; cap_element.textContent = content; key_element.appendChild(cap_element); // Append class if specified if (e.getAttribute("class")) cap_element.className += " " + e.getAttribute("class"); // Get modifier value var modifierValue = 0; if (e.getAttribute("if")) { // Get modifier value for specified comma-delimited // list of required modifiers. var requirements = e.getAttribute("if").split(","); for (var i=0; i