/* * Copyright (C) 2014 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. */ /** * A directive which displays the Guacamole text input method. */ angular.module('textInput').directive('guacTextInput', [function guacTextInput() { return { restrict: 'E', replace: true, scope: {}, templateUrl: 'app/textInput/templates/guacTextInput.html', controller: ['$scope', '$rootScope', '$element', '$timeout', function guacTextInput($scope, $rootScope, $element, $timeout) { /** * The number of characters to include on either side of text input * content, to allow the user room to use backspace and delete. * * @type Number */ var TEXT_INPUT_PADDING = 128; /** * The Unicode codepoint of the character to use for padding on * either side of text input content. * * @type Number */ var TEXT_INPUT_PADDING_CODEPOINT = 0x200B; /** * Keys which should be allowed through to the client when in text * input mode, providing corresponding key events are received. * Keys in this set will be allowed through to the server. * * @type Object. */ var ALLOWED_KEYS = { 0xFF08: true, /* Backspace */ 0xFF09: true, /* Tab */ 0xFF0D: true, /* Enter */ 0xFF1B: true, /* Escape */ 0xFF50: true, /* Home */ 0xFF51: true, /* Left */ 0xFF52: true, /* Up */ 0xFF53: true, /* Right */ 0xFF54: true, /* Down */ 0xFF57: true, /* End */ 0xFF64: true, /* Insert */ 0xFFBE: true, /* F1 */ 0xFFBF: true, /* F2 */ 0xFFC0: true, /* F3 */ 0xFFC1: true, /* F4 */ 0xFFC2: true, /* F5 */ 0xFFC3: true, /* F6 */ 0xFFC4: true, /* F7 */ 0xFFC5: true, /* F8 */ 0xFFC6: true, /* F9 */ 0xFFC7: true, /* F10 */ 0xFFC8: true, /* F11 */ 0xFFC9: true, /* F12 */ 0xFFE1: true, /* Left shift */ 0xFFE2: true, /* Right shift */ 0xFFFF: true /* Delete */ }; /** * Recently-sent text, ordered from oldest to most recent. * * @type String[] */ $scope.sentText = []; /** * Whether the "Alt" key is currently pressed within the text input * interface. * * @type Boolean */ $scope.altPressed = false; /** * Whether the "Ctrl" key is currently pressed within the text * input interface. * * @type Boolean */ $scope.ctrlPressed = false; /** * The text area input target. * * @type Element */ var target = $element.find('.target')[0]; /** * Whether the text input target currently has focus. Setting this * attribute has no effect, but any bound property will be updated * as focus is gained or lost. * * @type Boolean */ var hasFocus = false; target.onfocus = function targetFocusGained() { hasFocus = true; resetTextInputTarget(TEXT_INPUT_PADDING); }; target.onblur = function targetFocusLost() { hasFocus = false; target.focus(); }; /** * Whether composition is currently active within the text input * target element, such as when an IME is in use. * * @type Boolean */ var composingText = false; target.addEventListener("compositionstart", function targetComposeStart(e) { composingText = true; }, false); target.addEventListener("compositionend", function targetComposeEnd(e) { composingText = false; }, false); /** * Translates a given Unicode codepoint into the corresponding X11 * keysym. * * @param {Number} codepoint * The Unicode codepoint to translate. * * @returns {Number} * The X11 keysym that corresponds to the given Unicode * codepoint, or null if no such keysym exists. */ var keysymFromCodepoint = function keysymFromCodepoint(codepoint) { // Keysyms for control characters if (codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F)) return 0xFF00 | codepoint; // Keysyms for ASCII chars if (codepoint >= 0x0000 && codepoint <= 0x00FF) return codepoint; // Keysyms for Unicode if (codepoint >= 0x0100 && codepoint <= 0x10FFFF) return 0x01000000 | codepoint; return null; }; /** * Presses and releases the key corresponding to the given keysym, * as if typed by the user. * * @param {Number} keysym The keysym of the key to send. */ var sendKeysym = function sendKeysym(keysym) { $rootScope.$broadcast('guacSyntheticKeydown', keysym); $rootScope.$broadcast('guacSyntheticKeyup', keysym); }; /** * Presses and releases the key having the keysym corresponding to * the Unicode codepoint given, as if typed by the user. * * @param {Number} codepoint * The Unicode codepoint of the key to send. */ var sendCodepoint = function sendCodepoint(codepoint) { if (codepoint === 10) { sendKeysym(0xFF0D); releaseStickyKeys(); return; } var keysym = keysymFromCodepoint(codepoint); if (keysym) { sendKeysym(keysym); releaseStickyKeys(); } }; /** * Translates each character within the given string to keysyms and * sends each, in order, as if typed by the user. * * @param {String} content * The string to send. */ var sendString = function sendString(content) { var sentText = ""; // Send each codepoint within the string for (var i=0; i