From 7ec53c94ce889b6f5bdcedb689f580dff0eb5fbe Mon Sep 17 00:00:00 2001 From: James Muehlner Date: Mon, 1 Feb 2016 22:03:44 -0800 Subject: [PATCH] GUAC-1480: Use AllowClipboard and execCommand for local/remote clipboard integration. --- .../client/controllers/clientController.js | 19 +- .../app/client/services/clipboardService.js | 171 ++++++++++++++++++ .../webapp/app/client/types/ManagedClient.js | 3 + 3 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 guacamole/src/main/webapp/app/client/services/clipboardService.js diff --git a/guacamole/src/main/webapp/app/client/controllers/clientController.js b/guacamole/src/main/webapp/app/client/controllers/clientController.js index fd445d1c7..99bf72328 100644 --- a/guacamole/src/main/webapp/app/client/controllers/clientController.js +++ b/guacamole/src/main/webapp/app/client/controllers/clientController.js @@ -35,6 +35,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams // Required services var $location = $injector.get('$location'); var authenticationService = $injector.get('authenticationService'); + var clipboardService = $injector.get('clipboardService'); var guacClientManager = $injector.get('guacClientManager'); var guacNotification = $injector.get('guacNotification'); var preferenceService = $injector.get('preferenceService'); @@ -230,11 +231,6 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams $scope.menu.shown = false; }; - // Update the model when clipboard data received from client - $scope.$on('guacClientClipboard', function clientClipboardListener(event, client, mimetype, clipboardData) { - $scope.clipboardData = clipboardData; - }); - /** * The client which should be attached to the client UI. * @@ -526,6 +522,19 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams }); } + // Hide status and sync local clipboard once connected + else if (connectionState === ManagedClientState.ConnectionState.CONNECTED) { + + // Sync with local clipboard + clipboardService.getLocalClipboard().then(function clipboardRead(data) { + $scope.$broadcast('guacClipboard', 'text/plain', data); + }); + + // Hide status notification + guacNotification.showStatus(false); + + } + // Hide status for all other states else guacNotification.showStatus(false); diff --git a/guacamole/src/main/webapp/app/client/services/clipboardService.js b/guacamole/src/main/webapp/app/client/services/clipboardService.js new file mode 100644 index 000000000..788015928 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/services/clipboardService.js @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 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 service for accessing local clipboard data. + */ +angular.module('client').factory('clipboardService', ['$injector', + function clipboardService($injector) { + + // Get required services + var $q = $injector.get('$q'); + var $rootScope = $injector.get('$rootScope'); + + var service = {}; + + /** + * A div which is used to hide the clipboard textarea and remove it from + * document flow. + * + * @type Element + */ + var clipElement = document.createElement('div'); + + /** + * The textarea that will be used to hold the local clipboard contents. + * + * @type Element + */ + var clipboardContent = document.createElement('textarea'); + + /** + * The contents of the last clipboard event broadcast by this service when + * the clipboard contents changed. + * + * @type String + */ + var lastClipboardEvent = ''; + + clipElement.appendChild(clipboardContent); + clipElement.style.position = 'absolute'; + clipElement.style.width = '0px'; + clipElement.style.height = '0px'; + clipElement.style.overflow = 'hidden'; + + document.body.appendChild(clipElement); + + /** + * Sets the local clipboard, if possible, to the given text. + * + * @param {String} text + * The text to which the local clipboard should be set. + * + * @return {Promise} + * A promise that will resolve if setting the clipboard was successful, + * and will reject if it failed. + */ + service.setLocalClipboard = function setLocalClipboard(text) { + + var deferred = $q.defer(); + + // First, see if allow clipboard extension is installed + if (window.AllowClipboard) { + + var clipboardClient = new AllowClipboard.Client.ClipboardClient(); + + clipboardClient.write(text, function(success) { + if (success) + deferred.resolve(); + else + deferred.reject(); + }); + + } + + // Otherwise, try execCommand + else { + + // Copy the given value into the clipboard DOM element + clipboardContent.value = text; + clipboardContent.select(); + + // Attempt to copy data from clipboard element into local clipboard + if (document.execCommand('copy')) + deferred.resolve(); + else + deferred.reject(); + + } + + return deferred.promise; + }; + + /** + * Get the current value of the local clipboard. + * + * @return {Promise} + * A promise that will resolve with the contents of the local clipboard + * if getting the clipboard was successful, and will reject if it + * failed. + */ + service.getLocalClipboard = function getLocalClipboard() { + + var deferred = $q.defer(); + + // First, see if allow clipboard extension is installed + if (window.AllowClipboard) { + + var clipboardClient = new AllowClipboard.Client.ClipboardClient(); + + clipboardClient.read(function(success, data) { + if (success) + deferred.resolve(data); + else + deferred.reject(); + }); + + } + + // Otherwise, try execCommand + else { + + // Clear and select the clipboard DOM element + clipboardContent.value = ''; + clipboardContent.select(); + + // Attempt paste local clipboard into clipboard DOM element + if (document.execCommand('paste')) + deferred.resolve(clipboardContent.value); + else + deferred.reject(); + + } + + return deferred.promise; + }; + + // Periodically attempt to read the clipboard, firing an event if successful + window.setInterval(function periodicallyReadClipboard() { + service.getLocalClipboard().then(function clipboardRead(data) { + + // Fire clipboard event if the data has changed + if (data !== lastClipboardEvent) { + $rootScope.$broadcast('guacClipboard', 'text/plain', data); + lastClipboardEvent = data; + } + + }); + }, 100); + + return service; + +}]); diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index a518ca88a..5187cb00c 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -38,8 +38,10 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', // Required services var $document = $injector.get('$document'); var $q = $injector.get('$q'); + var $rootScope = $injector.get('$rootScope'); var $window = $injector.get('$window'); var authenticationService = $injector.get('authenticationService'); + var clipboardService = $injector.get('clipboardService'); var connectionGroupService = $injector.get('connectionGroupService'); var connectionService = $injector.get('connectionService'); var guacAudio = $injector.get('guacAudio'); @@ -403,6 +405,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', reader.onend = function clipboard_text_end() { $rootScope.$apply(function updateClipboard() { managedClient.clipboardData = data; + clipboardService.setLocalClipboard(data); }); };