From 16072bd4d83ff02bd98b95fd6d476ad6555bf69d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 12:27:38 -0700 Subject: [PATCH 01/29] GUAC-1170: Add JSON format for layouts. --- .../src/main/webapp/layouts/en-us-qwerty.json | 361 ++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 guacamole/src/main/webapp/layouts/en-us-qwerty.json diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json new file mode 100644 index 000000000..0dcd96bb6 --- /dev/null +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -0,0 +1,361 @@ +{ + + "language" : "en_US", + "type" : "qwerty", + "width" : 22, + + "keys" : { + + "Back" : 65288, + "Tab" : 65289, + "Enter" : 65293, + "Esc" : 65307, + "Home" : 65360, + "Left" : 65361, + "Up" : 65362, + "Right" : 65363, + "Down" : 65364, + "PgUp" : 65365, + "PgDn" : 65366, + "End" : 65367, + "Ins" : 65379, + "F1" : 65470, + "F2" : 65471, + "F3" : 65472, + "F4" : 65473, + "F5" : 65474, + "F6" : 65475, + "F7" : 65476, + "F8" : 65477, + "F9" : 65478, + "F10" : 65479, + "F11" : 65480, + "F12" : 65481, + "Del" : 65535, + + "Space" : " ", + + "Menu" : [{ + "title" : "Menu", + "modifier" : "super", + "keysym" : 65383 + }], + "LeftShift" : [{ + "title" : "Shift", + "modifier" : "shift", + "keysym" : 65505 + }], + "RightShift" : [{ + "title" : "Shift", + "modifier" : "shift", + "keysym" : 65506 + }], + "LeftCtrl" : [{ + "title" : "Ctrl", + "modifier" : "control", + "keysym" : 65507 + }], + "RightCtrl" : [{ + "title" : "Ctrl", + "modifier" : "control", + "keysym" : 65508 + }], + "Caps" : [{ + "title" : "Caps", + "modifier" : "caps", + "keysym" : 65509 + }], + "LeftAlt" : [{ + "title" : "Alt", + "modifier" : "alt", + "keysym" : 65513 + }], + "RightAlt" : [{ + "title" : "Alt", + "modifier" : "alt", + "keysym" : 65514 + }], + "Super" : [{ + "title" : "Super", + "modifier" : "super", + "keysym" : 65515 + }], + + "`" : [ + { "title" : "`", "requires" : [ ] }, + { "title" : "~", "requires" : [ "shift" ] } + ], + "1" : [ + { "title" : "1", "requires" : [ ] }, + { "title" : "!", "requires" : [ "shift" ] } + ], + "2" : [ + { "title" : "2", "requires" : [ ] }, + { "title" : "@", "requires" : [ "shift" ] } + ], + "3" : [ + { "title" : "3", "requires" : [ ] }, + { "title" : "#", "requires" : [ "shift" ] } + ], + "4" : [ + { "title" : "4", "requires" : [ ] }, + { "title" : "$", "requires" : [ "shift" ] } + ], + "5" : [ + { "title" : "5", "requires" : [ ] }, + { "title" : "%", "requires" : [ "shift" ] } + ], + "6" : [ + { "title" : "6", "requires" : [ ] }, + { "title" : "^", "requires" : [ "shift" ] } + ], + "7" : [ + { "title" : "7", "requires" : [ ] }, + { "title" : "&", "requires" : [ "shift" ] } + ], + "8" : [ + { "title" : "8", "requires" : [ ] }, + { "title" : "*", "requires" : [ "shift" ] } + ], + "9" : [ + { "title" : "9", "requires" : [ ] }, + { "title" : "(", "requires" : [ "shift" ] } + ], + "0" : [ + { "title" : "0", "requires" : [ ] }, + { "title" : ")", "requires" : [ "shift" ] } + ], + "-" : [ + { "title" : "-", "requires" : [ ] }, + { "title" : "_", "requires" : [ "shift" ] } + ], + "=" : [ + { "title" : "=", "requires" : [ ] }, + { "title" : "+", "requires" : [ "shift" ] } + ], + "," : [ + { "title" : ",", "requires" : [ ] }, + { "title" : "<", "requires" : [ "shift" ] } + ], + "." : [ + { "title" : ".", "requires" : [ ] }, + { "title" : ">", "requires" : [ "shift" ] } + ], + "/" : [ + { "title" : "/", "requires" : [ ] }, + { "title" : "?", "requires" : [ "shift" ] } + ], + "[" : [ + { "title" : "[", "requires" : [ ] }, + { "title" : "{", "requires" : [ "shift" ] } + ], + "]" : [ + { "title" : "]", "requires" : [ ] }, + { "title" : "}", "requires" : [ "shift" ] } + ], + "\\" : [ + { "title" : "\\", "requires" : [ ] }, + { "title" : "|", "requires" : [ "shift" ] } + ], + ";" : [ + { "title" : ";", "requires" : [ ] }, + { "title" : ":", "requires" : [ "shift" ] } + ], + "'" : [ + { "title" : "'", "requires" : [ ] }, + { "title" : "\"", "requires" : [ "shift" ] } + ], + + "q" : [ + { "title" : "q", "requires" : [ ] }, + { "title" : "Q", "requires" : [ "caps" ] }, + { "title" : "Q", "requires" : [ "shift" ] }, + { "title" : "q", "requires" : [ "caps", "shift" ] } + ], + "w" : [ + { "title" : "w", "requires" : [ ] }, + { "title" : "W", "requires" : [ "caps" ] }, + { "title" : "W", "requires" : [ "shift" ] }, + { "title" : "w", "requires" : [ "caps", "shift" ] } + ], + "e" : [ + { "title" : "e", "requires" : [ ] }, + { "title" : "E", "requires" : [ "caps" ] }, + { "title" : "E", "requires" : [ "shift" ] }, + { "title" : "e", "requires" : [ "caps", "shift" ] } + ], + "r" : [ + { "title" : "r", "requires" : [ ] }, + { "title" : "R", "requires" : [ "caps" ] }, + { "title" : "R", "requires" : [ "shift" ] }, + { "title" : "r", "requires" : [ "caps", "shift" ] } + ], + "t" : [ + { "title" : "t", "requires" : [ ] }, + { "title" : "T", "requires" : [ "caps" ] }, + { "title" : "T", "requires" : [ "shift" ] }, + { "title" : "t", "requires" : [ "caps", "shift" ] } + ], + "y" : [ + { "title" : "y", "requires" : [ ] }, + { "title" : "Y", "requires" : [ "caps" ] }, + { "title" : "Y", "requires" : [ "shift" ] }, + { "title" : "y", "requires" : [ "caps", "shift" ] } + ], + "u" : [ + { "title" : "u", "requires" : [ ] }, + { "title" : "U", "requires" : [ "caps" ] }, + { "title" : "U", "requires" : [ "shift" ] }, + { "title" : "u", "requires" : [ "caps", "shift" ] } + ], + "i" : [ + { "title" : "i", "requires" : [ ] }, + { "title" : "I", "requires" : [ "caps" ] }, + { "title" : "I", "requires" : [ "shift" ] }, + { "title" : "i", "requires" : [ "caps", "shift" ] } + ], + "o" : [ + { "title" : "o", "requires" : [ ] }, + { "title" : "O", "requires" : [ "caps" ] }, + { "title" : "O", "requires" : [ "shift" ] }, + { "title" : "o", "requires" : [ "caps", "shift" ] } + ], + "p" : [ + { "title" : "p", "requires" : [ ] }, + { "title" : "P", "requires" : [ "caps" ] }, + { "title" : "P", "requires" : [ "shift" ] }, + { "title" : "p", "requires" : [ "caps", "shift" ] } + ], + "a" : [ + { "title" : "a", "requires" : [ ] }, + { "title" : "A", "requires" : [ "caps" ] }, + { "title" : "A", "requires" : [ "shift" ] }, + { "title" : "a", "requires" : [ "caps", "shift" ] } + ], + "s" : [ + { "title" : "s", "requires" : [ ] }, + { "title" : "S", "requires" : [ "caps" ] }, + { "title" : "S", "requires" : [ "shift" ] }, + { "title" : "s", "requires" : [ "caps", "shift" ] } + ], + "d" : [ + { "title" : "d", "requires" : [ ] }, + { "title" : "D", "requires" : [ "caps" ] }, + { "title" : "D", "requires" : [ "shift" ] }, + { "title" : "d", "requires" : [ "caps", "shift" ] } + ], + "f" : [ + { "title" : "f", "requires" : [ ] }, + { "title" : "F", "requires" : [ "caps" ] }, + { "title" : "F", "requires" : [ "shift" ] }, + { "title" : "f", "requires" : [ "caps", "shift" ] } + ], + "g" : [ + { "title" : "g", "requires" : [ ] }, + { "title" : "G", "requires" : [ "caps" ] }, + { "title" : "G", "requires" : [ "shift" ] }, + { "title" : "g", "requires" : [ "caps", "shift" ] } + ], + "h" : [ + { "title" : "h", "requires" : [ ] }, + { "title" : "H", "requires" : [ "caps" ] }, + { "title" : "H", "requires" : [ "shift" ] }, + { "title" : "h", "requires" : [ "caps", "shift" ] } + ], + "j" : [ + { "title" : "j", "requires" : [ ] }, + { "title" : "J", "requires" : [ "caps" ] }, + { "title" : "J", "requires" : [ "shift" ] }, + { "title" : "j", "requires" : [ "caps", "shift" ] } + ], + "k" : [ + { "title" : "k", "requires" : [ ] }, + { "title" : "K", "requires" : [ "caps" ] }, + { "title" : "K", "requires" : [ "shift" ] }, + { "title" : "k", "requires" : [ "caps", "shift" ] } + ], + "l" : [ + { "title" : "l", "requires" : [ ] }, + { "title" : "L", "requires" : [ "caps" ] }, + { "title" : "L", "requires" : [ "shift" ] }, + { "title" : "l", "requires" : [ "caps", "shift" ] } + ], + "z" : [ + { "title" : "z", "requires" : [ ] }, + { "title" : "Z", "requires" : [ "caps" ] }, + { "title" : "Z", "requires" : [ "shift" ] }, + { "title" : "z", "requires" : [ "caps", "shift" ] } + ], + "x" : [ + { "title" : "x", "requires" : [ ] }, + { "title" : "X", "requires" : [ "caps" ] }, + { "title" : "X", "requires" : [ "shift" ] }, + { "title" : "x", "requires" : [ "caps", "shift" ] } + ], + "c" : [ + { "title" : "c", "requires" : [ ] }, + { "title" : "C", "requires" : [ "caps" ] }, + { "title" : "C", "requires" : [ "shift" ] }, + { "title" : "c", "requires" : [ "caps", "shift" ] } + ], + "v" : [ + { "title" : "v", "requires" : [ ] }, + { "title" : "V", "requires" : [ "caps" ] }, + { "title" : "V", "requires" : [ "shift" ] }, + { "title" : "v", "requires" : [ "caps", "shift" ] } + ], + "b" : [ + { "title" : "b", "requires" : [ ] }, + { "title" : "B", "requires" : [ "caps" ] }, + { "title" : "B", "requires" : [ "shift" ] }, + { "title" : "b", "requires" : [ "caps", "shift" ] } + ], + "n" : [ + { "title" : "n", "requires" : [ ] }, + { "title" : "N", "requires" : [ "caps" ] }, + { "title" : "N", "requires" : [ "shift" ] }, + { "title" : "n", "requires" : [ "caps", "shift" ] } + ], + "m" : [ + { "title" : "m", "requires" : [ ] }, + { "title" : "M", "requires" : [ "caps" ] }, + { "title" : "M", "requires" : [ "shift" ] }, + { "title" : "m", "requires" : [ "caps", "shift" ] } + ] + + }, + + "layout" : { + + "main" : { + "function-keys" : { + "esc" : [ "Esc" ], + "f1-f4" : [ "F1", "F2", "F3", "F4" ], + "f5-f8" : [ "F5", "F6", "F7", "F8" ], + "f9-f12" : [ "F9", "F10", "F11", "F12" ] + }, + + "alpha" : [ + [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], + [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], + [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], + [ "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift" ], + [ "Ctrl", "Super", "Alt", "Space", "Alt", "Menu", "Ctrl" ] + ] + }, + + "movement" : { + "large" : [ + [ "Ins", "Home", "PgUp" ], + [ "Del", "End", "PgDn" ] + ], + + "fine" : [ + [ "Up" ], + [ "Left", "Down", "Right" ] + ] + } + + } + +} From 29a0bc6387065852fe9ea02609e639fefeb8b069 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 14:34:04 -0700 Subject: [PATCH 02/29] GUAC-1170: Migrate to JSON representation, at least in spirit. --- .../main/webapp/modules/OnScreenKeyboard.js | 757 +++++++----------- 1 file changed, 294 insertions(+), 463 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 71a5a0897..0229b6c6d 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Glyptodon LLC + * Copyright (C) 2015 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 @@ -23,22 +23,29 @@ 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. - * + * Dynamic on-screen keyboard. Given the layout object for an on-screen + * keyboard, this object will construct a clickable on-screen keyboard with its + * own key events. + * * @constructor - * @param {String} url The URL of an XML keyboard layout file. + * @param {Guacamole.OnScreenKeyboard.Layout} layout + * The layout of the on-screen keyboard to display. */ -Guacamole.OnScreenKeyboard = function(url) { +Guacamole.OnScreenKeyboard = function(layout) { - var on_screen_keyboard = this; + /** + * Reference to this Guacamole.OnScreenKeyboard. + * + * @type Guacamole.OnScreenKeyboard + */ + var osk = this; /** * State of all modifiers. This is the bitwise OR of all active modifier * values. * * @private + * @type Number */ var modifiers = 0; @@ -47,86 +54,69 @@ Guacamole.OnScreenKeyboard = function(url) { * original press. When the modifier is cleared, this keysym must be * released. * + * @private * @type Object. */ - var modifier_keysyms = {}; + var modifierKeysyms = {}; + /** + * All scalable elements which are part of the on-screen keyboard. Each + * scalable element is carefully controlled to ensure the interface layout + * and sizing remains constant, even on browsers that would otherwise + * experience rounding error due to unit conversions. + * + * @private + * @type ScaledElement[] + */ var scaledElements = []; - - var modifier_masks = {}; - var next_mask = 1; /** - * Adds a class to an element. + * Adds a CSS 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. + * @param {Element} element + * The element to add a class to. + * + * @param {String} classname + * The name of the class to add. */ - var addClass; + var addClass = function addClass(element, classname) { - /** - * 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) { + // If classList supported, use that + if (element.classList) 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 + // Otherwise, simply append the class + else element.className += " " + classname; - }; - - /** @ignore */ - removeClass = function(element, classname) { + }; - // Filter out classes with given name + /** + * Removes a CSS 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 = function removeClass(element, classname) { + + // If classList supported, use that + if (element.classList) + element.classList.remove(classname); + + // Otherwise, manually filter out classes with given name + else { element.className = element.className.replace(/([^ ]+)[ ]*/g, - function(match, testClassname, spaces, offset, string) { + function removeMatchingClasses(match, testClassname) { // If same class, remove - if (testClassname == classname) + if (testClassname === classname) return ""; // Otherwise, allow @@ -134,393 +124,170 @@ Guacamole.OnScreenKeyboard = function(url) { } ); + } - }; - - } + }; - // 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) { + /** + * Counter of mouse events to ignore. This decremented by mousemove, and + * while non-zero, mouse events will have no effect. + * + * @private + * @type Number + */ + var ignoreMouse = 0; + + /** + * Ignores all pending mouse events when touch events are the apparent + * source. Mouse events are ignored until at least touchMouseThreshold + * mouse events occur without corresponding touch events. + * + * @private + */ + var ignorePendingMouseEvents = function ignorePendingMouseEvents() { + ignoreMouse = osk.touchMouseThreshold; + }; + + /** + * Map of modifier names to their corresponding, unique, power-of-two value + * bitmasks. + * + * @private + * @type Object. + */ + var modifierMasks = {}; + + /** + * The bitmask to assign to the next modifier, if a new modifier is used + * which does not yet have a corresponding bitmask. + * + * @private + * @type Number + */ + var nextMask = 1; + + /** + * Returns a unique power-of-two value for the modifier with the given + * name. The same value will be returned consistently for the same + * modifier. + * + * @private + * @param {String} name + * The name of the modifier to return a bitmask for. + * + * @return {Number} + * The unique power-of-two value associated with the modifier having + * the given name, which may be newly allocated. + */ + var getModifierMask = function getModifierMask(name) { - var value = modifier_masks[name]; + var value = modifierMasks[name]; if (!value) { // Get current modifier, advance to next - value = next_mask; - next_mask <<= 1; + value = nextMask; + nextMask <<= 1; // Store value of this modifier - modifier_masks[name] = value; + modifierMasks[name] = value; } return value; - } + }; - function ScaledElement(element, width, height, scaleFont) { + /** + * An element whose dimensions are maintained according to an arbitrary + * scale. The conversion factor for these arbitrary units to pixels is + * provided later via a call to scale(). + * + * @private + * @constructor + * @param {Element} element + * The element whose scale should be maintained. + * + * @param {Number} width + * The width of the element, in arbitrary units, relative to other + * ScaledElements. + * + * @param {Number} height + * The height of the element, in arbitrary units, relative to other + * ScaledElements. + * + * @param {Boolean} [scaleFont=false] + * Whether the line height and font size should be scaled as well. + */ + var ScaledElement = function ScaledElement(element, width, height, scaleFont) { - this.width = width; - this.height = height; + /** + * The width of this ScaledElement, in arbitrary units, relative to + * other ScaledElements. + * + * @type Number + */ + this.width = width; + /** + * The height of this ScaledElement, in arbitrary units, relative to + * other ScaledElements. + * + * @type Number + */ + this.height = height; + + /** + * Resizes the associated element, updating its dimensions according to + * the given pixels per unit. + * + * @param {Number} pixels + * The number of pixels to assign per arbitrary unit. + */ this.scale = function(pixels) { - element.style.width = (width * pixels) + "px"; - element.style.height = (height * pixels) + "px"; + // Scale element width/height + element.style.width = (width * pixels) + "px"; + element.style.height = (height * pixels) + "px"; + + // Scale font, if requested 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 */ - this.caps = {}; + this.keys = template.keys; /** - * Bit mask with all modifiers that affect this key set. + * Arbitrarily nested, arbitrarily grouped key names. The contents of the + * layout will be traversed to produce an identically-nested grouping of + * keys in the DOM tree. All strings will be transformed into their + * corresponding sets of keys, while all objects and arrays will be + * transformed into named groups and anonymous groups respectively. + * + * @type Object */ - this.modifierMask = 0; + this.layout = template.layout; /** - * Given the bitwise OR of all active modifiers, returns the key cap - * which applies. + * The width of the entire keyboard, in arbitrary units. The width of each + * key is relative to this width, as both width values are assumed to be in + * the same units. The conversion factor between these units and pixels is + * derived later via a call to resize() on the Guacamole.OnScreenKeyboard. + * + * @type Number */ - this.getCap = function(modifier) { - return key.caps[modifier & key.modifierMask]; - }; + this.width = template.width; }; /** - * Basic representation of a cap of a key. The cap is the visible part of a key - * and determines the active behavior of a key when pressed. The state of all - * modifiers on the keyboard determines the active cap for all keys, thus - * each cap is associated with a set of modifiers. - * + * Represents a single key, or a single possible behavior of a key. Each key + * on the on-screen keyboard must have at least one associated + * Guacamole.OnScreenKeyboard.Key, whether that key is explicitly defined or + * implied, and may have multiple Guacamole.OnScreenKeyboard.Key if behavior + * depends on modifier states. + * * @constructor - * @param {String} text The text to be displayed within this cap. - * @param {Number} keysym The keysym this cap sends when its associated key is - * pressed or released. - * @param {String} modifier The modifier represented by this cap. + * @param {Guacamole.OnScreenKeyboard.Key|Object} template + * The object whose identically-named properties will be used to initialize + * the properties of this key. */ -Guacamole.OnScreenKeyboard.Cap = function(text, keysym, modifier) { - - /** - * Modifier represented by this keycap - */ - this.modifier = null; - - /** - * The text to be displayed within this keycap - */ - this.text = text; +Guacamole.OnScreenKeyboard.Key = function(template) { /** - * The keysym this cap sends when its associated key is pressed/released + * The unique name identifying this key within the keyboard layout. + * + * @type String */ - this.keysym = keysym; + this.name = template.name; + + /** + * The human-readable title that will be displayed to the user within the + * key. If not provided, this will be derived from the key name. + * + * @type String + */ + this.title = template.title || template.name; + + /** + * The keysym to be pressed/released when this key is pressed/released. If + * not provided, this will be derived from the title if the title is a + * single character. + * + * @type Number + */ + this.keysym = template.keysym; + + /** + * The name of the modifier set when the key is pressed and cleared when + * this key is released, if any. The names of modifiers are distinct from + * the names of keys; both the "RightShift" and "LeftShift" keys may set + * the "shift" modifier, for example. By default, the key will affect no + * modifiers. + * + * @type String + */ + this.modifier = template.modifier; + + /** + * An array containing the names of each modifier required for this key to + * have an effect. For example, a lowercase letter may require nothing, + * while an uppercase letter would require "shift", assuming the Shift key + * is named "shift" within the layout. By default, the key will require + * no modifiers. + * + * @type String[] + */ + this.requires = template.requires || []; + + /** + * The width of this key, in arbitrary units, relative to other keys in the + * same layout. The true pixel size of this key will be determined by the + * overall size of the keyboard. By default, this will be 1. + * + * @type Number + */ + this.width = template.width || 1; - // Set modifier if provided - if (modifier) this.modifier = modifier; - }; From ee83060bb5ab401f52a1f6a27e266d9e65056dc8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 14:56:00 -0700 Subject: [PATCH 03/29] GUAC-1170: Load keyboard layout within guacOsk directive. Update translation to point to new JSON layout. Remove old XML layouts. --- .../main/webapp/app/osk/directives/guacOsk.js | 57 +- .../webapp/layouts/en-us-qwerty-mobile.xml | 316 ----------- .../src/main/webapp/layouts/en-us-qwerty.xml | 500 ------------------ .../src/main/webapp/translations/en_US.json | 2 +- 4 files changed, 40 insertions(+), 835 deletions(-) delete mode 100644 guacamole/src/main/webapp/layouts/en-us-qwerty-mobile.xml delete mode 100644 guacamole/src/main/webapp/layouts/en-us-qwerty.xml diff --git a/guacamole/src/main/webapp/app/osk/directives/guacOsk.js b/guacamole/src/main/webapp/app/osk/directives/guacOsk.js index decb06f8d..bca9bc9b4 100644 --- a/guacamole/src/main/webapp/app/osk/directives/guacOsk.js +++ b/guacamole/src/main/webapp/app/osk/directives/guacOsk.js @@ -40,8 +40,13 @@ angular.module('osk').directive('guacOsk', [function guacOsk() { }, templateUrl: 'app/osk/templates/guacOsk.html', - controller: ['$scope', '$rootScope', '$window', '$element', - function guacOsk($scope, $rootScope, $window, $element) { + controller: ['$scope', '$injector', '$element', + function guacOsk($scope, $injector, $element) { + + // Required services + var $http = $injector.get('$http'); + var $rootScope = $injector.get('$rootScope'); + var cacheService = $injector.get('cacheService'); /** * The current on-screen keyboard, if any. @@ -67,7 +72,7 @@ angular.module('osk').directive('guacOsk', [function guacOsk() { }; // Set layout whenever URL changes - $scope.$watch("layout", function setLayout(layout) { + $scope.$watch("layout", function setLayout(url) { // Remove current keyboard if (keyboard) { @@ -76,24 +81,40 @@ angular.module('osk').directive('guacOsk', [function guacOsk() { } // Load new keyboard - if (layout) { + if (url) { - // Add OSK element - keyboard = new Guacamole.OnScreenKeyboard(layout); - main.appendChild(keyboard.getElement()); + // Retrieve layout JSON + $http({ + cache : cacheService.languages, + method : 'GET', + url : url + }) - // Init size - keyboard.resize(main.offsetWidth); + // Build OSK with retrieved layout + .success(function layoutRetrieved(layout) { - // Broadcast keydown for each key pressed - keyboard.onkeydown = function(keysym) { - $rootScope.$broadcast('guacSyntheticKeydown', keysym); - }; - - // Broadcast keydown for each key released - keyboard.onkeyup = function(keysym) { - $rootScope.$broadcast('guacSyntheticKeyup', keysym); - }; + // Abort if the layout changed while we were waiting for a response + if ($scope.layout !== url) + return; + + // Add OSK element + keyboard = new Guacamole.OnScreenKeyboard(layout); + main.appendChild(keyboard.getElement()); + + // Init size + keyboard.resize(main.offsetWidth); + + // Broadcast keydown for each key pressed + keyboard.onkeydown = function(keysym) { + $rootScope.$broadcast('guacSyntheticKeydown', keysym); + }; + + // Broadcast keydown for each key released + keyboard.onkeyup = function(keysym) { + $rootScope.$broadcast('guacSyntheticKeyup', keysym); + }; + + }); } diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty-mobile.xml b/guacamole/src/main/webapp/layouts/en-us-qwerty-mobile.xml deleted file mode 100644 index a61ec4c2b..000000000 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty-mobile.xml +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - - Tab - - - - q - 1 - Q - q - - - - w - 2 - W - w - - - - e - 3 - E - e - - - - r - 4 - R - r - - - - t - 5 - T - t - - - - y - 6 - Y - y - - - - u - 7 - U - u - - - - i - 8 - I - i - - - - o - 9 - O - o - - - - p - 0 - P - p - - - - [ - { - - - - ] - } - - - - Back - - - - - - - - ?123 - - - - a - # - A - a - - - - s - $ - S - s - - - - d - % - D - d - - - - f - & - F - f - - - - g - * - G - g - - - - h - - - H - h - - - - j - + - J - j - - - - k - ( - K - k - - - - l - ) - L - l - - - - ; - : - - - - ' - " - - - - Enter - - - - - - - - Shift - - - - z - < - Z - z - - - - x - > - X - x - - - - c - = - C - c - - - - v - ' - V - v - - - - b - ; - B - b - - - - n - , - N - n - - - - m - . - M - m - - - - , - ! - ! - ! - - - - . - ? - ? - ? - - - - / - ? - - - - Shift - - - - - - - - Ctrl - - - - Super - - - - Alt - - - - - - - - Alt - - - - Menu - - - - Ctrl - - - diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.xml b/guacamole/src/main/webapp/layouts/en-us-qwerty.xml deleted file mode 100644 index 8cf8bb5e0..000000000 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.xml +++ /dev/null @@ -1,500 +0,0 @@ - - - - - - - - - - Esc - - - - F1 - - - - F2 - - - - F3 - - - - F4 - - - - F5 - - - - F6 - - - - F7 - - - - F8 - - - - F9 - - - - F10 - - - - F11 - - - - F12 - - - - - - - - - ` - ~ - - - - 1 - ! - - - - 2 - @ - - - - 3 - # - - - - 4 - $ - - - - 5 - % - - - - 6 - ^ - - - - 7 - & - - - - 8 - * - - - - 9 - ( - - - - 0 - ) - - - - - - _ - - - - = - + - - - - Back - - - - - - - - Tab - - - - q - Q - Q - q - - - - w - W - W - w - - - - e - E - E - e - - - - r - R - R - r - - - - t - T - T - t - - - - y - Y - Y - y - - - - u - U - U - u - - - - i - I - I - i - - - - o - O - O - o - - - - p - P - P - p - - - - [ - { - - - - ] - } - - - - \ - | - - - - - - - - Caps - - - - a - A - A - a - - - - s - S - S - s - - - - d - D - D - d - - - - f - F - F - f - - - - g - G - G - g - - - - h - H - H - h - - - - j - J - J - j - - - - k - K - K - k - - - - l - L - L - l - - - - ; - : - - - - ' - " - - - - Enter - - - - - - - - Shift - - - - z - Z - Z - z - - - - x - X - X - x - - - - c - C - C - c - - - - v - V - V - v - - - - b - B - B - b - - - - n - N - N - n - - - - m - M - M - m - - - - , - < - - - - . - > - - - - / - ? - - - - Shift - - - - - - - - Ctrl - - - - Super - - - - Alt - - - - - - - - Alt - - - - Menu - - - - Ctrl - - - - - - - - - - - - Ins - - - - Home - - - - PgUp - - - - - - Del - - - - End - - - - PgDn - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/guacamole/src/main/webapp/translations/en_US.json b/guacamole/src/main/webapp/translations/en_US.json index 2ea7b6f78..79e5a6db6 100644 --- a/guacamole/src/main/webapp/translations/en_US.json +++ b/guacamole/src/main/webapp/translations/en_US.json @@ -117,7 +117,7 @@ "TEXT_RECONNECT_COUNTDOWN" : "Reconnecting in {REMAINING} {REMAINING, plural, one{second} other{seconds}}...", "TEXT_FILE_TRANSFER_PROGRESS" : "{PROGRESS} {UNIT, select, b{B} kb{KB} mb{MB} gb{GB} other{}}", - "URL_OSK_LAYOUT" : "layouts/en-us-qwerty.xml" + "URL_OSK_LAYOUT" : "layouts/en-us-qwerty.json" }, From 181290f1f5ccbc3661d3b0f40a3734862dcd42bc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 16:03:59 -0700 Subject: [PATCH 04/29] GUAC-1170: Fix names of modifier keys in layout. --- .../src/main/webapp/layouts/en-us-qwerty.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 0dcd96bb6..9b76bff01 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -40,22 +40,22 @@ "modifier" : "super", "keysym" : 65383 }], - "LeftShift" : [{ + "LShift" : [{ "title" : "Shift", "modifier" : "shift", "keysym" : 65505 }], - "RightShift" : [{ + "RShift" : [{ "title" : "Shift", "modifier" : "shift", "keysym" : 65506 }], - "LeftCtrl" : [{ + "LCtrl" : [{ "title" : "Ctrl", "modifier" : "control", "keysym" : 65507 }], - "RightCtrl" : [{ + "RCtrl" : [{ "title" : "Ctrl", "modifier" : "control", "keysym" : 65508 @@ -65,12 +65,12 @@ "modifier" : "caps", "keysym" : 65509 }], - "LeftAlt" : [{ + "LAlt" : [{ "title" : "Alt", "modifier" : "alt", "keysym" : 65513 }], - "RightAlt" : [{ + "RAlt" : [{ "title" : "Alt", "modifier" : "alt", "keysym" : 65514 @@ -339,8 +339,8 @@ [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], - [ "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift" ], - [ "Ctrl", "Super", "Alt", "Space", "Alt", "Menu", "Ctrl" ] + [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], + [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] ] }, From 71e219842050091d659e3933a75a9950ae65e66b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 16:33:17 -0700 Subject: [PATCH 05/29] GUAC-1170: Parse layout structure. --- .../main/webapp/modules/OnScreenKeyboard.js | 124 +++++++++++++++++- 1 file changed, 121 insertions(+), 3 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 0229b6c6d..de2598b48 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -263,8 +263,6 @@ Guacamole.OnScreenKeyboard = function(layout) { var keyboard = document.createElement("div"); keyboard.className = "guac-keyboard"; - /* TODO: Actually parse the darn thing */ - // Do not allow selection or mouse movement to propagate/register. keyboard.onselectstart = keyboard.onmousemove = @@ -331,7 +329,6 @@ Guacamole.OnScreenKeyboard = function(layout) { // Get pixel size of a unit var unit = Math.floor(width * 10 / osk.layout.width) / 10; - console.log("OnScreenKeyboard", "resizing unit", unit); // Resize all scaled elements for (var i=0; i Date: Tue, 28 Apr 2015 16:41:08 -0700 Subject: [PATCH 06/29] GUAC-1170: Derive keysym from title. --- .../main/webapp/modules/OnScreenKeyboard.js | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index de2598b48..7d98467e0 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -441,7 +441,7 @@ Guacamole.OnScreenKeyboard = function(layout) { // If key name is only one character, use codepoint for name var keyName = object; if (keyName.length === 1) - keyName = '0x' + keyName.codePointAt(0).toString(16); + keyName = '0x' + keyName.charCodeAt(0).toString(16); // Add key-specific classes addClass(div, 'guac-osk-key'); @@ -558,7 +558,25 @@ Guacamole.OnScreenKeyboard.Key = function(template) { * * @type Number */ - this.keysym = template.keysym; + this.keysym = template.keysym || (function deriveKeysym(title) { + + // Do not derive keysym if title is not exactly one character + if (!title || title.length !== 1) + return null; + + // For characters between U+0000 and U+00FF, the keysym is the codepoint + var charCode = title.charCodeAt(0); + if (charCode >= 0x0000 && charCode <= 0x00FF) + return charCode; + + // For characters between U+0100 and U+10FFFF, the keysym is the codepoint or'd with 0x01000000 + if (charCode >= 0x0100 && charCode <= 0x10FFFF) + return 0x01000000 | charCode; + + // Unable to derive keysym + return null; + + })(this.title); /** * The name of the modifier set when the key is pressed and cleared when From ddf44817c8dbd632dc080380c42002bd1dd9e6c3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 17:05:08 -0700 Subject: [PATCH 07/29] GUAC-1170: Retrieve associated keys for each key in layout. --- .../main/webapp/modules/OnScreenKeyboard.js | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 7d98467e0..fc3711f42 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -338,6 +338,56 @@ Guacamole.OnScreenKeyboard = function(layout) { }; + /** + * Given the name of a key, returns the set of key objects associated with + * that name. A particular key may have many associated key objects due to + * the various effects of modifiers. Regardless of how the key is declared + * in the layout, this function will ALWAYS return an array of key objects. + * + * @private + * @param {String} name + * The name of the key whose set of possible keys should be returned. + * + * @returns {Guacamole.OnScreenKeyboard.Key[]} + * The array of all keys associated with the given name. + */ + var getKeys = function getKeys(name) { + + // Pull associated object, which might be a key object already + var object = osk.layout.keys[name]; + if (!object) + return null; + + // If already an array, just coerce into a true Key[] + if (object instanceof Array) { + var keys = []; + for (var i=0; i < object.length; i++) { + keys.push(new Guacamole.OnScreenKeyboard.Key(object[i], name)); + } + return keys; + } + + // Derive key object from keysym if that's all we have + if (typeof object === 'number') { + return [new Guacamole.OnScreenKeyboard.Key({ + name : name, + keysym : object + })]; + } + + // Derive key object from title if that's all we have + if (typeof object === 'string') { + return [new Guacamole.OnScreenKeyboard.Key({ + name : name, + title : object + })]; + } + + // Otherwise, assume it's already a key object, just not an array + return [new Guacamole.OnScreenKeyboard.Key(object, name)]; + + }; + /** * Given an arbitrary string representing the name of some component of the * on-screen keyboard, returns a string formatted for use as a CSS class @@ -345,6 +395,7 @@ Guacamole.OnScreenKeyboard = function(layout) { * by CamelCase will be replaced by individual hyphens, as will all * contiguous non-alphanumeric characters. * + * @private * @param {String} name * An arbitrary string representing the name of some component of the * on-screen keyboard. @@ -387,6 +438,7 @@ Guacamole.OnScreenKeyboard = function(layout) { * C-style hexadecimal literal for the Unicode codepoint of that character. * For example, the key "A" would become "guac-osk-key-0x41". * + * @private * @param {Element} element * The element to append elements to. * @@ -447,7 +499,9 @@ Guacamole.OnScreenKeyboard = function(layout) { addClass(div, 'guac-osk-key'); addClass(div, 'guac-osk-key-' + getCSSName(keyName)); - // TODO: Add key caps + // Retrieve all associated keys + var keys = getKeys(object); + console.log(object, keys); } @@ -533,15 +587,20 @@ Guacamole.OnScreenKeyboard.Layout = function(template) { * @param {Guacamole.OnScreenKeyboard.Key|Object} template * The object whose identically-named properties will be used to initialize * the properties of this key. + * + * @param {String} [name] + * The name to use instead of any name provided within the template, if + * any. If omitted, the name within the template will be used, assuming the + * template contains a name. */ -Guacamole.OnScreenKeyboard.Key = function(template) { +Guacamole.OnScreenKeyboard.Key = function(template, name) { /** * The unique name identifying this key within the keyboard layout. * * @type String */ - this.name = template.name; + this.name = name || template.name; /** * The human-readable title that will be displayed to the user within the @@ -549,7 +608,7 @@ Guacamole.OnScreenKeyboard.Key = function(template) { * * @type String */ - this.title = template.title || template.name; + this.title = template.title || this.name; /** * The keysym to be pressed/released when this key is pressed/released. If From 75544418691f3457683bccf878cd900273ddb7c3 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 17:38:57 -0700 Subject: [PATCH 08/29] GUAC-1170: Add each associated key to the DOM. Expose keys predictably within OSK object, even if shorthand is used in the layout definition object. --- .../main/webapp/modules/OnScreenKeyboard.js | 78 +++++++++++++++---- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index fc3711f42..7ba565739 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -339,10 +339,10 @@ Guacamole.OnScreenKeyboard = function(layout) { }; /** - * Given the name of a key, returns the set of key objects associated with - * that name. A particular key may have many associated key objects due to - * the various effects of modifiers. Regardless of how the key is declared - * in the layout, this function will ALWAYS return an array of key objects. + * Given a key entry, which may be an array of keys objects, a number + * (keysym), a string (key title), or a single key object, returns an array + * of key objects, deriving any missing properties as needed, and ensuring + * the key name is defined. * * @private * @param {String} name @@ -351,12 +351,7 @@ Guacamole.OnScreenKeyboard = function(layout) { * @returns {Guacamole.OnScreenKeyboard.Key[]} * The array of all keys associated with the given name. */ - var getKeys = function getKeys(name) { - - // Pull associated object, which might be a key object already - var object = osk.layout.keys[name]; - if (!object) - return null; + var asKeyArray = function asKeyArray(object) { // If already an array, just coerce into a true Key[] if (object instanceof Array) { @@ -388,6 +383,43 @@ Guacamole.OnScreenKeyboard = function(layout) { }; + /** + * Converts the rather forgiving key mapping allowed by + * Guacamole.OnScreenKeyboard.Layout into a rigorous mapping of key name + * to key definition, where the key definition is always an array of Key + * objects. + * + * @private + * @param {Object.} keys + * A mapping of key name to key definition, where the key definition is + * the title of the key (a string), the keysym (a number), a single + * Key object, or an array of Key objects. + * + * @returns {Object.} + * A more-predictable mapping of key name to key definition, where the + * key definition is always simply an array of Key objects. + */ + var getKeys = function getKeys(keys) { + + var keyArrays = {}; + + // Coerce all keys into individual key arrays + for (var name in layout.keys) { + keyArrays[name] = asKeyArray(keys[name]); + } + + return keyArrays; + + }; + + /** + * Map of all key names to their corresponding set of keys. Each key name + * may correspond to multiple keys due to the effect of modifiers. + * + * @type Object. + */ + this.keys = getKeys(layout.keys); + /** * Given an arbitrary string representing the name of some component of the * on-screen keyboard, returns a string formatted for use as a CSS class @@ -499,11 +531,27 @@ Guacamole.OnScreenKeyboard = function(layout) { addClass(div, 'guac-osk-key'); addClass(div, 'guac-osk-key-' + getCSSName(keyName)); - // Retrieve all associated keys - var keys = getKeys(object); - console.log(object, keys); + // Add all associated keys to DOM + var keys = osk.keys[object]; + if (keys) { + for (i=0; i < keys.length; i++) { - } + // Get current key + var key = keys[i]; + + // Create element for key + var keyElement = document.createElement('div'); + keyElement.className = 'guac-osk-key-cap'; + keyElement.textContent = key.title; + + // Add key to DOM, maintain scale + div.appendChild(keyElement); + scaledElements.push(new ScaledElement(keyElement, key.width, 1, true)); + + } + } + + } // end if object is key name // Add newly-created group/key element.appendChild(div); @@ -549,7 +597,7 @@ Guacamole.OnScreenKeyboard.Layout = function(template) { * implicitly. In all cases, the name property of the key object will be * taken from the name given in the mapping. * - * @type Object. + * @type Object. */ this.keys = template.keys; From 6f99e3f1ab5a01e08734d08b030c9204dcbe88f0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:15:35 -0700 Subject: [PATCH 09/29] GUAC-1170: Fix coercion of key definition into key array (missing name). Use CSS style names compatible with 0.9.6 and older OSK. --- .../main/webapp/modules/OnScreenKeyboard.js | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 7ba565739..e9b5e18d5 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -339,19 +339,24 @@ Guacamole.OnScreenKeyboard = function(layout) { }; /** - * Given a key entry, which may be an array of keys objects, a number - * (keysym), a string (key title), or a single key object, returns an array - * of key objects, deriving any missing properties as needed, and ensuring - * the key name is defined. + * Given the name of a key and its corresponding definition, which may be + * an array of keys objects, a number (keysym), a string (key title), or a + * single key object, returns an array of key objects, deriving any missing + * properties as needed, and ensuring the key name is defined. * * @private * @param {String} name - * The name of the key whose set of possible keys should be returned. + * The name of the key being coerced into an array of Key objects. * + * @param {Number|String|Guacamole.OnScreenKeyboard.Key|Guacamole.OnScreenKeyboard.Key[]} object + * The object defining the behavior of the key having the given name, + * which may be the title of the key (a string), the keysym (a number), + * a single Key object, or an array of Key objects. + * * @returns {Guacamole.OnScreenKeyboard.Key[]} - * The array of all keys associated with the given name. + * An array of all keys associated with the given name. */ - var asKeyArray = function asKeyArray(object) { + var asKeyArray = function asKeyArray(name, object) { // If already an array, just coerce into a true Key[] if (object instanceof Array) { @@ -405,7 +410,7 @@ Guacamole.OnScreenKeyboard = function(layout) { // Coerce all keys into individual key arrays for (var name in layout.keys) { - keyArrays[name] = asKeyArray(keys[name]); + keyArrays[name] = asKeyArray(name, keys[name]); } return keyArrays; @@ -450,25 +455,26 @@ Guacamole.OnScreenKeyboard = function(layout) { /** * Appends DOM elements to the given element as dictated by the layout * structure object provided. If a name is provided, an additional CSS - * class, prepended with "guac-osk-", will be added to the top-level + * class, prepended with "guac-keyboard-", will be added to the top-level * element. * * If the layout structure object is an array, all elements within that * array will be recursively appended as children of a group, and the - * top-level element will be given the CSS class "guac-osk-group". + * top-level element will be given the CSS class "guac-keyboard-group". * * If the layout structure object is an object, all properties within that * object will be recursively appended as children of a group, and the - * top-level element will be given the CSS class "guac-osk-group". The + * top-level element will be given the CSS class "guac-keyboard-group". The * name of each property will be applied as the name of each child object * for the sake of CSS. Each property will be added in sorted order. * * If the layout structure object is a string, the key having that name - * will be appended. The key will be given the CSS class "guac-osk-key" and - * "guac-osk-key-NAME", where NAME is the name of the key. If the name of - * the key is a single character, this will first be transformed into the - * C-style hexadecimal literal for the Unicode codepoint of that character. - * For example, the key "A" would become "guac-osk-key-0x41". + * will be appended. The key will be given the CSS class + * "guac-keyboard-key" and "guac-keyboard-key-NAME", where NAME is the name + * of the key. If the name of the key is a single character, this will + * first be transformed into the C-style hexadecimal literal for the + * Unicode codepoint of that character. For example, the key "A" would + * become "guac-keyboard-key-0x41". * * @private * @param {Element} element @@ -490,13 +496,13 @@ Guacamole.OnScreenKeyboard = function(layout) { // Add class based on name, if name given if (name) - addClass(div, 'guac-osk-' + getCSSName(name)); + addClass(div, 'guac-keyboard-' + getCSSName(name)); // If an array, append each element if (object instanceof Array) { // Add group class - addClass(div, 'guac-osk-group'); + addClass(div, 'guac-keyboard-group'); // Append all elements of array for (i=0; i < object.length; i++) @@ -508,7 +514,7 @@ Guacamole.OnScreenKeyboard = function(layout) { else if (object instanceof Object) { // Add group class - addClass(div, 'guac-osk-group'); + addClass(div, 'guac-keyboard-group'); // Append all children, sorted by name var names = Object.keys(object).sort(); @@ -527,11 +533,15 @@ Guacamole.OnScreenKeyboard = function(layout) { if (keyName.length === 1) keyName = '0x' + keyName.charCodeAt(0).toString(16); - // Add key-specific classes - addClass(div, 'guac-osk-key'); - addClass(div, 'guac-osk-key-' + getCSSName(keyName)); + // Add key container class + addClass(div, 'guac-keyboard-key-container'); - // Add all associated keys to DOM + // Create key element which will contain all possible caps + var keyElement = document.createElement('div'); + keyElement.className = 'guac-keyboard-key ' + + 'guac-keyboard-key-' + getCSSName(keyName); + + // Add all associated keys as caps within DOM var keys = osk.keys[object]; if (keys) { for (i=0; i < keys.length; i++) { @@ -539,18 +549,28 @@ Guacamole.OnScreenKeyboard = function(layout) { // Get current key var key = keys[i]; - // Create element for key - var keyElement = document.createElement('div'); - keyElement.className = 'guac-osk-key-cap'; - keyElement.textContent = key.title; + // Create cap element for key + var capElement = document.createElement('div'); + capElement.className = 'guac-keyboard-cap'; + capElement.textContent = key.title; - // Add key to DOM, maintain scale - div.appendChild(keyElement); - scaledElements.push(new ScaledElement(keyElement, key.width, 1, true)); + // Add classes for any requirements + for (var j=0; j < key.requires.length; j++) { + var requirement = key.requires[j]; + addClass(capElement, 'guac-keyboard-requires-' + getCSSName(requirement)); + addClass(keyElement, 'guac-keyboard-uses-' + getCSSName(requirement)); + } + + // Add cap to key within DOM + keyElement.appendChild(capElement); } } + // Add key to DOM, maintain scale + div.appendChild(keyElement); + scaledElements.push(new ScaledElement(div, 1 /* TODO: Pull from layout */, 1, true)); + } // end if object is key name // Add newly-created group/key From 2a1eb4dd7030ee6422c732e3173c5aa75de87060 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:36:54 -0700 Subject: [PATCH 10/29] GUAC-1170: Use separately-declared key widths. --- .../main/webapp/modules/OnScreenKeyboard.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index e9b5e18d5..96cd5f15f 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -307,7 +307,7 @@ Guacamole.OnScreenKeyboard = function(layout) { * * @type Guacamole.OnScreenKeyboard.Layout */ - this.layout = layout; + this.layout = new Guacamole.OnScreenKeyboard.Layout(layout); /** * Returns the element containing the entire on-screen keyboard. @@ -569,7 +569,7 @@ Guacamole.OnScreenKeyboard = function(layout) { // Add key to DOM, maintain scale div.appendChild(keyElement); - scaledElements.push(new ScaledElement(div, 1 /* TODO: Pull from layout */, 1, true)); + scaledElements.push(new ScaledElement(div, osk.layout.keyWidth[object] || 1, 1, true)); } // end if object is key name @@ -642,6 +642,16 @@ Guacamole.OnScreenKeyboard.Layout = function(template) { */ this.width = template.width; + /** + * The width of each key, in arbitrary units, relative to other keys in + * this layout. The true pixel size of each key will be determined by the + * overall size of the keyboard. If not defined here, the width of each + * key will default to 1. + * + * @type Object. + */ + this.keyWidth = template.keyWidth || {}; + }; /** @@ -727,13 +737,4 @@ Guacamole.OnScreenKeyboard.Key = function(template, name) { */ this.requires = template.requires || []; - /** - * The width of this key, in arbitrary units, relative to other keys in the - * same layout. The true pixel size of this key will be determined by the - * overall size of the keyboard. By default, this will be 1. - * - * @type Number - */ - this.width = template.width || 1; - }; From 6ae278fde52dce93a8563659cb1522519224a4d0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:37:20 -0700 Subject: [PATCH 11/29] GUAC-1170: Add widths of wide keys. Add proper titles for arrow keys. --- .../src/main/webapp/layouts/en-us-qwerty.json | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 9b76bff01..37152a94a 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -11,10 +11,6 @@ "Enter" : 65293, "Esc" : 65307, "Home" : 65360, - "Left" : 65361, - "Up" : 65362, - "Right" : 65363, - "Down" : 65364, "PgUp" : 65365, "PgDn" : 65366, "End" : 65367, @@ -35,6 +31,23 @@ "Space" : " ", + "Left" : [{ + "title" : "←", + "keysym" : 65361 + }], + "Up" : [{ + "title" : "↑", + "keysym" : 65362 + }], + "Right" : [{ + "title" : "→", + "keysym" : 65363 + }], + "Down" : [{ + "title" : "↓", + "keysym" : 65364 + }], + "Menu" : [{ "title" : "Menu", "modifier" : "super", @@ -356,6 +369,33 @@ ] } + }, + + "keyWidth" : { + + "Back" : 2, + "Tab" : 1.5, + "\\" : 1.5, + "Caps" : 1.85, + "Enter" : 2.25, + "LShift" : 2.1, + "RShift" : 3.1, + + "LCtrl" : 1.6, + "Super" : 1.6, + "LAlt" : 1.6, + "Space" : 6.1, + "RAlt" : 1.6, + "Menu" : 1.6, + "RCtrl" : 1.6, + + "Ins" : 1.75, + "Home" : 1.75, + "PgUp" : 1.75, + "Del" : 1.75, + "End" : 1.75, + "PgDn" : 1.75 + } } From c58935398ab9427bac602fc1e5a8b988b3145529 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:43:56 -0700 Subject: [PATCH 12/29] GUAC-1170: Center the arrow keys. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index ad131bed4..80deeb733 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -97,16 +97,6 @@ border-style: inset; } -.guac-keyboard .guac-keyboard-row { - line-height: 0; -} - -.guac-keyboard .guac-keyboard-column { - display: inline-block; - text-align: center; - vertical-align: top; -} - .guac-keyboard .guac-keyboard-gap { display: inline-block; } @@ -138,3 +128,8 @@ display: none; } + +/* Center the arrow keys */ +.guac-keyboard .guac-keyboard-group.guac-keyboard-fine { + text-align: center; +} From eefab3777544d98404bbb12729f2b745f65a7534 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:47:42 -0700 Subject: [PATCH 13/29] GUAC-1170: Keep function key groups within same row. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index 80deeb733..0681c6b60 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -133,3 +133,11 @@ .guac-keyboard .guac-keyboard-group.guac-keyboard-fine { text-align: center; } + +/* Keep function key groups on same line */ +.guac-keyboard .guac-keyboard-group.guac-keyboard-esc, +.guac-keyboard .guac-keyboard-group.guac-keyboard-f1-f4, +.guac-keyboard .guac-keyboard-group.guac-keyboard-f5-f8, +.guac-keyboard .guac-keyboard-group.guac-keyboard-f9-f12 { + display: inline-block; +} From abc8726b2268a36c965e25f76acbd3a887b4352b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:53:58 -0700 Subject: [PATCH 14/29] GUAC-1170: Use same font for OSK as rest of app. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index 0681c6b60..843442288 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -51,8 +51,7 @@ .guac-keyboard .guac-keyboard-cap { color: white; - font-family: sans-serif; - font-size: 50%; + font-size: 45%; font-weight: lighter; text-align: center; white-space: pre; From 741d25291f5b90492da5a7da705d720db2630381 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 18:57:25 -0700 Subject: [PATCH 15/29] GUAC-1170: For sake of consistency, keyWidth should be keyWidths. --- .../src/main/webapp/modules/OnScreenKeyboard.js | 4 ++-- guacamole/src/main/webapp/layouts/en-us-qwerty.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 96cd5f15f..3657843d3 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -569,7 +569,7 @@ Guacamole.OnScreenKeyboard = function(layout) { // Add key to DOM, maintain scale div.appendChild(keyElement); - scaledElements.push(new ScaledElement(div, osk.layout.keyWidth[object] || 1, 1, true)); + scaledElements.push(new ScaledElement(div, osk.layout.keyWidths[object] || 1, 1, true)); } // end if object is key name @@ -650,7 +650,7 @@ Guacamole.OnScreenKeyboard.Layout = function(template) { * * @type Object. */ - this.keyWidth = template.keyWidth || {}; + this.keyWidths = template.keyWidths || {}; }; diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 37152a94a..de0751708 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -371,7 +371,7 @@ }, - "keyWidth" : { + "keyWidths" : { "Back" : 2, "Tab" : 1.5, From fa130a8e25b349fd636371c8399d16407ef7db5d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 20:37:32 -0700 Subject: [PATCH 16/29] GUAC-1170: Restore support for gaps within the OSK. --- .../main/webapp/modules/OnScreenKeyboard.js | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index 3657843d3..b169bc143 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -475,12 +475,16 @@ Guacamole.OnScreenKeyboard = function(layout) { * first be transformed into the C-style hexadecimal literal for the * Unicode codepoint of that character. For example, the key "A" would * become "guac-keyboard-key-0x41". + * + * If the layout structure object is a number, a gap of that size will be + * inserted. The gap will be given the CSS class "guac-keyboard-gap", and + * will be scaled according to the same size units as each key. * * @private * @param {Element} element * The element to append elements to. * - * @param {Array|Object|String} object + * @param {Array|Object|String|Number} object * The layout structure object to use when constructing the elements to * append. * @@ -525,6 +529,17 @@ Guacamole.OnScreenKeyboard = function(layout) { } + // If a number, create as a gap + else if (typeof object === 'number') { + + // Add gap class + addClass(div, 'guac-keyboard-gap'); + + // Maintain scale + scaledElements.push(new ScaledElement(div, object, object)); + + } + // If a string, create as a key else if (typeof object === 'string') { @@ -626,7 +641,9 @@ Guacamole.OnScreenKeyboard.Layout = function(template) { * layout will be traversed to produce an identically-nested grouping of * keys in the DOM tree. All strings will be transformed into their * corresponding sets of keys, while all objects and arrays will be - * transformed into named groups and anonymous groups respectively. + * transformed into named groups and anonymous groups respectively. Any + * numbers present will be transformed into gaps of that size, scaled + * according to the same units as each key. * * @type Object */ From 146f59e9c0103a4886c879d1321635d46970ca49 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 20:38:01 -0700 Subject: [PATCH 17/29] GUAC-1170: Merge function keys into main section. Add spacing between function groups. --- .../src/main/webapp/layouts/en-us-qwerty.json | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index de0751708..94907372f 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -340,22 +340,19 @@ "layout" : { - "main" : { - "function-keys" : { - "esc" : [ "Esc" ], - "f1-f4" : [ "F1", "F2", "F3", "F4" ], - "f5-f8" : [ "F5", "F6", "F7", "F8" ], - "f9-f12" : [ "F9", "F10", "F11", "F12" ] - }, + "main" : [ - "alpha" : [ - [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], - [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], - [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], - [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], - [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] - ] - }, + [ "Esc", 0.7, "F1", "F2", "F3", "F4", + 0.7, "F5", "F6", "F7", "F8", + 0.7, "F9", "F10", "F11", "F12" ], + + [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], + [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], + [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], + [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], + [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] + + ], "movement" : { "large" : [ From 7666ff6547d3bd291ff6c58362ebb81b1f630a72 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 20:38:43 -0700 Subject: [PATCH 18/29] GUAC-1170: Add spacing between each key. Remove now-unused function group styling. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index 843442288..b93600d87 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -38,6 +38,7 @@ .guac-keyboard .guac-keyboard-key-container { display: inline-block; + margin: 0.05em; } .guac-keyboard .guac-keyboard-key { @@ -132,11 +133,3 @@ .guac-keyboard .guac-keyboard-group.guac-keyboard-fine { text-align: center; } - -/* Keep function key groups on same line */ -.guac-keyboard .guac-keyboard-group.guac-keyboard-esc, -.guac-keyboard .guac-keyboard-group.guac-keyboard-f1-f4, -.guac-keyboard .guac-keyboard-group.guac-keyboard-f5-f8, -.guac-keyboard .guac-keyboard-group.guac-keyboard-f9-f12 { - display: inline-block; -} From 8e9aa3673279ddd38e1acedda2bc461a7c053152 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 20:46:10 -0700 Subject: [PATCH 19/29] GUAC-1170: Move the two main OSK sections to the same line. Abuse section sort order to place arrow keys below others. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 11 +++++++++-- guacamole/src/main/webapp/layouts/en-us-qwerty.json | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index b93600d87..749201d9a 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -129,7 +129,14 @@ } -/* Center the arrow keys */ -.guac-keyboard .guac-keyboard-group.guac-keyboard-fine { + +/* Display both keyboard sections within same line */ +.guac-keyboard .guac-keyboard-group.guac-keyboard-main, +.guac-keyboard .guac-keyboard-group.guac-keyboard-movement { + display: inline-block; +} + +/* Center the keys within the cursor movement section */ +.guac-keyboard .guac-keyboard-group.guac-keyboard-movement { text-align: center; } diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 94907372f..f156f8a58 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -360,7 +360,7 @@ [ "Del", "End", "PgDn" ] ], - "fine" : [ + "small" : [ [ "Up" ], [ "Left", "Down", "Right" ] ] From d8e74fee6ac2207a68bd4a73dce7c431e6cb2c06 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 20:53:25 -0700 Subject: [PATCH 20/29] GUAC-1170: Simplify cursor movement section. Add gap before arrow keys. --- .../src/main/webapp/layouts/en-us-qwerty.json | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index f156f8a58..01c230389 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -354,17 +354,13 @@ ], - "movement" : { - "large" : [ - [ "Ins", "Home", "PgUp" ], - [ "Del", "End", "PgDn" ] - ], - - "small" : [ - [ "Up" ], - [ "Left", "Down", "Right" ] - ] - } + "movement" : [ + [ "Ins", "Home", "PgUp" ], + [ "Del", "End", "PgDn" ], + [ 0.5 ], + [ "Up" ], + [ "Left", "Down", "Right" ] + ] }, From dd5e055f1c210cefa4b0dd0f60d937becf13efed Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:00:47 -0700 Subject: [PATCH 21/29] GUAC-1170: Combine and restore styling location for sake of diff. --- .../src/main/webapp/app/osk/styles/osk.css | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index 749201d9a..f291a48ef 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -97,6 +97,13 @@ border-style: inset; } +.guac-keyboard .guac-keyboard-group.guac-keyboard-main, +.guac-keyboard .guac-keyboard-group.guac-keyboard-movement { + display: inline-block; + text-align: center; + vertical-align: top; +} + .guac-keyboard .guac-keyboard-gap { display: inline-block; } @@ -128,15 +135,3 @@ display: none; } - - -/* Display both keyboard sections within same line */ -.guac-keyboard .guac-keyboard-group.guac-keyboard-main, -.guac-keyboard .guac-keyboard-group.guac-keyboard-movement { - display: inline-block; -} - -/* Center the keys within the cursor movement section */ -.guac-keyboard .guac-keyboard-group.guac-keyboard-movement { - text-align: center; -} From 110c736ebfcb7795bdf01f5f1880a741f94d132c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:01:57 -0700 Subject: [PATCH 22/29] GUAC-1170: Separate function keys out from main sections. --- .../src/main/webapp/layouts/en-us-qwerty.json | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 01c230389..9a40fd40f 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -338,31 +338,33 @@ }, - "layout" : { + "layout" : [ - "main" : [ + [ "Esc", 0.7, "F1", "F2", "F3", "F4", + 0.7, "F5", "F6", "F7", "F8", + 0.7, "F9", "F10", "F11", "F12" ], - [ "Esc", 0.7, "F1", "F2", "F3", "F4", - 0.7, "F5", "F6", "F7", "F8", - 0.7, "F9", "F10", "F11", "F12" ], + { + "main" : [ - [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], - [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], - [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], - [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], - [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] + [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], + [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], + [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], + [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], + [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] - ], + ], - "movement" : [ - [ "Ins", "Home", "PgUp" ], - [ "Del", "End", "PgDn" ], - [ 0.5 ], - [ "Up" ], - [ "Left", "Down", "Right" ] - ] + "movement" : [ + [ "Ins", "Home", "PgUp" ], + [ "Del", "End", "PgDn" ], + [ 0.5 ], + [ "Up" ], + [ "Left", "Down", "Right" ] + ] + } - }, + ], "keyWidths" : { From df433eb4e1fbecd6e389cc6d81ff9aa13e08aab2 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:06:08 -0700 Subject: [PATCH 23/29] GUAC-1170: CSS line-height of keyboard rows must be 0 (or row gaps do not function as expected). --- guacamole/src/main/webapp/app/osk/styles/osk.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index f291a48ef..2165c0a9e 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -97,6 +97,10 @@ border-style: inset; } +.guac-keyboard .guac-keyboard-group { + line-height: 0; +} + .guac-keyboard .guac-keyboard-group.guac-keyboard-main, .guac-keyboard .guac-keyboard-group.guac-keyboard-movement { display: inline-block; From 2e78137c1a0a947302ac21b2acb69b80b3ba9b65 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:06:39 -0700 Subject: [PATCH 24/29] GUAC-1170: Add gap between function keys and main keys. --- guacamole/src/main/webapp/layouts/en-us-qwerty.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 9a40fd40f..168a875ce 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -344,6 +344,8 @@ 0.7, "F5", "F6", "F7", "F8", 0.7, "F9", "F10", "F11", "F12" ], + [ 0.1 ], + { "main" : [ From 5877848796b6a39bc6bf0173fa655931c570ca7d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:32:34 -0700 Subject: [PATCH 25/29] GUAC-1170: Restore original font size. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index 2165c0a9e..e4cba3879 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -52,7 +52,7 @@ .guac-keyboard .guac-keyboard-cap { color: white; - font-size: 45%; + font-size: 50%; font-weight: lighter; text-align: center; white-space: pre; From 9a276e1232e111ca860404abf134fad8397f7898 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:37:21 -0700 Subject: [PATCH 26/29] GUAC-1170: Restructure and resize cursor keys. Space using flex layout. --- .../src/main/webapp/app/osk/styles/osk.css | 39 ++++++++++++++++- .../src/main/webapp/layouts/en-us-qwerty.json | 42 ++++++++++--------- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index e4cba3879..3c5047960 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -101,13 +101,50 @@ line-height: 0; } -.guac-keyboard .guac-keyboard-group.guac-keyboard-main, +.guac-keyboard .guac-keyboard-group.guac-keyboard-alpha, .guac-keyboard .guac-keyboard-group.guac-keyboard-movement { display: inline-block; text-align: center; vertical-align: top; } +.guac-keyboard .guac-keyboard-group.guac-keyboard-main { + + /* IE10 */ + display: -ms-flexbox; + -ms-flex-align: stretch; + -ms-flex-direction: row; + + /* Ancient Mozilla */ + display: -moz-box; + -moz-box-align: stretch; + -moz-box-orient: horizontal; + + /* Ancient WebKit */ + display: -webkit-box; + -webkit-box-align: stretch; + -webkit-box-orient: horizontal; + + /* Old WebKit */ + display: -webkit-flex; + -webkit-align-items: stretch; + -webkit-flex-direction: row; + + /* W3C */ + display: flex; + align-items: stretch; + flex-direction: row; + +} + +.guac-keyboard .guac-keyboard-group.guac-keyboard-movement { + -ms-flex: 1 1 auto; + -moz-box-flex: 1; + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; +} + .guac-keyboard .guac-keyboard-gap { display: inline-block; } diff --git a/guacamole/src/main/webapp/layouts/en-us-qwerty.json b/guacamole/src/main/webapp/layouts/en-us-qwerty.json index 168a875ce..fc7cdfcea 100644 --- a/guacamole/src/main/webapp/layouts/en-us-qwerty.json +++ b/guacamole/src/main/webapp/layouts/en-us-qwerty.json @@ -347,23 +347,25 @@ [ 0.1 ], { - "main" : [ + "main" : { + "alpha" : [ - [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], - [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], - [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], - [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], - [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] + [ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Back" ], + [ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\" ], + [ "Caps", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter" ], + [ "LShift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "RShift" ], + [ "LCtrl", "Super", "LAlt", "Space", "RAlt", "Menu", "RCtrl" ] - ], + ], - "movement" : [ - [ "Ins", "Home", "PgUp" ], - [ "Del", "End", "PgDn" ], - [ 0.5 ], - [ "Up" ], - [ "Left", "Down", "Right" ] - ] + "movement" : [ + [ "Ins", "Home", "PgUp" ], + [ "Del", "End", "PgDn" ], + [ 1 ], + [ "Up" ], + [ "Left", "Down", "Right" ] + ] + } } ], @@ -386,12 +388,12 @@ "Menu" : 1.6, "RCtrl" : 1.6, - "Ins" : 1.75, - "Home" : 1.75, - "PgUp" : 1.75, - "Del" : 1.75, - "End" : 1.75, - "PgDn" : 1.75 + "Ins" : 1.6, + "Home" : 1.6, + "PgUp" : 1.6, + "Del" : 1.6, + "End" : 1.6, + "PgDn" : 1.6 } From 4defd9b22bb1d72c4e45edaae8641032d488bfaf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 28 Apr 2015 21:47:33 -0700 Subject: [PATCH 27/29] GUAC-1170: Fix spacing of sections on resize - nothing should ever wrap. --- guacamole/src/main/webapp/app/osk/styles/osk.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index 3c5047960..a63b5f658 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -36,6 +36,12 @@ vertical-align: middle; } +.guac-keyboard, +.guac-keyboard * { + overflow: hidden; + white-space: nowrap; +} + .guac-keyboard .guac-keyboard-key-container { display: inline-block; margin: 0.05em; From c7625fd0026c2c1a38285653a4d760d91cf6214c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 29 Apr 2015 10:13:23 -0700 Subject: [PATCH 28/29] GUAC-1170: Restore original functionality of OSK. --- .../main/webapp/modules/OnScreenKeyboard.js | 295 ++++++++++++++---- 1 file changed, 237 insertions(+), 58 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js index b169bc143..4a2e0e560 100644 --- a/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/OnScreenKeyboard.js @@ -40,15 +40,6 @@ Guacamole.OnScreenKeyboard = function(layout) { */ var osk = this; - /** - * State of all modifiers. This is the bitwise OR of all active modifier - * values. - * - * @private - * @type Number - */ - 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 @@ -59,6 +50,15 @@ Guacamole.OnScreenKeyboard = function(layout) { */ var modifierKeysyms = {}; + /** + * Map of all key names to their current pressed states. If a key is not + * pressed, it may not be in this map at all, but all pressed keys will + * have a corresponding mapping to true. + * + * @type Object. + */ + var pressed = {}; + /** * All scalable elements which are part of the on-screen keyboard. Each * scalable element is carefully controlled to ensure the interface layout @@ -148,55 +148,6 @@ Guacamole.OnScreenKeyboard = function(layout) { ignoreMouse = osk.touchMouseThreshold; }; - /** - * Map of modifier names to their corresponding, unique, power-of-two value - * bitmasks. - * - * @private - * @type Object. - */ - var modifierMasks = {}; - - /** - * The bitmask to assign to the next modifier, if a new modifier is used - * which does not yet have a corresponding bitmask. - * - * @private - * @type Number - */ - var nextMask = 1; - - /** - * Returns a unique power-of-two value for the modifier with the given - * name. The same value will be returned consistently for the same - * modifier. - * - * @private - * @param {String} name - * The name of the modifier to return a bitmask for. - * - * @return {Number} - * The unique power-of-two value associated with the modifier having - * the given name, which may be newly allocated. - */ - var getModifierMask = function getModifierMask(name) { - - var value = modifierMasks[name]; - if (!value) { - - // Get current modifier, advance to next - value = nextMask; - nextMask <<= 1; - - // Store value of this modifier - modifierMasks[name] = value; - - } - - return value; - - }; - /** * An element whose dimensions are maintained according to an arbitrary * scale. The conversion factor for these arbitrary units to pixels is @@ -259,6 +210,169 @@ Guacamole.OnScreenKeyboard = function(layout) { }; + /** + * Returns whether all modifiers having the given names are currently + * active. + * + * @param {String[]} names + * The names of all modifiers to test. + * + * @returns {Boolean} + * true if all specified modifiers are pressed, false otherwise. + */ + var modifiersPressed = function modifiersPressed(names) { + + // If any required modifiers are not pressed, return false + for (var i=0; i < names.length; i++) { + + // Test whether current modifier is pressed + var name = names[i]; + if (!(name in modifierKeysyms)) + return false; + + } + + // Otherwise, all required modifiers are pressed + return true; + + }; + + /** + * Returns the single matching Key object associated with the key of the + * given name, where that Key object's requirements (such as pressed + * modifiers) are all currently satisfied. + * + * @param {String} keyName + * The name of the key to retrieve. + * + * @returns {Guacamole.OnScreenKeyboard.Key} + * The Key object associated with the given name, where that object's + * requirements are all currently satisfied, or null if no such Key + * can be found. + */ + var getActiveKey = function getActiveKey(keyName) { + + // Get key array for given name + var keys = osk.keys[keyName]; + if (!keys) + return null; + + // Find last matching key + for (var i = keys.length - 1; i >= 0; i--) { + + // Get candidate key + var candidate = keys[i]; + + // If all required modifiers are pressed, use that key + if (modifiersPressed(candidate.requires)) + return candidate; + + } + + // No valid key + return null; + + }; + + /** + * Presses the key having the given name, updating the associated key + * element with the "guac-keyboard-pressed" CSS class. If the key is + * already pressed, this function has no effect. + * + * @param {String} keyName + * The name of the key to press. + * + * @param {String} keyElement + * The element associated with the given key. + */ + var press = function press(keyName, keyElement) { + + // Press key if not yet pressed + if (!pressed[keyName]) { + + addClass(keyElement, "guac-keyboard-pressed"); + + // Get current key based on modifier state + var key = getActiveKey(keyName); + + // Update modifier state + if (key.modifier) { + + // Construct classname for modifier + var modifierClass = "guac-keyboard-modifier-" + getCSSName(key.modifier); + + // Retrieve originally-pressed keysym, if modifier was already pressed + var originalKeysym = modifierKeysyms[key.modifier]; + + // Activate modifier if not pressed + if (!originalKeysym) { + + addClass(keyboard, modifierClass); + modifierKeysyms[key.modifier] = key.keysym; + + // Send key event + if (osk.onkeydown) + osk.onkeydown(key.keysym); + + } + + // Deactivate if not pressed + else { + + removeClass(keyboard, modifierClass); + delete modifierKeysyms[key.modifier]; + + // Send key event + if (osk.onkeyup) + osk.onkeyup(originalKeysym); + + } + + } + + // If not modifier, send key event now + else if (osk.onkeydown) + osk.onkeydown(key.keysym); + + // Mark key as pressed + pressed[keyName] = true; + + } + + }; + + /** + * Releases the key having the given name, removing the + * "guac-keyboard-pressed" CSS class from the associated element. If the + * key is already released, this function has no effect. + * + * @param {String} keyName + * The name of the key to release. + * + * @param {String} keyElement + * The element associated with the given key. + */ + var release = function release(keyName, keyElement) { + + // Release key if currently pressed + if (pressed[keyName]) { + + removeClass(keyElement, "guac-keyboard-pressed"); + + // Get current key based on modifier state + var key = getActiveKey(keyName); + + // Send key event if not a modifier key + if (!key.modifier && osk.onkeyup) + osk.onkeyup(key.keysym); + + // Mark key as released + pressed[keyName] = false; + + } + + }; + // Create keyboard var keyboard = document.createElement("div"); keyboard.className = "guac-keyboard"; @@ -586,6 +700,71 @@ Guacamole.OnScreenKeyboard = function(layout) { div.appendChild(keyElement); scaledElements.push(new ScaledElement(div, osk.layout.keyWidths[object] || 1, 1, true)); + /** + * Handles a touch event which results in the pressing of an OSK + * key. Touch events will result in mouse events being ignored for + * touchMouseThreshold events. + * + * @param {TouchEvent} e + * The touch event being handled. + */ + var touchPress = function touchPress(e) { + e.preventDefault(); + ignoreMouse = osk.touchMouseThreshold; + press(object, keyElement); + }; + + /** + * Handles a touch event which results in the release of an OSK + * key. Touch events will result in mouse events being ignored for + * touchMouseThreshold events. + * + * @param {TouchEvent} e + * The touch event being handled. + */ + var touchRelease = function touchRelease(e) { + e.preventDefault(); + ignoreMouse = osk.touchMouseThreshold; + release(object, keyElement); + }; + + /** + * Handles a mouse event which results in the pressing of an OSK + * key. If mouse events are currently being ignored, this handler + * does nothing. + * + * @param {MouseEvent} e + * The touch event being handled. + */ + var mousePress = function mousePress(e) { + e.preventDefault(); + if (ignoreMouse === 0) + press(object, keyElement); + }; + + /** + * Handles a mouse event which results in the release of an OSK + * key. If mouse events are currently being ignored, this handler + * does nothing. + * + * @param {MouseEvent} e + * The touch event being handled. + */ + var mouseRelease = function mouseRelease(e) { + e.preventDefault(); + if (ignoreMouse === 0) + release(object, keyElement); + }; + + // Handle touch events on key + keyElement.addEventListener("touchstart", touchPress, true); + keyElement.addEventListener("touchend", touchRelease, true); + + // Handle mouse events on key + keyElement.addEventListener("mousedown", mousePress, true); + keyElement.addEventListener("mouseup", mouseRelease, true); + keyElement.addEventListener("mouseout", mouseRelease, true); + } // end if object is key name // Add newly-created group/key From f9911a32883ea773cbe86bf5fb71880604926cc8 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 29 Apr 2015 10:13:38 -0700 Subject: [PATCH 29/29] GUAC-1170: Fix styling of modifiers. --- .../src/main/webapp/app/osk/styles/osk.css | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/guacamole/src/main/webapp/app/osk/styles/osk.css b/guacamole/src/main/webapp/app/osk/styles/osk.css index a63b5f658..f61ac6f9d 100644 --- a/guacamole/src/main/webapp/app/osk/styles/osk.css +++ b/guacamole/src/main/webapp/app/osk/styles/osk.css @@ -73,26 +73,26 @@ border-color: #666; } -.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key.shift, -.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym { +/* Active shift */ +.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-rshift, +.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-lshift, + +/* Active ctrl */ +.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-rctrl, +.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-lctrl, + +/* Active alt */ +.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-ralt, +.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-lalt, + +/* Active caps */ +.guac-keyboard.guac-keyboard-modifier-caps .guac-keyboard-key-caps { background: #882; border-color: #DD4; } -.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key.control, -.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym { - background: #882; - border-color: #DD4; -} - -.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key.alt, -.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym { - background: #882; - border-color: #DD4; -} - -.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key.super, -.guac-keyboard.guac-keyboard-modifier-numsym .guac-keyboard-key.numsym { +.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-super, +.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-menu { background: #882; border-color: #DD4; } @@ -159,9 +159,6 @@ .guac-keyboard:not(.guac-keyboard-modifier-caps) .guac-keyboard-cap.guac-keyboard-requires-caps, -.guac-keyboard:not(.guac-keyboard-modifier-numsym) -.guac-keyboard-cap.guac-keyboard-requires-numsym, - .guac-keyboard:not(.guac-keyboard-modifier-shift) .guac-keyboard-cap.guac-keyboard-requires-shift, @@ -171,10 +168,6 @@ .guac-keyboard-key.guac-keyboard-uses-shift .guac-keyboard-cap:not(.guac-keyboard-requires-shift), -.guac-keyboard.guac-keyboard-modifier-numsym -.guac-keyboard-key.guac-keyboard-uses-numsym -.guac-keyboard-cap:not(.guac-keyboard-requires-numsym), - .guac-keyboard.guac-keyboard-modifier-caps .guac-keyboard-key.guac-keyboard-uses-caps .guac-keyboard-cap:not(.guac-keyboard-requires-caps) {