GUACAMOLE-1204: Merge addition of client-side support for multi-touch events.

This commit is contained in:
James Muehlner
2021-02-11 20:54:37 -08:00
committed by GitHub
15 changed files with 1081 additions and 146 deletions

View File

@@ -463,6 +463,11 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams
// Zoom and pan client via pinch gestures
$scope.clientPinch = function clientPinch(inProgress, startLength, currentLength, centerX, centerY) {
// Do not handle pinch gestures if they would conflict with remote
// handling of similar gestures
if ($scope.client.multiTouchSupport > 1)
return false;
// Do not handle pinch gestures while relative mouse is in use
if (!$scope.client.clientProperties.emulateAbsoluteMouse)
return false;

View File

@@ -119,6 +119,14 @@ angular.module('client').directive('guacClient', [function guacClient() {
*/
var touchPad = new Guacamole.Mouse.Touchpad(displayContainer);
/**
* Guacamole touch event handling object, wrapped around the main
* client dislay.
*
* @type Guacamole.Touch
*/
var touch = new Guacamole.Touch(displayContainer);
/**
* Updates the scale of the attached Guacamole.Client based on current window
* size and "auto-fit" setting.
@@ -185,29 +193,6 @@ angular.module('client').directive('guacClient', [function guacClient() {
};
/**
* Sends the given mouse state to the current client.
*
* @param {Guacamole.Mouse.State} mouseState The mouse state to
* send.
*/
var sendScaledMouseState = function sendScaledMouseState(mouseState) {
// Scale event by current scale
var scaledState = new Guacamole.Mouse.State(
mouseState.x / display.getScale(),
mouseState.y / display.getScale(),
mouseState.left,
mouseState.middle,
mouseState.right,
mouseState.up,
mouseState.down);
// Send mouse event
client.sendMouseState(scaledState);
};
/**
* Handles a mouse event originating from the user's actual mouse.
* This differs from handleEmulatedMouseState() in that the
@@ -226,7 +211,7 @@ angular.module('client').directive('guacClient', [function guacClient() {
// Send mouse state, show cursor if necessary
display.showCursor(!localCursor);
sendScaledMouseState(mouseState);
client.sendMouseState(mouseState, true);
};
@@ -251,7 +236,28 @@ angular.module('client').directive('guacClient', [function guacClient() {
// Send mouse state, ensure cursor is visible
scrollToMouse(mouseState);
sendScaledMouseState(mouseState);
client.sendMouseState(mouseState, true);
};
/**
* Handles a touch event originating from the user's device.
*
* @param {Guacamole.Touch.Event} touchEvent
* The touch event.
*/
var handleTouchEvent = function handleTouchEvent(event) {
// Do not attempt to handle touch state changes if the client
// or display are not yet available
if (!client || !display)
return;
event.preventDefault();
// Send touch state, hiding local cursor
display.showCursor(false);
client.sendTouchState(event.state, true);
};
@@ -310,39 +316,42 @@ angular.module('client').directive('guacClient', [function guacClient() {
localCursor = mouse.setCursor(cursor.canvas, cursor.x, cursor.y);
});
// Swap mouse emulation modes depending on absolute mode flag
$scope.$watch('client.clientProperties.emulateAbsoluteMouse',
function mouseEmulationModeChanged(emulateAbsoluteMouse) {
// Update touch event handling depending on remote multi-touch
// support and mouse emulation mode
$scope.$watchGroup([
'client.multiTouchSupport',
'client.clientProperties.emulateAbsoluteMouse'
], function touchBehaviorChanged(emulateAbsoluteMouse) {
var newMode, oldMode;
// Clear existing event handling
touch.offEach(['touchstart', 'touchmove', 'touchend'], handleTouchEvent);
// Switch to touchscreen if absolute
if (emulateAbsoluteMouse) {
newMode = touchScreen;
oldMode = touchPad;
touchScreen.onmousedown =
touchScreen.onmouseup =
touchScreen.onmousemove = null;
touchPad.onmousedown =
touchPad.onmouseup =
touchPad.onmousemove = null;
// Directly forward local touch events
if ($scope.client.multiTouchSupport)
touch.onEach(['touchstart', 'touchmove', 'touchend'], handleTouchEvent);
// Switch to touchscreen if mouse emulation is required and
// absolute mouse emulation is preferred
else if ($scope.client.clientProperties.emulateAbsoluteMouse) {
touchScreen.onmousedown =
touchScreen.onmouseup =
touchScreen.onmousemove = handleEmulatedMouseState;
}
// Switch to touchpad if not absolute (relative)
// Use touchpad for mouse emulation if absolute mouse emulation
// is not preferred
else {
newMode = touchPad;
oldMode = touchScreen;
}
// Set applicable mouse emulation object, unset the old one
if (newMode) {
// Clear old handlers and copy state to new emulation mode
if (oldMode) {
oldMode.onmousedown = oldMode.onmouseup = oldMode.onmousemove = null;
newMode.currentState.x = oldMode.currentState.x;
newMode.currentState.y = oldMode.currentState.y;
}
// Handle emulated events only from the new emulation mode
newMode.onmousedown =
newMode.onmouseup =
newMode.onmousemove = handleEmulatedMouseState;
touchPad.onmousedown =
touchPad.onmouseup =
touchPad.onmousemove = handleEmulatedMouseState;
}
});

View File

@@ -155,7 +155,7 @@
</div>
<!-- Mouse mode -->
<div class="menu-section" id="mouse-settings">
<div class="menu-section" id="mouse-settings" ng-hide="client.multiTouchSupport">
<h3>{{'CLIENT.SECTION_HEADER_MOUSE_MODE' | translate}}</h3>
<div class="content">
<p class="description">{{'CLIENT.HELP_MOUSE_MODE' | translate}}</p>

View File

@@ -203,6 +203,15 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
*/
this.shareLinks = template.shareLinks || {};
/**
* The number of simultaneous touch contacts supported by the remote
* desktop. Unless explicitly declared otherwise by the remote desktop
* after connecting, this will be 0 (multi-touch unsupported).
*
* @type Number
*/
this.multiTouchSupport = template.multiTouchSupport || 0;
/**
* The current state of the Guacamole client (idle, connecting,
* connected, terminated with error, etc.).
@@ -578,6 +587,11 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
};
// Update level of multi-touch support when known
client.onmultitouch = function multiTouchSupportDeclared(layer, touches) {
managedClient.multiTouchSupport = touches;
};
// Update title when a "name" instruction is received
client.onname = function clientNameReceived(name) {
$rootScope.$apply(function updateClientTitle() {

View File

@@ -124,8 +124,6 @@ angular.module('touch').directive('guacTouchDrag', [function guacTouchDrag() {
element.addEventListener("touchmove", function dragTouchMove(e) {
if (e.touches.length === 1) {
e.stopPropagation();
// Get touch location
var x = e.touches[0].clientX;
var y = e.touches[0].clientY;
@@ -163,8 +161,6 @@ angular.module('touch').directive('guacTouchDrag', [function guacTouchDrag() {
if (startX && startY && e.touches.length === 0) {
e.stopPropagation();
// Signal end of drag gesture
if (inProgress && guacTouchDrag) {
$scope.$apply(function dragComplete() {

View File

@@ -159,8 +159,6 @@ angular.module('touch').directive('guacTouchPinch', [function guacTouchPinch() {
element.addEventListener("touchmove", function pinchTouchMove(e) {
if (e.touches.length === 2) {
e.stopPropagation();
// Calculate current zoom level
currentLength = pinchDistance(e);
@@ -188,8 +186,6 @@ angular.module('touch').directive('guacTouchPinch', [function guacTouchPinch() {
if (startLength && e.touches.length < 2) {
e.stopPropagation();
// Notify of pinch end
if (guacTouchPinch) {
$scope.$apply(function pinchComplete() {

View File

@@ -480,6 +480,7 @@
"FIELD_HEADER_ENABLE_PRINTING" : "Enable printing:",
"FIELD_HEADER_ENABLE_SFTP" : "Enable SFTP:",
"FIELD_HEADER_ENABLE_THEMING" : "Enable theming:",
"FIELD_HEADER_ENABLE_TOUCH" : "Enable multi-touch:",
"FIELD_HEADER_ENABLE_WALLPAPER" : "Enable wallpaper:",
"FIELD_HEADER_GATEWAY_DOMAIN" : "Domain:",
"FIELD_HEADER_GATEWAY_HOSTNAME" : "Hostname:",
@@ -499,6 +500,7 @@
"FIELD_HEADER_READ_ONLY" : "Read-only:",
"FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Exclude mouse:",
"FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Exclude graphics/streams:",
"FIELD_HEADER_RECORDING_EXCLUDE_TOUCH" : "Exclude touch events:",
"FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Include key events:",
"FIELD_HEADER_RECORDING_NAME" : "Recording name:",
"FIELD_HEADER_RECORDING_PATH" : "Recording path:",