diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacClient.js b/guacamole/src/main/frontend/src/app/client/directives/guacClient.js index 18a1b080c..ae0ec644c 100644 --- a/guacamole/src/main/frontend/src/app/client/directives/guacClient.js +++ b/guacamole/src/main/frontend/src/app/client/directives/guacClient.js @@ -132,6 +132,13 @@ angular.module('client').directive('guacClient', [function guacClient() { */ const touch = new Guacamole.Touch(displayContainer); + /** + * Whether clipboard synchronization is currently in progress. + * + * @type boolean + */ + let clipboardSyncInProgress = false; + /** * Updates the scale of the attached Guacamole.Client based on current window * size and "auto-fit" setting. @@ -239,15 +246,47 @@ angular.module('client').directive('guacClient', [function guacClient() { event.stopPropagation(); event.preventDefault(); - // Send mouse state, show cursor if necessary - display.showCursor(!localCursor); - client.sendMouseState(event.state, true); + // Wait for any in-progress clipboard synchronization to complete. + // This avoids the pasting of outdated clipboard content when guacamole + // window regains focus. + waitForClipboardSync().then(() => { - // Broadcast the mouse event - $rootScope.$broadcast(getMouseEventName(event), event, client); + // Send mouse state, show cursor if necessary + display.showCursor(!localCursor); + client.sendMouseState(event.state, true); + + // Broadcast the mouse event + $rootScope.$broadcast(getMouseEventName(event), event, client); + + }); }; + /** + * Returns a promise which resolves once any in-progress clipboard + * synchronization has completed. + * + * @returns {Promise} + * A promise which resolves once any in-progress clipboard + * synchronization has completed. + */ + function waitForClipboardSync() { + return new Promise((resolve) => { + function checkClipboardSync() { + if (!clipboardSyncInProgress) { + resolve(); + return; + } + + // Synchronization can take 8-10ms, so check again shortly + // to not add slight latency in fast environments. + setTimeout(checkClipboardSync, 10); + } + + checkClipboardSync(); + }); + } + /** * Handles a mouse event originating from one of Guacamole's mouse * emulation objects. This differs from handleMouseState() in that @@ -601,6 +640,11 @@ angular.module('client').directive('guacClient', [function guacClient() { client.sendKeyEvent(0, keysym); }); + // Track clipboard synchronization status + $rootScope.$on('clipboardSyncInProgress', function clipboardSyncStatus(event, status) { + clipboardSyncInProgress = status; + }); + /** * Whether a drag/drop operation is currently in progress (the user has * dragged a file over the Guacamole connection but has not yet diff --git a/guacamole/src/main/frontend/src/app/clipboard/services/clipboardService.js b/guacamole/src/main/frontend/src/app/clipboard/services/clipboardService.js index 4ec9d04b8..52ca83a3e 100644 --- a/guacamole/src/main/frontend/src/app/clipboard/services/clipboardService.js +++ b/guacamole/src/main/frontend/src/app/clipboard/services/clipboardService.js @@ -603,6 +603,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', // updated contents storedClipboardData(data); $rootScope.$broadcast('guacClipboard', data); + $rootScope.$broadcast('clipboardSyncInProgress', false); }); }; @@ -615,6 +616,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', * components like the "guacClient" directive. */ service.resyncClipboard = function resyncClipboard() { + $rootScope.$broadcast('clipboardSyncInProgress', true); getLocalClipboard().then(function clipboardRead(data) { return service.setClipboard(data); }, angular.noop);