diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js index 815e9cddf..dc77739e6 100644 --- a/guacamole-common-js/src/main/webapp/modules/Client.js +++ b/guacamole-common-js/src/main/webapp/modules/Client.js @@ -709,6 +709,21 @@ Guacamole.Client = function(tunnel) { * A status object which describes the error. */ this.onerror = null; + + /** + * Fired when an arbitrary message is received from the tunnel that should + * be processed by the client. + * + * @event + * @param {!number} msgcode + * A status code sent by the remote server that indicates the nature of + * the message that is being sent to the client. + * + * @param {string[]} args + * An array of arguments to be processed with the message sent to the + * client. + */ + this.onmsg = null; /** * Fired when a audio stream is created. The stream provided to this event @@ -1427,6 +1442,12 @@ Guacamole.Client = function(tunnel) { } }, + + "msg" : function(parameters) { + + if (guac_client.onmsg) guac_client.onmsg(parseInt(parameters[0]), parameters.slice(1)); + + }, "name": function(parameters) { if (guac_client.onname) guac_client.onname(parameters[0]); @@ -1954,3 +1975,31 @@ Guacamole.Client.DefaultTransferFunction = { } }; + +/** + * A list of possible messages that can be sent by the server for processing + * by the client. + * + * @type {!Object.} + */ +Guacamole.Client.Message = { + + /** + * A client message that indicates that a user has joined an existing + * connection. This message expects a single additional argument - the + * name of the user who has joined the connection. + * + * @type {!number} + */ + "USER_JOINED": 0x0001, + + /** + * A client message that indicates that a user has left an existing + * connection. This message expects a single additional argument - the + * name of the user who has left the connection. + * + * @type {!number} + */ + "USER_LEFT": 0x0002 + +}; diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java index 1b34ef84a..ee1e26aef 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java @@ -293,7 +293,14 @@ public class ConfiguredGuacamoleSocket extends DelegatingGuacamoleSocket { if (GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE.isSupported(protocolVersion)) { String timezone = info.getTimezone(); if (timezone != null) - writer.writeInstruction(new GuacamoleInstruction("timezone", info.getTimezone())); + writer.writeInstruction(new GuacamoleInstruction("timezone", timezone)); + } + + // Send client name, if supported and available + if (GuacamoleProtocolCapability.NAME_HANDSHAKE.isSupported(protocolVersion)) { + String name = info.getName(); + if (name != null) + writer.writeInstruction(new GuacamoleInstruction("name", name)); } // Send args diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java index 6d54a2f6c..b295db887 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java @@ -47,17 +47,22 @@ public class GuacamoleClientInformation { /** * The list of audio mimetypes reported by the client to be supported. */ - private final List audioMimetypes = new ArrayList(); + private final List audioMimetypes = new ArrayList<>(); /** * The list of video mimetypes reported by the client to be supported. */ - private final List videoMimetypes = new ArrayList(); + private final List videoMimetypes = new ArrayList<>(); /** * The list of image mimetypes reported by the client to be supported. */ - private final List imageMimetypes = new ArrayList(); + private final List imageMimetypes = new ArrayList<>(); + + /** + * The name of the user reported by the client. + */ + private String name; /** * The timezone reported by the client. @@ -150,6 +155,17 @@ public class GuacamoleClientInformation { return imageMimetypes; } + /** + * Returns the name of the Guacamole user as reported by the client, or null + * if the user name is not set. + * + * @return + * A string value of the human-readable name reported by the client. + */ + public String getName() { + return name; + } + /** * Return the timezone as reported by the client, or null if the timezone * is not set. Valid timezones are specified in IANA zone key format, @@ -162,6 +178,16 @@ public class GuacamoleClientInformation { return timezone; } + /** + * Set the human-readable name of the user associated with this client. + * + * @param name + * The human-readable name of the user associated with this client. + */ + public void setName(String name) { + this.name = name; + } + /** * Set the string value of the timezone, or null if the timezone will not * be provided by the client. Valid timezones are specified in IANA zone diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java index 5cad8a280..4a5653bbd 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java @@ -33,6 +33,21 @@ public enum GuacamoleProtocolCapability { */ ARBITRARY_HANDSHAKE_ORDER(GuacamoleProtocolVersion.VERSION_1_1_0), + /** + * Support for the "msg" instruction. The "msg" instruction allows the + * server to send messages to the client. Support for this instruction was + * introduced in {@link GuacamoleProtocolVersion#VERSION_1_5_0}. + */ + MSG_INSTRUCTION(GuacamoleProtocolVersion.VERSION_1_5_0), + + /** + * Support for the "name" handshake instruction, allowing clients to send + * the name of the Guacamole user to be passed to guacd and associated with + * connections. Support for this instruction was introduced in + * {@link GuacamoleProtocolVersion#VERSION_1_5_0}. + */ + NAME_HANDSHAKE(GuacamoleProtocolVersion.VERSION_1_5_0), + /** * Negotiation of Guacamole protocol version between client and server * during the protocol handshake. The ability to negotiate protocol diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java index 3ceda3b1a..a8e443102 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java @@ -53,11 +53,20 @@ public class GuacamoleProtocolVersion { */ public static final GuacamoleProtocolVersion VERSION_1_3_0 = new GuacamoleProtocolVersion(1, 3, 0); + /** + * Protocol version 1.5.0, which introduces the "msg" instruction, allowing + * the server to send message notifications that will be displayed on the + * client. The version also adds support for the "name" handshake + * instruction, allowing guacd to store the name of the user who is + * accessing the connection. + */ + public static final GuacamoleProtocolVersion VERSION_1_5_0 = new GuacamoleProtocolVersion(1, 5, 0); + /** * The most recent version of the Guacamole protocol at the time this * version of GuacamoleProtocolVersion was built. */ - public static final GuacamoleProtocolVersion LATEST = VERSION_1_3_0; + public static final GuacamoleProtocolVersion LATEST = VERSION_1_5_0; /** * A regular expression that matches the VERSION_X_Y_Z pattern, where diff --git a/guacamole/src/main/frontend/src/app/client/controllers/clientController.js b/guacamole/src/main/frontend/src/app/client/controllers/clientController.js index 89654b5d8..3a0df8c11 100644 --- a/guacamole/src/main/frontend/src/app/client/controllers/clientController.js +++ b/guacamole/src/main/frontend/src/app/client/controllers/clientController.js @@ -791,6 +791,24 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams ManagedClient.uploadFile($scope.filesystemMenuContents.client, files[i], $scope.filesystemMenuContents); }; + + /** + * Determines whether the attached client group has any associated client + * messages to display. + * + * @returns {Boolean} + * true if there are messages to display; otherwise false. + */ + $scope.hasMessages = function hasMessages() { + + // No client group means no messages + if (!$scope.clientGroup) + return false; + + // Otherwise, find messages within the clients in the group. + return _.findIndex($scope.clientGroup.clients, ManagedClient.hasMessages) !== -1; + + }; /** * Determines whether the attached client group has any associated file diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacClientMessage.js b/guacamole/src/main/frontend/src/app/client/directives/guacClientMessage.js new file mode 100644 index 000000000..5348179d0 --- /dev/null +++ b/guacamole/src/main/frontend/src/app/client/directives/guacClientMessage.js @@ -0,0 +1,87 @@ +/* + * 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. + */ + +/** + * Directive which displays a message for the client. + */ +angular.module('client').directive('guacClientMessage', [function guacClientMessage() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The message to display to the client. + * + * @type {!ManagedClientMessage} + */ + message : '=' + + }, + + templateUrl: 'app/client/templates/guacClientMessage.html', + + controller: ['$scope', '$injector', '$element', + function guacClientMessageController($scope, $injector, $element) { + + // Required types + const ManagedClientMessage = $injector.get('ManagedClientMessage'); + + // Required services + var translationStringService = $injector.get('translationStringService'); + + /** + * Uses the msgcode to retrieve the correct translation key for + * the client message. + * + * @returns {string} + */ + $scope.getMessageKey = function getMessageKey() { + + let msgString = "DEFAULT"; + if (Object.values(Guacamole.Client.Message).includes($scope.message.msgcode)) + msgString = Object.keys(Guacamole.Client.Message).find(key => Guacamole.Client.Message[key] === $scope.message.msgcode); + + return "CLIENT.MESSAGE_" + translationStringService.canonicalize(msgString); + }; + + /** + * Returns a set of key/value object pairs that represent the + * arguments provided as part of the message in the form + * "ARGS_0 = value". Guacamole's translation system relies on + * the arguments being available in this format in order to be able + * to handle substituting values for an arbitrary list of arguments. + * + * @returns {Object} + */ + $scope.getMessageArgs = function getMessageArgs() { + return $scope.message.args.reduce( + function(acc, value, index) { + acc[`ARGS_${index}`] = value; + return acc; + }, + {} + ); + }; + + }] + + }; +}]); diff --git a/guacamole/src/main/frontend/src/app/client/directives/guacMessageDialog.js b/guacamole/src/main/frontend/src/app/client/directives/guacMessageDialog.js new file mode 100644 index 000000000..16ca81d4a --- /dev/null +++ b/guacamole/src/main/frontend/src/app/client/directives/guacMessageDialog.js @@ -0,0 +1,76 @@ +/* + * 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. + */ + +/** + * Directive which displays all client messages. + */ +angular.module('client').directive('guacMessageDialog', [function guacMessageDialog() { + + return { + restrict: 'E', + replace: true, + scope: { + + /** + * The client group whose messages should be managed by this + * directive. + * + * @type ManagedClientGroup + */ + clientGroup : '=' + + }, + + templateUrl: 'app/client/templates/guacMessageDialog.html', + controller: ['$scope', '$injector', function guacMessageDialogController($scope, $injector) { + + // Required types + const ManagedClient = $injector.get('ManagedClient'); + const ManagedClientGroup = $injector.get('ManagedClientGroup'); + + /** + * Removes all messages. + */ + $scope.clearAllMessages = function clearAllMessages() { + + // Nothing to clear if no client group attached + if (!$scope.clientGroup) + return; + + // Remove each client's messages + $scope.clientGroup.clients.forEach(client => { + client.messages = []; + }); + + }; + + /** + * @borrows ManagedClientGroup.hasMultipleClients + */ + $scope.hasMultipleClients = ManagedClientGroup.hasMultipleClients; + + /** + * @borrows ManagedClient.hasMessages + */ + $scope.hasMessages = ManagedClient.hasMessages; + + }] + + }; +}]); diff --git a/guacamole/src/main/frontend/src/app/client/styles/client-message.css b/guacamole/src/main/frontend/src/app/client/styles/client-message.css new file mode 100644 index 000000000..21b905672 --- /dev/null +++ b/guacamole/src/main/frontend/src/app/client/styles/client-message.css @@ -0,0 +1,23 @@ +/* + * 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. + */ + +p.client-message-text { + margin: 5px; +} + diff --git a/guacamole/src/main/frontend/src/app/client/styles/message-dialog.css b/guacamole/src/main/frontend/src/app/client/styles/message-dialog.css new file mode 100644 index 000000000..2a90c672d --- /dev/null +++ b/guacamole/src/main/frontend/src/app/client/styles/message-dialog.css @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#message-dialog { + + position: absolute; + right: 0; + bottom: 0; + z-index: 20; + + font-size: 0.8em; + + width: 3in; + max-width: 100%; + max-height: 3in; + + background: white; + opacity: 0.75; + +} + +#message-dialog .message-dialog-box { + + /* IE10 */ + display: -ms-flexbox; + -ms-flex-align: stretch; + -ms-flex-direction: column; + + /* Ancient Mozilla */ + display: -moz-box; + -moz-box-align: stretch; + -moz-box-orient: vertical; + + /* Ancient WebKit */ + display: -webkit-box; + -webkit-box-align: stretch; + -webkit-box-orient: vertical; + + /* Old WebKit */ + display: -webkit-flex; + -webkit-align-items: stretch; + -webkit-flex-direction: column; + + /* W3C */ + display: flex; + align-items: stretch; + flex-direction: column; + + max-width: inherit; + max-height: inherit; + + border: 1px solid rgba(0, 0, 0, 0.5); + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); + +} + +#message-dialog .message-dialog-box .header { + -ms-flex: 0 0 auto; + -moz-box-flex: 0; + -webkit-box-flex: 0; + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + margin-bottom: 5px; +} + +#message-dialog .message-dialog-box .client-message-body { + + -ms-flex: 1 1 auto; + -moz-box-flex: 1; + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; + + overflow: auto; + +} + +/* + * Shrink maximum height if viewport is too small for default 3in dialog. + */ +@media all and (max-height: 3in) { + + #message-dialog { + max-height: 1.5in; + } + +} + +/* + * If viewport is too small for even the 1.5in dialog, fit all available space. + */ +@media all and (max-height: 1.5in) { + + #message-dialog { + height: 100%; + } + + #message-dialog .message-dialog-box { + position: absolute; + left: 0.5em; + top: 0.5em; + right: 0.5em; + bottom: 0.5em; + } + +} diff --git a/guacamole/src/main/frontend/src/app/client/templates/client.html b/guacamole/src/main/frontend/src/app/client/templates/client.html index d9c6684cb..7cf976c0a 100644 --- a/guacamole/src/main/frontend/src/app/client/templates/client.html +++ b/guacamole/src/main/frontend/src/app/client/templates/client.html @@ -44,6 +44,11 @@
{{'CLIENT.TEXT_CLIENT_STATUS_UNSTABLE' | translate}}
+ + +
+ +