diff --git a/guacamole/src/main/webapp/app/client/clientModule.js b/guacamole/src/main/webapp/app/client/clientModule.js index 944189dd4..38eee4bbb 100644 --- a/guacamole/src/main/webapp/app/client/clientModule.js +++ b/guacamole/src/main/webapp/app/client/clientModule.js @@ -23,4 +23,4 @@ /** * The module for code used to connect to a connection or balancing group. */ -angular.module('client', ['auth', 'history', 'osk', 'rest']); +angular.module('client', ['auth', 'history', 'osk', 'rest', 'textInput']); diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index ec2690ee6..e0efc341c 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -204,8 +204,9 @@ angular.module('home').controller('clientController', ['$scope', '$routeParams', // Show/hide UI elements depending on input method $scope.$watch('inputMethod', function setInputMethod(inputMethod) { - // Show on-screen keyboard only if selected - $scope.showOSK = (inputMethod === 'osk'); + // Show input methods only if selected + $scope.showOSK = (inputMethod === 'osk'); + $scope.showTextInput = (inputMethod === 'text'); }); diff --git a/guacamole/src/main/webapp/app/client/directives/guacClient.js b/guacamole/src/main/webapp/app/client/directives/guacClient.js index 476c5ae11..8c330805a 100644 --- a/guacamole/src/main/webapp/app/client/directives/guacClient.js +++ b/guacamole/src/main/webapp/app/client/directives/guacClient.js @@ -115,6 +115,13 @@ angular.module('client').directive('guacClient', [function guacClient() { */ var main = $element[0]; + /** + * The element which functions as a detector for size changes. + * + * @type Element + */ + var resizeSensor = $element.find('.resize-sensor')[0]; + /** * Guacamole mouse event object, wrapped around the main client * display. @@ -491,8 +498,8 @@ angular.module('client').directive('guacClient', [function guacClient() { $scope.clientProperties.scale = $scope.clientProperties.minScale; }); - // If the window is resized, attempt to resize client - $window.addEventListener('resize', function onResizeWindow() { + // If the element is resized, attempt to resize client + resizeSensor.contentWindow.addEventListener('resize', function mainElementResized() { // Send new display size, if changed if (client && display) { @@ -529,6 +536,16 @@ angular.module('client').directive('guacClient', [function guacClient() { event.preventDefault(); } }); + + // Universally handle all synthetic keydown events + $scope.$on('guacSyntheticKeydown', function syntheticKeydownListener(event, keysym) { + client.sendKeyEvent(1, keysym); + }); + + // Universally handle all synthetic keyup events + $scope.$on('guacSyntheticKeyup', function syntheticKeyupListener(event, keysym) { + client.sendKeyEvent(0, keysym); + }); /** * Converts the given bytes to a base64-encoded string. diff --git a/guacamole/src/main/webapp/app/client/styles/client.css b/guacamole/src/main/webapp/app/client/styles/client.css index 615849ca3..6f20b41b4 100644 --- a/guacamole/src/main/webapp/app/client/styles/client.css +++ b/guacamole/src/main/webapp/app/client/styles/client.css @@ -27,18 +27,6 @@ body.client { overflow: hidden; } -/* Viewport Clone */ - -#viewportClone { - display: table; - height: 100%; - width: 100%; - position: fixed; - left: 0; - top: 0; - visibility: hidden; -} - #preload { visibility: hidden; position: absolute; @@ -48,3 +36,21 @@ body.client { height: 0; overflow: hidden; } + +.client-view { + display: table; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.client-view .client-body { + display: table-row; +} + +.client-view .client-bottom { + display: table-row; + height: 0; +} diff --git a/guacamole/src/main/webapp/app/client/styles/display.css b/guacamole/src/main/webapp/app/client/styles/display.css index 7fd44f623..a606514e4 100644 --- a/guacamole/src/main/webapp/app/client/styles/display.css +++ b/guacamole/src/main/webapp/app/client/styles/display.css @@ -32,11 +32,21 @@ div.main { overflow: auto; - position: fixed; - top: 0; - bottom: 0; - right: 0; + width: 100%; + height: 100%; + position: relative; +} + +.resize-sensor { + height: 100%; + width: 100%; + position: absolute; left: 0; + top: 0; + overflow: hidden; + border: none; + opacity: 0; + z-index: -1; } div.displayOuter { diff --git a/guacamole/src/main/webapp/app/client/templates/clientError.html b/guacamole/src/main/webapp/app/client/templates/blank.html similarity index 71% rename from guacamole/src/main/webapp/app/client/templates/clientError.html rename to guacamole/src/main/webapp/app/client/templates/blank.html index 735d7ca0a..62b9f6aa2 100644 --- a/guacamole/src/main/webapp/app/client/templates/clientError.html +++ b/guacamole/src/main/webapp/app/client/templates/blank.html @@ -19,17 +19,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - -
-
-
-

{{'client.error.connectionErrorTitle' | translate}}

- -

{{errorStatus}}

- -
- -
-
-
-
\ No newline at end of file + diff --git a/guacamole/src/main/webapp/app/client/templates/client.html b/guacamole/src/main/webapp/app/client/templates/client.html index 460e71641..4528040d2 100644 --- a/guacamole/src/main/webapp/app/client/templates/client.html +++ b/guacamole/src/main/webapp/app/client/templates/client.html @@ -20,99 +20,107 @@ THE SOFTWARE. --> -
- - - + +
- -
+ +
- -
+ + - - - -
- + +
+ + +
+ +
+
- -
- - +
+ + +
+ +
+ + + \ No newline at end of file +
+ + +
+ + +
diff --git a/guacamole/src/main/webapp/app/client/templates/guacClient.html b/guacamole/src/main/webapp/app/client/templates/guacClient.html index 91daf3b81..30e5d606d 100644 --- a/guacamole/src/main/webapp/app/client/templates/guacClient.html +++ b/guacamole/src/main/webapp/app/client/templates/guacClient.html @@ -21,6 +21,9 @@ THE SOFTWARE. --> + + +
diff --git a/guacamole/src/main/webapp/app/index/controllers/indexController.js b/guacamole/src/main/webapp/app/index/controllers/indexController.js index 25ca651b6..c927e22e2 100644 --- a/guacamole/src/main/webapp/app/index/controllers/indexController.js +++ b/guacamole/src/main/webapp/app/index/controllers/indexController.js @@ -193,19 +193,35 @@ angular.module('index').controller('indexController', ['$scope', '$injector', // Try to load them now $scope.loadBasicPermissions(); - + // Create event listeners at the global level var keyboard = new Guacamole.Keyboard($document[0]); - // Broadcast keydown events down the scope heirarchy + // Broadcast keydown events keyboard.onkeydown = function onkeydown(keysym) { + + // Warn of pending keydown + var guacBeforeKeydownEvent = $scope.$broadcast('guacBeforeKeydown', keysym, keyboard); + if (guacBeforeKeydownEvent.defaultPrevented) + return true; + + // If not prevented via guacBeforeKeydown, fire corresponding keydown event var guacKeydownEvent = $scope.$broadcast('guacKeydown', keysym, keyboard); return !guacKeydownEvent.defaultPrevented; + }; - // Broadcast keyup events down the scope heirarchy + // Broadcast keyup events keyboard.onkeyup = function onkeyup(keysym) { + + // Warn of pending keyup + var guacBeforeKeydownEvent = $scope.$broadcast('guacBeforeKeyup', keysym, keyboard); + if (guacBeforeKeydownEvent.defaultPrevented) + return; + + // If not prevented via guacBeforeKeyup, fire corresponding keydown event $scope.$broadcast('guacKeyup', keysym, keyboard); + }; // Release all keys when window loses focus diff --git a/guacamole/src/main/webapp/app/osk/directives/guacOsk.js b/guacamole/src/main/webapp/app/osk/directives/guacOsk.js index 6b7cf529c..f8891e437 100644 --- a/guacamole/src/main/webapp/app/osk/directives/guacOsk.js +++ b/guacamole/src/main/webapp/app/osk/directives/guacOsk.js @@ -88,12 +88,12 @@ angular.module('osk').directive('guacOsk', [function guacOsk() { // Broadcast keydown for each key pressed keyboard.onkeydown = function(keysym) { - $rootScope.$broadcast('guacKeydown', keysym); + $rootScope.$broadcast('guacSyntheticKeydown', keysym); }; // Broadcast keydown for each key released keyboard.onkeyup = function(keysym) { - $rootScope.$broadcast('guacKeyup', keysym); + $rootScope.$broadcast('guacSyntheticKeyup', keysym); }; // Resize keyboard whenever window changes size diff --git a/guacamole/src/main/webapp/app/textInput/directives/guacKey.js b/guacamole/src/main/webapp/app/textInput/directives/guacKey.js index c30665625..f6dd6f41f 100644 --- a/guacamole/src/main/webapp/app/textInput/directives/guacKey.js +++ b/guacamole/src/main/webapp/app/textInput/directives/guacKey.js @@ -90,8 +90,8 @@ angular.module('textInput').directive('guacKey', [function guacKey() { // For all non-sticky keys, press and release key immediately else { - $rootScope.$broadcast('guacKeydown', $scope.keysym); - $rootScope.$broadcast('guacKeyup', $scope.keysym); + $rootScope.$broadcast('guacSyntheticKeydown', $scope.keysym); + $rootScope.$broadcast('guacSyntheticKeyup', $scope.keysym); } }; @@ -101,11 +101,11 @@ angular.module('textInput').directive('guacKey', [function guacKey() { // If the key is pressed now, send keydown if (isPressed) - $rootScope.$broadcast('guacKeydown', $scope.keysym); + $rootScope.$broadcast('guacSyntheticKeydown', $scope.keysym); // If the key was pressed, but is not pressed any longer, send keyup else if (wasPressed) - $rootScope.$broadcast('guacKeyup', $scope.keysym); + $rootScope.$broadcast('guacSyntheticKeyup', $scope.keysym); }); diff --git a/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js b/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js index c09572489..dc9600f12 100644 --- a/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js +++ b/guacamole/src/main/webapp/app/textInput/directives/guacTextInput.js @@ -28,7 +28,17 @@ angular.module('textInput').directive('guacTextInput', [function guacTextInput() return { restrict: 'E', replace: true, - scope: {}, + scope: { + + /** + * Whether the text input UI should have focus. Setting this value + * is not guaranteed to work, due to browser limitations. + * + * @type Boolean + */ + needsFocus : '=?' + + }, templateUrl: 'app/textInput/templates/guacTextInput.html', controller: ['$scope', '$rootScope', '$element', '$timeout', @@ -50,6 +60,47 @@ angular.module('textInput').directive('guacTextInput', [function guacTextInput() */ 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 = { + 0xFE03: true, /* AltGr */ + 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 */ + 0xFFE3: true, /* Left ctrl */ + 0xFFE4: true, /* Right ctrl */ + 0xFFE9: true, /* Left alt */ + 0xFFEA: true, /* Right alt */ + 0xFFFF: true /* Delete */ + }; + /** * Recently-sent text, ordered from oldest to most recent. * @@ -151,8 +202,8 @@ angular.module('textInput').directive('guacTextInput', [function guacTextInput() * @param {Number} keysym The keysym of the key to send. */ var sendKeysym = function sendKeysym(keysym) { - $rootScope.$broadcast('guacKeydown', keysym); - $rootScope.$broadcast('guacKeyup', keysym); + $rootScope.$broadcast('guacSyntheticKeydown', keysym); + $rootScope.$broadcast('guacSyntheticKeyup', keysym); }; /** @@ -283,6 +334,28 @@ angular.module('textInput').directive('guacTextInput', [function guacTextInput() e.preventDefault(); }, false); + // Attempt to change focus depending on need + $scope.$watch('needsFocus', function focusDesireChanged(focusNeeded) { + + if (focusNeeded) + target.focus(); + else + target.blur(); + + }); + + // If the text input UI has focus, prevent keydown events + $scope.$on('guacBeforeKeydown', function filterKeydown(event, keysym) { + if (hasFocus && !ALLOWED_KEYS[keysym]) + event.preventDefault(); + }); + + // If the text input UI has focus, prevent keyup events + $scope.$on('guacBeforeKeyup', function filterKeyup(event, keysym) { + if (hasFocus && !ALLOWED_KEYS[keysym]) + event.preventDefault(); + }); + }] };