Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 195 additions & 78 deletions guacamole-common-js/src/main/webapp/modules/Client.js

Large diffs are not rendered by default.

35 changes: 29 additions & 6 deletions guacamole-common-js/src/main/webapp/modules/Display.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ Guacamole.Display = function() {
* Reference to this Guacamole.Display.
* @private
*/
var guac_display = this;
const guac_display = this;

var displayWidth = 0;
var displayHeight = 0;
var displayScale = 1;
let displayWidth = 0;
let displayHeight = 0;
let monitorWidth = null;
let monitorHeight = null;
let displayScale = 1;

// Create display
var display = document.createElement("div");
const display = document.createElement("div");
display.style.position = "relative";
display.style.width = displayWidth + "px";
display.style.height = displayHeight + "px";
Expand Down Expand Up @@ -740,7 +742,7 @@ Guacamole.Display = function() {
* @param {!number} y
* The Y coordinate to move the cursor to.
*/
this.moveCursor = function(x, y) {
this.moveCursor = function moveCursor(x, y) {

// Move cursor layer
cursor.translate(x - guac_display.cursorHotspotX,
Expand All @@ -752,6 +754,19 @@ Guacamole.Display = function() {

};

/**
* Set the current monitor size.
*
* @param {!number} width
* The width of the monitor, in pixels.
* @param {!number} height
* The height of the monitor, in pixels.
*/
this.setMonitorSize = function setMonitorSize(width, height) {
monitorWidth = width;
monitorHeight = height;
}

/**
* Changes the size of the given Layer to the given width and height.
* Resizing is only attempted if the new size provided is actually different
Expand All @@ -769,6 +784,14 @@ Guacamole.Display = function() {
this.resize = function(layer, width, height) {
scheduleTask(function __display_resize() {

// Adjust width when using multiple monitors
if (monitorWidth)
width = monitorWidth;

// Adjust height when using multiple monitors
if (monitorHeight)
height = monitorHeight;

layer.resize(width, height);

// Resize display if default layer is resized
Expand Down
11 changes: 11 additions & 0 deletions guacamole-common-js/src/main/webapp/modules/Mouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ Guacamole.Mouse = function Mouse(element) {
Guacamole.Event.DOMEvent.cancelEvent(e);
}, false);

// Capture mouse events outside the display element when a button is
// pressed to allow drag and drop between multiple windows.
element.addEventListener("pointerdown", function(e) {
element.setPointerCapture(e.pointerId);
}, false);

// Stop capture when mouse button is released
element.addEventListener("pointerup", function(e) {
element.releasePointerCapture(e.pointerId);
}, false);

element.addEventListener("mousemove", function(e) {

// If ignoring events, decrement counter
Expand Down
2 changes: 1 addition & 1 deletion guacamole-common-js/src/main/webapp/modules/Tunnel.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
}


this.sendMessage = function() {
this.sendMessage = function sendMessage() {

// Do not attempt to send messages if not connected
if (!tunnel.isConnected())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@
"type" : "ENUM",
"options" : [ "", "display-update", "reconnect" ]
},
{
"name" : "secondary-monitors",
"type" : "NUMERIC"
},
{
"name" : "read-only",
"type" : "BOOLEAN",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
const dataSourceService = $injector.get('dataSourceService');
const guacClientManager = $injector.get('guacClientManager');
const guacFullscreen = $injector.get('guacFullscreen');
const guacManageMonitor = $injector.get('guacManageMonitor');
const iconService = $injector.get('iconService');
const preferenceService = $injector.get('preferenceService');
const requestService = $injector.get('requestService');
Expand Down Expand Up @@ -724,6 +725,67 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
// Set client-specific menu actions
$scope.clientMenuActions = [ DISCONNECT_MENU_ACTION,FULLSCREEN_MENU_ACTION ];

/**
* Show the section to add an additional monitor only on supported protocols
* and when the functionality is enabled.
*
* @returns {boolean}
* true when user can use multi monitor, false otherwise.
*/
$scope.showAddMonitor = function showAddMonitor() {

// Multi monitor only supported with rdp protocol
if ($scope.focusedClient.protocol !== 'rdp')
return false;

// The maximum number of secondary monitors that can be added.
const secondaryMonitorsAllowed = parseInt(
$scope.focusedClient.arguments['secondary-monitors'] ?? 0);

guacManageMonitor.setMaxSecondaryMonitors(secondaryMonitorsAllowed);

// Secondary monitors disabled
if (secondaryMonitorsAllowed < 1 || !guacManageMonitor.supported())
return false;

// Disable button when the limit is reached (still visible)
$scope.disableAddMonitor = guacManageMonitor.monitorLimitReached();

return true;

};

/**
* Action that adds an additional monitor on the RDP connection. Will open
* a new window to display the new monitor.
* Check that the client is in connected state and that the monitor limit
* is not reached before triggering the open.
*/
$scope.addMonitor = function addMonitor() {

// Prevent opening an additional monitor when the client is not connected
if ($scope.focusedClient.clientState.connectionState !== 'CONNECTED')
return;

// Prevent opening of too many monitors
if (guacManageMonitor.monitorLimitReached())
return;

// Add or remove additional monitor
guacManageMonitor.addMonitor();

// Close menu
$scope.menu.shown = false;

};

// Init guacManageMonitor
guacManageMonitor.init();
guacManageMonitor.menuShown = function menuShown() {
$scope.menu.shown = !$scope.menu.shown;
$scope.$apply();
}

/**
* @borrows Protocol.getNamespace
*/
Expand Down Expand Up @@ -874,6 +936,9 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams

// always unset fullscreen mode to not confuse user
guacFullscreen.setFullscreenMode(false);

// Close additional monitors
guacManageMonitor.closeAllMonitors();
});

}]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* 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.
*/

/**
* The controller for the page used to display secondary monitors.
*/
angular.module('client').controller('secondaryMonitorController', ['$scope', '$injector', '$routeParams',
function clientController($scope, $injector, $routeParams) {

// Required services
const $window = $injector.get('$window');
const guacFullscreen = $injector.get('guacFullscreen');
const guacManageMonitor = $injector.get('guacManageMonitor');

/**
* ID of this monitor.
*
* @type {!String}
*/
const monitorId = $routeParams.id;

/**
* In order to open the guacamole menu, we need to hit ctrl-alt-shift. There are
* several possible keysysms for each key.
*/
const SHIFT_KEYS = {0xFFE1 : true, 0xFFE2 : true},
ALT_KEYS = {0xFFE9 : true, 0xFFEA : true, 0xFE03 : true,
0xFFE7 : true, 0xFFE8 : true},
CTRL_KEYS = {0xFFE3 : true, 0xFFE4 : true},
MENU_KEYS = angular.extend({}, SHIFT_KEYS, ALT_KEYS, CTRL_KEYS);

guacManageMonitor.init("secondary");
guacManageMonitor.monitorId = monitorId;

guacManageMonitor.openConsentButton = function openConsentButton() {

// Show button
$scope.showFullscreenConsent = true;
$scope.$apply();

// Auto hide button after delay
setTimeout(function() {
$scope.showFullscreenConsent = false;
$scope.$apply();
}, 10000);

};

/**
* User clicked on the consent button : switch to fullscreen mode and hide
* the button.
*/
$scope.enableFullscreenMode = function enableFullscreenMode() {
guacFullscreen.setFullscreenMode(true);
$scope.showFullscreenConsent = false;
};

/**
* Returns whether the shortcut for showing/hiding the Guacamole menu
* (Ctrl+Alt+Shift) has been pressed.
*
* @param {Guacamole.Keyboard} keyboard
* The Guacamole.Keyboard object tracking the local keyboard state.
*
* @returns {boolean}
* true if Ctrl+Alt+Shift has been pressed, false otherwise.
*/
const isMenuShortcutPressed = function isMenuShortcutPressed(keyboard) {

// Ctrl+Alt+Shift has NOT been pressed if any key is currently held
// down that isn't Ctrl, Alt, or Shift
if (_.findKey(keyboard.pressed, (_, keysym) => !MENU_KEYS[keysym]))
return false;

// Verify that one of each required key is held, regardless of
// left/right location on the keyboard
return !!(
_.findKey(SHIFT_KEYS, (_, keysym) => keyboard.pressed[keysym])
&& _.findKey(ALT_KEYS, (_, keysym) => keyboard.pressed[keysym])
&& _.findKey(CTRL_KEYS, (_, keysym) => keyboard.pressed[keysym])
);

};

// Opening the Guacamole menu after Ctrl+Alt+Shift, preventing those
// keypresses from reaching any Guacamole client
$scope.$on('guacBeforeKeydown', function incomingKeydown(event, keysym, keyboard) {

// Toggle menu if menu shortcut (Ctrl+Alt+Shift) is pressed
if (isMenuShortcutPressed(keyboard)) {

// Don't send this key event through to the client, and release
// all other keys involved in performing this shortcut
event.preventDefault();
keyboard.reset();

// Toggle the menu
$scope.$apply(function() {
guacManageMonitor.pushBroadcastMessage('guacMenu', true);
});

}

});

// Send monitor-close event to broadcast channel on window unload
$window.addEventListener('unload', function unloadWindow() {
guacManageMonitor.pushBroadcastMessage('monitorClose', monitorId);
});

}]);
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ angular.module('client').directive('guacClient', [function guacClient() {
function guacClientController($scope, $injector, $element) {

// Required types
const ManagedClient = $injector.get('ManagedClient');
const ManagedClient = $injector.get('ManagedClient');

// Required services
const $rootScope = $injector.get('$rootScope');
const $window = $injector.get('$window');
const $rootScope = $injector.get('$rootScope');
const $window = $injector.get('$window');
const guacManageMonitor = $injector.get('guacManageMonitor');

/**
* Whether the local, hardware mouse cursor is in use.
Expand Down Expand Up @@ -459,11 +460,20 @@ angular.module('client').directive('guacClient', [function guacClient() {
ManagedClient.connect($scope.client, main.offsetWidth, main.offsetHeight);

const pixelDensity = $window.devicePixelRatio || 1;
const width = main.offsetWidth * pixelDensity;
const height = main.offsetHeight * pixelDensity;
const width = main.offsetWidth * pixelDensity;
const height = main.offsetHeight * pixelDensity;
const top = window.screenY;
const left = window.screenX;

// Window resized
if (display.getWidth() !== width || display.getHeight() !== height)
client.sendSize(width, height);
guacManageMonitor.sendSize({
width: width,
height: height,
monitorId: 0,
top: top,
left: left,
});

}

Expand Down
Loading