diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 213a42ea8..404b5979c 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -24,14 +24,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', function defineManagedClient($rootScope, $injector) { // Required types - var ClientProperties = $injector.get('ClientProperties'); - var ClientIdentifier = $injector.get('ClientIdentifier'); - var ClipboardData = $injector.get('ClipboardData'); - var ManagedClientState = $injector.get('ManagedClientState'); - var ManagedDisplay = $injector.get('ManagedDisplay'); - var ManagedFilesystem = $injector.get('ManagedFilesystem'); - var ManagedFileUpload = $injector.get('ManagedFileUpload'); - var ManagedShareLink = $injector.get('ManagedShareLink'); + var ClientProperties = $injector.get('ClientProperties'); + var ClientIdentifier = $injector.get('ClientIdentifier'); + var ClipboardData = $injector.get('ClipboardData'); + var ManagedClientState = $injector.get('ManagedClientState'); + var ManagedClientThumbnail = $injector.get('ManagedClientThumbnail'); + var ManagedDisplay = $injector.get('ManagedDisplay'); + var ManagedFilesystem = $injector.get('ManagedFilesystem'); + var ManagedFileUpload = $injector.get('ManagedFileUpload'); + var ManagedShareLink = $injector.get('ManagedShareLink'); // Required services var $document = $injector.get('$document'); @@ -46,7 +47,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', var guacHistory = $injector.get('guacHistory'); var guacImage = $injector.get('guacImage'); var guacVideo = $injector.get('guacVideo'); - + + /** + * The minimum amount of time to wait between updates to the client + * thumbnail, in milliseconds. + * + * @type Number + */ + var THUMBNAIL_UPDATE_FREQUENCY = 5000; + /** * Object which serves as a surrogate interface, encapsulating a Guacamole * client while it is active, allowing it to be detached and reattached @@ -98,6 +107,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ this.name = template.name; + /** + * The most recently-generated thumbnail for this connection, as + * stored within the local connection history. If no thumbnail is + * stored, this will be null. + * + * @type ManagedClientThumbnail + */ + this.thumbnail = template.thumbnail; + /** * The current clipboard contents. * @@ -227,45 +245,6 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; - /** - * Store the thumbnail of the given managed client within the connection - * history under its associated ID. If the client is not connected, this - * function has no effect. - * - * @param {String} managedClient - * The client whose history entry should be updated. - */ - var updateHistoryEntry = function updateHistoryEntry(managedClient) { - - var display = managedClient.client.getDisplay(); - - // Update stored thumbnail of previous connection - if (display && display.getWidth() > 0 && display.getHeight() > 0) { - - // Get screenshot - var canvas = display.flatten(); - - // Calculate scale of thumbnail (max 320x240, max zoom 100%) - var scale = Math.min(320 / canvas.width, 240 / canvas.height, 1); - - // Create thumbnail canvas - var thumbnail = $document[0].createElement("canvas"); - thumbnail.width = canvas.width*scale; - thumbnail.height = canvas.height*scale; - - // Scale screenshot to thumbnail - var context = thumbnail.getContext("2d"); - context.drawImage(canvas, - 0, 0, canvas.width, canvas.height, - 0, 0, thumbnail.width, thumbnail.height - ); - - guacHistory.updateThumbnail(managedClient.id, thumbnail.toDataURL("image/png")); - - } - - }; - /** * Requests the creation of a new audio stream, recorded from the user's * local audio input device. If audio input is supported by the connection, @@ -403,12 +382,14 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', // Begin streaming audio input if possible requestAudioStream(client); + // Update thumbnail with initial display contents + ManagedClient.updateThumbnail(managedClient); break; // Update history when disconnecting case 4: // Disconnecting case 5: // Disconnected - updateHistoryEntry(managedClient); + ManagedClient.updateThumbnail(managedClient); break; } @@ -431,6 +412,21 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }); }; + // Automatically update the client thumbnail + client.onsync = function syncReceived() { + + var thumbnail = managedClient.thumbnail; + var timestamp = new Date().getTime(); + + // Update thumbnail if it doesn't exist or is old + if (!thumbnail || timestamp - thumbnail.timestamp >= THUMBNAIL_UPDATE_FREQUENCY) { + $rootScope.$apply(function updateClientThumbnail() { + ManagedClient.updateThumbnail(managedClient); + }); + } + + }; + // Handle any received clipboard data client.onclipboard = function clientClipboardReceived(stream, mimetype) { @@ -651,6 +647,52 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; + /** + * Store the thumbnail of the given managed client within the connection + * history under its associated ID. If the client is not connected, this + * function has no effect. + * + * @param {ManagedClient} managedClient + * The client whose history entry should be updated. + */ + ManagedClient.updateThumbnail = function updateThumbnail(managedClient) { + + var display = managedClient.client.getDisplay(); + + // Update stored thumbnail of previous connection + if (display && display.getWidth() > 0 && display.getHeight() > 0) { + + // Get screenshot + var canvas = display.flatten(); + + // Calculate scale of thumbnail (max 320x240, max zoom 100%) + var scale = Math.min(320 / canvas.width, 240 / canvas.height, 1); + + // Create thumbnail canvas + var thumbnail = $document[0].createElement("canvas"); + thumbnail.width = canvas.width*scale; + thumbnail.height = canvas.height*scale; + + // Scale screenshot to thumbnail + var context = thumbnail.getContext("2d"); + context.drawImage(canvas, + 0, 0, canvas.width, canvas.height, + 0, 0, thumbnail.width, thumbnail.height + ); + + // Store updated thumbnail within client + managedClient.thumbnail = new ManagedClientThumbnail({ + timestamp : new Date().getTime(), + canvas : thumbnail + }); + + // Update historical thumbnail + guacHistory.updateThumbnail(managedClient.id, thumbnail.toDataURL("image/png")); + + } + + }; + return ManagedClient; }]); \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClientThumbnail.js b/guacamole/src/main/webapp/app/client/types/ManagedClientThumbnail.js new file mode 100644 index 000000000..fd9b3dea5 --- /dev/null +++ b/guacamole/src/main/webapp/app/client/types/ManagedClientThumbnail.js @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Provides the ManagedClientThumbnail class used by ManagedClient. + */ +angular.module('client').factory('ManagedClientThumbnail', [function defineManagedClientThumbnail() { + + /** + * Object which represents a thumbnail of the Guacamole client display, + * along with the time that the thumbnail was generated. + * + * @constructor + * @param {ManagedClientThumbnail|Object} [template={}] + * The object whose properties should be copied within the new + * ManagedClientThumbnail. + */ + var ManagedClientThumbnail = function ManagedClientThumbnail(template) { + + // Use empty object by default + template = template || {}; + + /** + * The time that this thumbnail was generated, as the number of + * milliseconds elapsed since midnight of January 1, 1970 UTC. + * + * @type Number + */ + this.timestamp = template.timestamp; + + /** + * The thumbnail of the Guacamole client display. + * + * @type HTMLCanvasElement + */ + this.canvas = template.canvas; + + }; + + return ManagedClientThumbnail; + +}]); \ No newline at end of file