mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-23 22:50:09 +03:00
(cherry picked from commit 9935822e52cf057e70053ea152c3421f77d8e79f)
This commit is contained in:
parent
2e7b2231db
commit
b4d71562a5
@ -15,30 +15,20 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
/*
|
||||
DEPENDENCIES
|
||||
*/
|
||||
|
||||
var BaseDialog = require('utils/dialogs/dialog');
|
||||
var TemplateHTML = require('hbs!./guac/html');
|
||||
var GuacController = require('utils/guacamole/controller');
|
||||
var Locale = require("utils/locale");
|
||||
var Notifier = require("utils/notifier");
|
||||
var Sunstone = require('sunstone');
|
||||
var GClient = require('utils/gclient');
|
||||
var Files = require('utils/files');
|
||||
|
||||
/*
|
||||
CONSTANTS
|
||||
*/
|
||||
var TemplateHTML = require('hbs!./guac/html');
|
||||
|
||||
var DIALOG_ID = require('./guac/dialogId');
|
||||
var TAB_ID = require('../tabId')
|
||||
|
||||
/*
|
||||
CONSTRUCTOR
|
||||
*/
|
||||
|
||||
function Dialog() {
|
||||
this.dialogId = DIALOG_ID;
|
||||
this.gClient = new GClient();
|
||||
|
||||
BaseDialog.call(this);
|
||||
};
|
||||
@ -54,47 +44,47 @@ define(function(require) {
|
||||
|
||||
return Dialog;
|
||||
|
||||
/*
|
||||
FUNCTION DEFINITIONS
|
||||
*/
|
||||
/* FUNCTION DEFINITIONS */
|
||||
|
||||
function _html() {
|
||||
return TemplateHTML({
|
||||
'dialogId': this.dialogId
|
||||
});
|
||||
return TemplateHTML({ 'dialogId': this.dialogId });
|
||||
}
|
||||
|
||||
function _setup(context) {
|
||||
var that = this;
|
||||
|
||||
$("#open_in_a_new_window_gclient", context).on("click", function() {
|
||||
var dialog = Sunstone.getDialog(DIALOG_ID);
|
||||
dialog.hide();
|
||||
});
|
||||
|
||||
$("#takeScreenshot_gclient", context).on("click", function() {
|
||||
var canvas = that.gClient.snapshot();
|
||||
Files.downloadImage('screenshot', canvas)
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _onShow() {
|
||||
this.gClient.connect(this.element);
|
||||
this.gClient.mouse(true);
|
||||
this.gClient.keyboard(true);
|
||||
var token = this.element.token;
|
||||
var info = this.element.info;
|
||||
|
||||
if (!token) {
|
||||
Notifier.notifyError(
|
||||
Locale.tr("The OpenNebula service for remote console is not running, please contact your administrator.")
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
this.controller = new GuacController();
|
||||
this.controller.setInformation(info);
|
||||
this.controller.setConnection(token);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _onClose() {
|
||||
this.gClient.disconnect();
|
||||
this.gClient.mouse(false);
|
||||
this.gClient.keyboard(false);
|
||||
this.controller.disconnect();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _setElement(element) {
|
||||
this.element = element
|
||||
this.element = element;
|
||||
}
|
||||
});
|
||||
|
@ -17,24 +17,47 @@
|
||||
<div id="{{dialogId}}" class="reveal full" data-reveal>
|
||||
<div class="row">
|
||||
<div class="large-12 columns">
|
||||
<h5 class="subheader" id="guacamole_dialog">
|
||||
<h5 class="subheader">
|
||||
<span id="guacamole-state"></span>
|
||||
<span id="guacamole-loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
</span>
|
||||
<span id="guacamole-buttons" class="right">
|
||||
<button class="button alert" id="sendCtrlAltDelButton_gclient">
|
||||
{{tr "Send CtrlAltDel"}}
|
||||
</button>
|
||||
<button class="button primary" id="oskButton_gclient">
|
||||
<i class="fas fa-keyboard fa-fw"></i>
|
||||
</button>
|
||||
<button class="button primary" id="mouseButton_gclient">
|
||||
<i class="fas fa-mouse-pointer fa-fw"></i>
|
||||
</button>
|
||||
<button class="button primary" id="takeScreenshot_gclient">
|
||||
<i class="fas fa-camera fa-fw" title="{{tr 'Take screenshot'}}"></i>
|
||||
</button>
|
||||
<button class="button secondary" data-close aria-label="{{tr "Close modal"}}" type="button" title="Close Guacamole">
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<i class="fas fa-times-circle fa-fw"></i>
|
||||
</button>
|
||||
</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="large-12 columns">
|
||||
<div class="guac_info"></div>
|
||||
<div class="guacamole_info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="guacamole-display" class="guacamole-display"></div>
|
||||
<div id="guacamole-main" class="guacamole-main">
|
||||
<div id="guacamole-display" class="guacamole-display"></div>
|
||||
</div>
|
||||
<!-- On-screen keyboard -->
|
||||
<div class="osk-container" id="osk-container">
|
||||
<div class="osk-container-header" id="osk-container-header">
|
||||
<div class="buttons">
|
||||
<button class="close" id="osk-close">x</button>
|
||||
</div>
|
||||
<div class="layouts">
|
||||
<select id="osk-qwerty"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="osk" id="osk"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -24,15 +24,36 @@ define(function(require) {
|
||||
var Locale = require("utils/locale");
|
||||
var UtilsConnection = require("utils/info-connection/utils");
|
||||
|
||||
/*
|
||||
CONSTANTS
|
||||
*/
|
||||
|
||||
var KEYS = {
|
||||
CTRL: 0xFF03,
|
||||
ALT: 0xFFE9,
|
||||
DELETE: 0xFFFF,
|
||||
};
|
||||
|
||||
var client = null;
|
||||
|
||||
/**
|
||||
* Whether the local, hardware mouse cursor is in use.
|
||||
* @type Boolean
|
||||
*/
|
||||
var localCursor = false;
|
||||
|
||||
/**
|
||||
* CONSTRUCTOR
|
||||
*/
|
||||
|
||||
function GClient() {
|
||||
this._display = null;
|
||||
this._client = null;
|
||||
this._mouse = null;
|
||||
this._keyboard = null;
|
||||
this._clientErrorText = Locale.tr("The OpenNebula service for remote console is not running, please contact your administrator.")
|
||||
|
||||
this._mouse = null;
|
||||
this._touchScreen = null;
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -41,8 +62,11 @@ define(function(require) {
|
||||
"connect": connect,
|
||||
"mouse": mouse,
|
||||
"keyboard": keyboard,
|
||||
"snapshot": getCanvas,
|
||||
"disconnect": disconnect,
|
||||
|
||||
/* ----- FUNCTIONS INTERACTION ----- */
|
||||
snapshot: getCanvas,
|
||||
sendCtrlAltDelete: sendCtrlAltDelete
|
||||
};
|
||||
|
||||
return GClient;
|
||||
@ -62,7 +86,9 @@ define(function(require) {
|
||||
Notifier.notifyError(this._clientErrorText)
|
||||
return null;
|
||||
}
|
||||
else setLoading(true);
|
||||
else {
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
var endpoint = Config.publicFireedgeEndpoint.split("//");
|
||||
var fireedge_protocol = endpoint[0];
|
||||
@ -74,30 +100,40 @@ define(function(require) {
|
||||
var wsprotocol = (fireedge_protocol == 'https:') ? 'wss:' : 'ws:';
|
||||
|
||||
var tunnel = new Guacamole.WebSocketTunnel(wsprotocol + '//' + host + ':' + port + '/fireedge/guacamole')
|
||||
var guac = this._client = new Guacamole.Client(tunnel);
|
||||
console.log('-<<<<<< pepe')
|
||||
client = new Guacamole.Client(tunnel);
|
||||
var display = client.getDisplay();
|
||||
|
||||
config.guac = client;
|
||||
|
||||
//var guacLayout = new Guacamole.OnScreenKeyboard.Layout()
|
||||
|
||||
//var keyboard = new Guacamole.OnScreenKeyboard(guacLayout);
|
||||
//$("#guacamole-keyboard").html(keyboard.getElement());
|
||||
//if (keyboard) keyboard.resize($("#guacamole-keyboard").offsetWidth);
|
||||
|
||||
var info_decode = UtilsConnection.decodeInfoConnection(response.info);
|
||||
UtilsConnection.printInfoConnection($('.guac_info'), info_decode)
|
||||
UtilsConnection.printInfoConnection($('.guac_info'), info_decode);
|
||||
|
||||
// Client display
|
||||
this._display = $("#guacamole-display");
|
||||
this._display.html(this._client.getDisplay().getElement());
|
||||
this._displayContainer = document.getElementById('guacamole-display');
|
||||
this._displayContainer.appendChild(display.getElement());
|
||||
|
||||
// client error handler
|
||||
guac.onerror = function() {
|
||||
Notifier.notifyError(that._clientErrorText)
|
||||
client.onerror = function() {
|
||||
Notifier.notifyError(clientErrorText)
|
||||
};
|
||||
|
||||
// websoket error handler
|
||||
// websocket error handler
|
||||
tunnel.onerror = function() {
|
||||
disconnect();
|
||||
Notifier.notifyError(that._clientErrorText)
|
||||
Notifier.notifyError(clientErrorText)
|
||||
|
||||
setStatus("Guacamole tunnel ERROR");
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
guac.onstatechange = function(state) {
|
||||
client.onstatechange = function(state) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
setStatus('Client IDLE');
|
||||
@ -112,11 +148,14 @@ define(function(require) {
|
||||
setLoading(true);
|
||||
break;
|
||||
case 3:
|
||||
$("#sendCtrlAltDelButton_gclient").on("click", function() {
|
||||
sendCtrlAltDelete();
|
||||
});
|
||||
setStatus('Client CONNECTED');
|
||||
setLoading(false);
|
||||
setTimeout(function() {
|
||||
rescale(that);
|
||||
guac.getDisplay().showCursor(false);
|
||||
display.showCursor(false);
|
||||
}, 100);
|
||||
break;
|
||||
case 4:
|
||||
@ -137,13 +176,15 @@ define(function(require) {
|
||||
// Connect
|
||||
var params = [
|
||||
'token=' + response.token,
|
||||
'width=' + this._display.width()
|
||||
'width=' + this._displayContainer.offsetWidth
|
||||
];
|
||||
|
||||
try {
|
||||
guac.connect(params.join('&'));
|
||||
var con = client.connect(params.join('&'));
|
||||
console.log('... connect', con)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
client = null;
|
||||
}
|
||||
|
||||
// Disconnect on close
|
||||
@ -163,60 +204,133 @@ define(function(require) {
|
||||
*/
|
||||
function mouse(enable = true) {
|
||||
var that = this;
|
||||
if (!this._client) return;
|
||||
var display = client.getDisplay();
|
||||
|
||||
if (!client || !display) return;
|
||||
|
||||
if (enable) {
|
||||
this._mouse = new Guacamole.Mouse(this._client.getDisplay().getElement());
|
||||
|
||||
// apply sendState function
|
||||
this._mouse.onmousedown =
|
||||
this._mouse.onmouseup = function(mouseState) {
|
||||
that._client.sendMouseState(mouseState);
|
||||
var displayElement = display.getElement();
|
||||
|
||||
var mouse = this._mouse = new Guacamole.Mouse(displayElement);
|
||||
|
||||
// Ensure focus is regained via mouseup/mousedown before forwarding event
|
||||
mouse.onmouseup =
|
||||
mouse.onmousedown = function(mouseState) {
|
||||
document.body.focus();
|
||||
handleMouseState(mouseState, that);
|
||||
};
|
||||
|
||||
this._mouse.onmousemove = function(mouseState) {
|
||||
mouseState.y = mouseState.y / that._client.getDisplay().getScale();
|
||||
mouseState.x = mouseState.x / that._client.getDisplay().getScale();
|
||||
that._client.sendMouseState(mouseState);
|
||||
// Forward mousemove events
|
||||
mouse.onmousemove = function(mouseState) {
|
||||
mouseState.y = mouseState.y / display.getScale();
|
||||
mouseState.x = mouseState.x / display.getScale();
|
||||
handleMouseState(mouseState, that);
|
||||
}
|
||||
|
||||
// Hide software cursor when mouse leaves display
|
||||
mouse.onmouseout = function() {
|
||||
if (!display) return;
|
||||
display.showCursor(false);
|
||||
};
|
||||
|
||||
display.oncursor = function setClientCursor(canvas, x, y) {
|
||||
localCursor = mouse.setCursor(canvas, x, y);
|
||||
}
|
||||
} else {
|
||||
this._mouse.onmouseup =
|
||||
this._mouse.onmousedown =
|
||||
this._mouse.onmousemove =
|
||||
this._mouse.onmouseout =
|
||||
this._mouse = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a mouse event originating from the user's actual mouse.
|
||||
* This differs from handleEmulatedMouseState() in that the
|
||||
* software mouse cursor must be shown only if the user's browser
|
||||
* does not support explicitly setting the hardware mouse cursor.
|
||||
*
|
||||
* @param {Guacamole.Mouse.State} mouseState
|
||||
* The current state of the user's hardware mouse.
|
||||
*/
|
||||
function handleMouseState(mouseState, thisContext) {
|
||||
var display = client.getDisplay();
|
||||
|
||||
// Do not attempt to handle mouse state changes if the client
|
||||
// or display are not yet available
|
||||
if (!client || !display)
|
||||
return;
|
||||
|
||||
// Send mouse state, show cursor if necessary
|
||||
display.showCursor(!localCursor);
|
||||
client.sendMouseState(mouseState, true);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
GuacamoleWrapper.keyboard
|
||||
- handles keyboard interaction
|
||||
*/
|
||||
function keyboard(enable = true) {
|
||||
var that = this;
|
||||
if (!this._client) return;
|
||||
var displayContainer = this._displayContainer;
|
||||
|
||||
if (!client || !displayContainer) return;
|
||||
|
||||
if (enable) {
|
||||
this._keyboard = new Guacamole.Keyboard(document);
|
||||
this._keyboard.onkeydown = function(keysym) {
|
||||
that._client.sendKeyEvent(1, keysym);
|
||||
var keyboard = this._keyboard = new Guacamole.Keyboard(document);
|
||||
var sink = new Guacamole.InputSink();
|
||||
|
||||
document.body.appendChild(sink.getElement());
|
||||
keyboard.listenTo(sink.getElement());
|
||||
|
||||
keyboard.onkeydown = function(keysym) {
|
||||
console.log(keysym)
|
||||
client.sendKeyEvent(1, keysym);
|
||||
}
|
||||
this._keyboard.onkeyup = function(keysym) {
|
||||
that._client.sendKeyEvent(0, keysym);
|
||||
|
||||
keyboard.onkeyup = function(keysym) {
|
||||
client.sendKeyEvent(0, keysym);
|
||||
}
|
||||
} else {
|
||||
this._keyboard.onkeydown = null;
|
||||
this._keyboard.onkeyup = null;
|
||||
this._keyboard.onkeydown =
|
||||
this._keyboard.onkeyup =
|
||||
this._keyboard = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
GuacamoleWrapper.sendCtrlAltDelete
|
||||
*/
|
||||
function sendCtrlAltDelete() {
|
||||
if (!client) return;
|
||||
|
||||
client.sendKeyEvent(1, KEYS.CTRL);
|
||||
client.sendKeyEvent(1, KEYS.ALT);
|
||||
client.sendKeyEvent(1, KEYS.DELETE);
|
||||
client.sendKeyEvent(0, KEYS.DELETE);
|
||||
client.sendKeyEvent(0, KEYS.ALT);
|
||||
client.sendKeyEvent(0, KEYS.CTRL);
|
||||
}
|
||||
|
||||
/*
|
||||
GuacamoleWrapper.getCanvas
|
||||
- shortcut for returning default guac layer (active tunnel viewport)
|
||||
*/
|
||||
function getCanvas() {
|
||||
return (this._client) ? this._client.getDisplay().getDefaultLayer().getCanvas() : false;
|
||||
return (client) ? client.getDisplay().getDefaultLayer().getCanvas() : false;
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
if (this._client) {
|
||||
this._client.disconnect();
|
||||
if (client) {
|
||||
client.disconnect();
|
||||
client = null;
|
||||
|
||||
console.log('->>> disconnect')
|
||||
|
||||
this._scale = 1;
|
||||
this._displayContainer.innerHTML = ""
|
||||
$(window).off('resize');
|
||||
setStatus()
|
||||
}
|
||||
@ -227,29 +341,30 @@ define(function(require) {
|
||||
* size and "auto-fit" setting.
|
||||
*/
|
||||
function rescale(thisContext) {
|
||||
var gclientDisplay = thisContext._client.getDisplay();
|
||||
var displayContainer = thisContext._displayContainer;
|
||||
var display = client.getDisplay();
|
||||
|
||||
//Get screen resolution.
|
||||
var origHeigth = Math.max(gclientDisplay.getHeight(), 1);
|
||||
var origWidth = Math.max(gclientDisplay.getWidth(), 1);
|
||||
// Get screen resolution.
|
||||
var origHeight = Math.max(display.getHeight(), 1);
|
||||
var origWidth = Math.max(display.getWidth(), 1);
|
||||
|
||||
var htmlWidth = thisContext._display.width();
|
||||
var htmlHeigth = thisContext._display.height();
|
||||
var htmlWidth = displayContainer.offsetWidth;
|
||||
var htmlHeight = displayContainer.offsetHeight;
|
||||
|
||||
var xscale = htmlWidth / origWidth;
|
||||
var yscale = htmlHeigth / origHeigth;
|
||||
var xScale = htmlWidth / origWidth;
|
||||
var yScale = htmlHeight / origHeight;
|
||||
|
||||
// This is done to handle both X and Y axis slacing
|
||||
var scale = Math.min(yscale, xscale);
|
||||
// This is done to handle both X and Y axis
|
||||
var scale = Math.min(yScale, xScale);
|
||||
|
||||
// Limit to 1
|
||||
scale = Math.min(scale, 1);
|
||||
|
||||
if (scale !== 0) {
|
||||
gclientDisplay.scale(scale);
|
||||
display.scale(scale);
|
||||
|
||||
// Set minimum height container display
|
||||
thisContext._display.css('min-height', gclientDisplay.getHeight());
|
||||
// Set minimum height to display container
|
||||
displayContainer.style['min-height'] = display.getHeight() + "px";
|
||||
}
|
||||
}
|
||||
});
|
191
src/sunstone/public/app/utils/guacamole/controller.js
Normal file
191
src/sunstone/public/app/utils/guacamole/controller.js
Normal file
@ -0,0 +1,191 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
require("guacamole-common-js")
|
||||
var ManagedClient = require("utils/guacamole/types/client");
|
||||
var ManagedClientState = require("utils/guacamole/types/client-state");
|
||||
var Utils = require("utils/guacamole/utils");
|
||||
var UtilsConnection = require("utils/info-connection/utils");
|
||||
|
||||
var GuacButtons = require("utils/guacamole/directives/guacButtons");
|
||||
var GuacClipboard = require("utils/guacamole/directives/guacClipboard");
|
||||
var GuacKeyboard = require("utils/guacamole/directives/guacKeyboard");
|
||||
var GuacMouse = require("utils/guacamole/directives/guacMouse");
|
||||
var GuacOsk = require("utils/guacamole/directives/guacOsk");
|
||||
|
||||
function GuacController() {
|
||||
var $guac = {};
|
||||
var $scope = {};
|
||||
var $elements = {
|
||||
main: document.getElementById('guacamole-main'),
|
||||
displayContainer: document.getElementById('guacamole-display'),
|
||||
osk: document.getElementById('osk'),
|
||||
closeOskButton: document.getElementById('osk-close'),
|
||||
|
||||
/* Buttons */
|
||||
sendCtrlAltDelButton: document.getElementById('sendCtrlAltDelButton_gclient'),
|
||||
mouseButton: document.getElementById('mouseButton_gclient'),
|
||||
screenshotButton: document.getElementById('takeScreenshot_gclient'),
|
||||
oskButton: document.getElementById('oskButton_gclient'),
|
||||
};
|
||||
|
||||
var throttleResizeFunction = Utils.throttle(containerResized, 250);
|
||||
window.addEventListener('resize', throttleResizeFunction);
|
||||
|
||||
this.disconnect = function() {
|
||||
if ($guac.client) $guac.client.disconnect();
|
||||
if ($guac.keyboard) GuacKeyboard.destroy();
|
||||
if ($guac.mouse) GuacMouse.destroy();
|
||||
if ($guac.osk) GuacOsk.destroy();
|
||||
|
||||
GuacButtons.destroy();
|
||||
GuacClipboard.destroy();
|
||||
window.removeEventListener('resize', throttleResizeFunction);
|
||||
$('#guacamole-state').text('');
|
||||
|
||||
$guac = {};
|
||||
$scope = {};
|
||||
}
|
||||
|
||||
this.setConnection = function(token) {
|
||||
var managedClient = ManagedClient.getInstance(token, undefined, $elements.displayContainer)
|
||||
|
||||
new GuacKeyboard($guac, $scope, $elements);
|
||||
new GuacMouse($guac, $scope, $elements);
|
||||
new GuacOsk($guac, $scope, $elements);
|
||||
new GuacButtons($guac, $scope, $elements);
|
||||
new GuacClipboard($guac, $scope, $elements);
|
||||
|
||||
// Remove any existing display
|
||||
$elements.displayContainer.innerHTML = "";
|
||||
|
||||
// Only proceed if a client is given
|
||||
if (!managedClient) return;
|
||||
$scope.client = managedClient;
|
||||
|
||||
// Get Guacamole client instance
|
||||
$guac.client = managedClient.client;
|
||||
|
||||
// Attach possibly new display
|
||||
$guac.display = $guac.client.getDisplay();
|
||||
$guac.display.scale($scope.client.clientProperties.scale);
|
||||
|
||||
// Add display element
|
||||
$scope.displayElement = $guac.display.getElement();
|
||||
$elements.displayContainer.appendChild($scope.displayElement);
|
||||
|
||||
// Do nothing when the display element is clicked on
|
||||
$guac.display.getElement().onclick = function(event) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
// Size of newly-attached client
|
||||
containerResized();
|
||||
|
||||
Utils.observe($scope.client.managedDisplay, 'size', (function() {
|
||||
updateDisplayScale();
|
||||
}).bind(this));
|
||||
|
||||
Utils.observe($scope.client.managedDisplay, 'cursor', (function(cursor) {
|
||||
if (cursor && $scope.localCursor) {
|
||||
$scope.localCursor = $guac.mouse.setCursor(cursor.canvas, cursor.x, cursor.y);
|
||||
}
|
||||
}).bind(this));
|
||||
|
||||
Utils.observe($scope, 'disableCursor', (function(disabled) {
|
||||
$elements.mouseButton.disabled = !!disabled;
|
||||
}).bind(this));
|
||||
|
||||
Utils.observe($scope.client.clientState, 'connectionState', (function(connectionState) {
|
||||
var isLoading = connectionState === ManagedClientState.ConnectionState.WAITING;
|
||||
|
||||
$('#guacamole-loading')[isLoading ? 'fadeIn' : 'fadeOut']('fast');
|
||||
$('#guacamole-state').text(connectionState).animate();
|
||||
}).bind(this));
|
||||
|
||||
Utils.observe($scope.client.clientProperties, 'scale', (function(scale) {
|
||||
scale = Math.max(scale, $scope.client.clientProperties.minScale);
|
||||
scale = Math.min(scale, $scope.client.clientProperties.maxScale);
|
||||
|
||||
// Apply scale if client attached
|
||||
if ($guac.display && scale !== 0) {
|
||||
$guac.display.scale(scale);
|
||||
$elements.displayContainer.style['min-height'] = $guac.display.getHeight() + "px";
|
||||
}
|
||||
|
||||
if (scale !== $scope.client.clientProperties.scale) {
|
||||
$scope.client.clientProperties.scale = scale;
|
||||
}
|
||||
}).bind(this));
|
||||
};
|
||||
|
||||
this.setInformation = function(information) {
|
||||
var info_decode = UtilsConnection.decodeInfoConnection(information);
|
||||
UtilsConnection.printInfoConnection($('.guacamole_info'), info_decode);
|
||||
}
|
||||
|
||||
function containerResized() {
|
||||
// Send new display size, if changed
|
||||
if ($guac.client && $guac.display) {
|
||||
var pixelDensity = window.devicePixelRatio || 1;
|
||||
var width = $elements.main.offsetWidth * pixelDensity;
|
||||
var height = $elements.main.offsetHeight * pixelDensity;
|
||||
|
||||
if ($guac.display.getWidth() !== width || $guac.display.getHeight() !== height) {
|
||||
$guac.client.sendSize(width, height);
|
||||
}
|
||||
|
||||
if ($guac.osk) {
|
||||
var MAX_OSK_WIDTH = 1000;
|
||||
|
||||
$guac.osk.resize(Math.min(MAX_OSK_WIDTH, width));
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplayScale();
|
||||
};
|
||||
|
||||
function updateDisplayScale() {
|
||||
if (!$guac.display) return;
|
||||
|
||||
// Calculate scale to fit screen
|
||||
$scope.client.clientProperties.minScale = Math.min(
|
||||
$elements.main.offsetWidth / Math.max($guac.display.getWidth(), 1),
|
||||
$elements.main.offsetHeight / Math.max($guac.display.getHeight(), 1)
|
||||
);
|
||||
|
||||
// Calculate appropriate maximum zoom level
|
||||
$scope.client.clientProperties.maxScale = Math.max($scope.client.clientProperties.minScale, 3);
|
||||
|
||||
// Clamp zoom level, maintain auto-fit
|
||||
if (
|
||||
$guac.display.getScale() < $scope.client.clientProperties.minScale ||
|
||||
$scope.client.clientProperties.autoFit
|
||||
) {
|
||||
$scope.client.clientProperties.scale = $scope.client.clientProperties.minScale;
|
||||
}
|
||||
else if ($guac.display.getScale() > $scope.client.clientProperties.maxScale) {
|
||||
$scope.client.clientProperties.scale = $scope.client.clientProperties.maxScale;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return GuacController;
|
||||
|
||||
});
|
@ -0,0 +1,73 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
var Files = require('utils/files');
|
||||
|
||||
function GuacButtons($guac, $scope, $elements) {
|
||||
$elements.screenshotButton.onclick = function() {
|
||||
if (!$guac.client) return;
|
||||
|
||||
var canvas = $guac.client.getDisplay().getDefaultLayer().getCanvas();
|
||||
Files.downloadImage('screenshot', canvas)
|
||||
};
|
||||
|
||||
$elements.sendCtrlAltDelButton.onclick = function() {
|
||||
if (!$guac.client || !$guac.osk) return;
|
||||
|
||||
var ctrlKey = $guac.osk.keys['LCtrl'][0].keysym;
|
||||
var altKey = $guac.osk.keys['LAlt'][0].keysym;
|
||||
var delKey = $guac.osk.keys['Del'][0].keysym;
|
||||
|
||||
$guac.client.sendKeyEvent(1, ctrlKey);
|
||||
$guac.client.sendKeyEvent(1, altKey);
|
||||
$guac.client.sendKeyEvent(1, delKey);
|
||||
$guac.client.sendKeyEvent(0, delKey);
|
||||
$guac.client.sendKeyEvent(0, altKey);
|
||||
$guac.client.sendKeyEvent(0, ctrlKey);
|
||||
};
|
||||
|
||||
$elements.oskButton.onclick =
|
||||
$elements.closeOskButton.onclick = function() {
|
||||
if (!$guac.client) return;
|
||||
|
||||
$('#osk-container').fadeToggle('fast');
|
||||
};
|
||||
|
||||
$elements.mouseButton.onclick = function() {
|
||||
// toggle disabled
|
||||
this.classList.toggle('disabled');
|
||||
|
||||
$scope.localCursor = $elements.mouseButton.classList.contains('disabled');
|
||||
};
|
||||
|
||||
GuacButtons.destroy = function() {
|
||||
// reset default state
|
||||
$('#osk-container').hide();
|
||||
$elements.mouseButton.classList.remove('disabled');
|
||||
|
||||
$elements.sendCtrlAltDelButton =
|
||||
$elements.screenshotButton.onclick =
|
||||
$elements.mouseButton.onclick =
|
||||
$elements.oskButton.onclick =
|
||||
$elements.closeOskButton.onclick = null;
|
||||
};
|
||||
}
|
||||
|
||||
return GuacButtons;
|
||||
|
||||
});
|
@ -0,0 +1,72 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
var ClipboardData = require("utils/guacamole/types/clipboard-data");
|
||||
var ManagedClient = require("utils/guacamole/types/client");
|
||||
|
||||
function GuacClipboard($guac, $scope, $elements) {
|
||||
window.addEventListener('load', checkClipboard, true);
|
||||
window.addEventListener('copy', checkClipboard);
|
||||
window.addEventListener('cut', checkClipboard);
|
||||
window.addEventListener('focus', focusGained, true);
|
||||
|
||||
function focusGained(event) {
|
||||
// Only recheck clipboard if it's the window itself that gained focus
|
||||
if (event.target === window) checkClipboard();
|
||||
}
|
||||
|
||||
function checkClipboard() {
|
||||
$.when(getLocalClipboard()).done(function(data) {
|
||||
if ($guac.client) {
|
||||
ManagedClient.setClipboard($scope.client, data);
|
||||
$scope.client.clipboardData = data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getLocalClipboard() {
|
||||
var deferred = $.Deferred();
|
||||
|
||||
try {
|
||||
// Attempt to read the clipboard using the Asynchronous Clipboard
|
||||
// API, if it's available
|
||||
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||
navigator.clipboard.readText().then(function(text) {
|
||||
deferred.resolve(
|
||||
new ClipboardData({ type: 'text/plain', data: text })
|
||||
);
|
||||
}, deferred.reject);
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
}
|
||||
// Ignore any hard failures to use Asynchronous Clipboard API
|
||||
catch (ignore) { console.error(ignore) }
|
||||
}
|
||||
|
||||
GuacClipboard.destroy = function() {
|
||||
window.removeEventListener('load', checkClipboard, true);
|
||||
window.removeEventListener('copy', checkClipboard);
|
||||
window.removeEventListener('cut', checkClipboard);
|
||||
window.removeEventListener('focus', focusGained, true);
|
||||
};
|
||||
}
|
||||
|
||||
return GuacClipboard;
|
||||
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
require("guacamole-common-js")
|
||||
|
||||
function GuacKeyboard($guac, $scope, $elements) {
|
||||
// Add default destination for input events
|
||||
var sink = $guac.sink = new Guacamole.InputSink();
|
||||
$elements.displayContainer.appendChild(sink.getElement());
|
||||
|
||||
// Create event listeners at the global level
|
||||
var keyboard = $guac.keyboard = new Guacamole.Keyboard(document);
|
||||
keyboard.listenTo(sink.getElement());
|
||||
|
||||
keyboard.onkeydown = function(keysym) {
|
||||
$guac.client.sendKeyEvent(1, keysym);
|
||||
};
|
||||
|
||||
keyboard.onkeyup = function(keysym) {
|
||||
$guac.client.sendKeyEvent(0, keysym);
|
||||
};
|
||||
|
||||
// Release all keys when window loses focus
|
||||
window.addEventListener('blur', keyboard.reset);
|
||||
|
||||
GuacKeyboard.destroy = function() {
|
||||
window.removeEventListener('blur', keyboard.reset);
|
||||
|
||||
$guac.sink =
|
||||
$guac.keyboard =
|
||||
$guac.keyboard.onkeydown =
|
||||
$guac.keyboard.onkeyup = null;
|
||||
}
|
||||
}
|
||||
|
||||
return GuacKeyboard;
|
||||
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
require("guacamole-common-js")
|
||||
|
||||
function GuacMouse($guac, $scope, $elements) {
|
||||
var mouse = $guac.mouse = new Guacamole.Mouse($elements.displayContainer);
|
||||
|
||||
// Ensure focus is regained via mousedown before forwarding event
|
||||
mouse.onmouseup =
|
||||
mouse.onmousedown = function(mouseState) {
|
||||
$elements.displayContainer.focus();
|
||||
handleMouseState(mouseState);
|
||||
};
|
||||
|
||||
// Forward mousemove events untouched
|
||||
mouse.onmousemove = function(mouseState) {
|
||||
mouseState.y = mouseState.y / $guac.display.getScale();
|
||||
mouseState.x = mouseState.x / $guac.display.getScale();
|
||||
handleMouseState(mouseState);
|
||||
}
|
||||
|
||||
// Hide software cursor when mouse leaves display
|
||||
mouse.onmouseout = function() {
|
||||
if (!$guac.display) return;
|
||||
|
||||
$guac.display.showCursor(false);
|
||||
};
|
||||
|
||||
function handleMouseState(mouseState) {
|
||||
// Do not attempt to handle mouse state changes if the client
|
||||
// or display are not yet available
|
||||
if (!$guac.client || !$guac.display) return;
|
||||
|
||||
// Send mouse state, show cursor if necessary
|
||||
$guac.display.showCursor(!$scope.localCursor);
|
||||
$guac.client.sendMouseState(mouseState);
|
||||
};
|
||||
|
||||
GuacMouse.destroy = function() {
|
||||
$guac.mouse =
|
||||
$guac.mouse.onmouseup =
|
||||
$guac.mouse.onmousedown =
|
||||
$guac.mouse.onmouseout = null;
|
||||
};
|
||||
}
|
||||
|
||||
return GuacMouse;
|
||||
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
require("guacamole-common-js")
|
||||
var enUsQwerty = require('utils/guacamole/layouts/en-us-qwerty');
|
||||
var esEsQwerty = require('utils/guacamole/layouts/es-es-qwerty');
|
||||
|
||||
var DEFAULT_LAYOUT = enUsQwerty.language;
|
||||
|
||||
function GuacOsk($guac, $scope, $elements) {
|
||||
loadLayouts();
|
||||
changeLayout(DEFAULT_LAYOUT);
|
||||
|
||||
$('#osk-container').draggable();
|
||||
|
||||
function loadLayouts() {
|
||||
$('#osk-qwerty').empty();
|
||||
|
||||
var enUsLayout = new Option(enUsQwerty.language, enUsQwerty.language);
|
||||
$('#osk-qwerty').append(enUsLayout);
|
||||
|
||||
var esEsLayout = new Option(esEsQwerty.language, esEsQwerty.language);
|
||||
$('#osk-qwerty').append(esEsLayout);
|
||||
|
||||
$('#osk-qwerty').off().on('change', function() {
|
||||
changeLayout(this.value);
|
||||
})
|
||||
};
|
||||
|
||||
function changeLayout(newLayout) {
|
||||
var layout = newLayout === enUsQwerty.language ? enUsQwerty : esEsQwerty;
|
||||
|
||||
var osk = $guac.osk = new Guacamole.OnScreenKeyboard(layout);
|
||||
|
||||
$elements.osk.innerHTML = "";
|
||||
$elements.osk.appendChild(osk.getElement());
|
||||
|
||||
osk.onkeydown = function(keysym) {
|
||||
$guac.client.sendKeyEvent(1, keysym);
|
||||
};
|
||||
|
||||
osk.onkeyup = function(keysym) {
|
||||
$guac.client.sendKeyEvent(0, keysym);
|
||||
};
|
||||
|
||||
var pixelDensity = window.devicePixelRatio || 1;
|
||||
var width = $elements.main.offsetWidth * pixelDensity;
|
||||
var MAX_OSK_WIDTH = 1000;
|
||||
$guac.osk.resize(Math.min(MAX_OSK_WIDTH, width));
|
||||
}
|
||||
|
||||
GuacOsk.destroy = function() {
|
||||
$guac.osk =
|
||||
$guac.osk.onkeydown =
|
||||
$guac.osk.onkeyup = null;
|
||||
};
|
||||
}
|
||||
|
||||
return GuacOsk;
|
||||
|
||||
});
|
1164
src/sunstone/public/app/utils/guacamole/layouts/en-us-qwerty.js
Normal file
1164
src/sunstone/public/app/utils/guacamole/layouts/en-us-qwerty.js
Normal file
File diff suppressed because it is too large
Load Diff
1376
src/sunstone/public/app/utils/guacamole/layouts/es-es-qwerty.js
Normal file
1376
src/sunstone/public/app/utils/guacamole/layouts/es-es-qwerty.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,91 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function() {
|
||||
|
||||
/**
|
||||
* Object used for interacting with a guacClient directive.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ClientProperties|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new ClientProperties.
|
||||
*/
|
||||
function ClientProperties(template = {}) {
|
||||
/**
|
||||
* Whether the display should be scaled automatically to fit within the
|
||||
* available space.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.autoFit = template.autoFit || true;
|
||||
|
||||
/**
|
||||
* The current scale. If autoFit is true, the effect of setting this
|
||||
* value is undefined.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.scale = template.scale || 1;
|
||||
|
||||
/**
|
||||
* The minimum scale value.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.minScale = template.minScale || 1;
|
||||
|
||||
/**
|
||||
* The maximum scale value.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.maxScale = template.maxScale || 3;
|
||||
|
||||
/**
|
||||
* Whether or not the client should listen to keyboard events.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.keyboardEnabled = template.keyboardEnabled || true;
|
||||
|
||||
/**
|
||||
* Whether translation of touch to mouse events should emulate an
|
||||
* absolute pointer device, or a relative pointer device.
|
||||
*
|
||||
* @type Boolean
|
||||
*/
|
||||
this.emulateAbsoluteMouse = template.emulateAbsoluteMouse || true;
|
||||
|
||||
/**
|
||||
* The relative Y coordinate of the scroll offset of the display within
|
||||
* the client element.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.scrollTop = template.scrollTop || 0;
|
||||
|
||||
/**
|
||||
* The relative X coordinate of the scroll offset of the display within
|
||||
* the client element.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.scrollLeft = template.scrollLeft || 0;
|
||||
};
|
||||
|
||||
return ClientProperties;
|
||||
|
||||
});
|
127
src/sunstone/public/app/utils/guacamole/types/client-state.js
Normal file
127
src/sunstone/public/app/utils/guacamole/types/client-state.js
Normal file
@ -0,0 +1,127 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
require("guacamole-common-js")
|
||||
|
||||
/**
|
||||
* Object which represents the state of a Guacamole client and its tunnel,
|
||||
* including any error conditions.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedClientState|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedClientState.
|
||||
*/
|
||||
function ManagedClientState(template = {}) {
|
||||
/**
|
||||
* The current connection state. Valid values are described by
|
||||
* ManagedClientState.ConnectionState.
|
||||
*
|
||||
* @type {String}
|
||||
* @default ManagedClientState.ConnectionState.IDLE
|
||||
*/
|
||||
this.connectionState = template.connectionState || ManagedClientState.ConnectionState.IDLE;
|
||||
|
||||
/**
|
||||
* Whether the network connection used by the tunnel seems unstable. If
|
||||
* the network connection is unstable, the remote desktop connection
|
||||
* may perform poorly or disconnect.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @default false
|
||||
*/
|
||||
this.tunnelUnstable = template.tunnelUnstable || false;
|
||||
|
||||
/**
|
||||
* The status code of the current error condition, if connectionState
|
||||
* is CLIENT_ERROR or TUNNEL_ERROR. For all other connectionState
|
||||
* values, this will be @link{Guacamole.Status.Code.SUCCESS}.
|
||||
*
|
||||
* @type {Number}
|
||||
* @default Guacamole.Status.Code.SUCCESS
|
||||
*/
|
||||
this.statusCode = template.statusCode || Guacamole.Status.Code.SUCCESS;
|
||||
};
|
||||
|
||||
/**
|
||||
* Valid connection state strings. Each state string is associated with a
|
||||
* specific state of a Guacamole connection.
|
||||
*/
|
||||
ManagedClientState.ConnectionState = {
|
||||
IDLE : "IDLE",
|
||||
CONNECTING : "CONNECTING",
|
||||
WAITING : "WAITING",
|
||||
CONNECTED : "CONNECTED",
|
||||
DISCONNECTED : "DISCONNECTED",
|
||||
CLIENT_ERROR : "CLIENT_ERROR",
|
||||
TUNNEL_ERROR : "TUNNEL_ERROR"
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the current client state and, if given, the associated status code.
|
||||
* If an error is already represented, this function has no effect. If the
|
||||
* client state was previously marked as unstable, that flag is implicitly
|
||||
* cleared.
|
||||
*
|
||||
* @param {ManagedClientState} clientState The ManagedClientState to update.
|
||||
*
|
||||
* @param {String} connectionState
|
||||
* The connection state to assign to the given ManagedClientState, as
|
||||
* listed within ManagedClientState.ConnectionState.
|
||||
*
|
||||
* @param {Number} [statusCode]
|
||||
* The status code to assign to the given ManagedClientState, if any,
|
||||
* as listed within Guacamole.Status.Code. If no status code is
|
||||
* specified, the status code of the ManagedClientState is not touched.
|
||||
*/
|
||||
ManagedClientState.setConnectionState = function(clientState, connectionState, statusCode) {
|
||||
// Do not set state after an error is registered
|
||||
if (
|
||||
clientState.connectionState === ManagedClientState.ConnectionState.TUNNEL_ERROR ||
|
||||
clientState.connectionState === ManagedClientState.ConnectionState.CLIENT_ERROR
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update connection state
|
||||
clientState.connectionState = connectionState;
|
||||
clientState.tunnelUnstable = false;
|
||||
|
||||
// Set status code, if given
|
||||
if (statusCode) {
|
||||
clientState.statusCode = statusCode;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the given client state, setting whether the underlying tunnel
|
||||
* is currently unstable. An unstable tunnel is not necessarily
|
||||
* disconnected, but appears to be misbehaving and may be disconnected.
|
||||
*
|
||||
* @param {ManagedClientState} clientState The ManagedClientState to update.
|
||||
*
|
||||
* @param {Boolean} unstable
|
||||
* Whether the underlying tunnel of the connection currently appears unstable.
|
||||
*/
|
||||
ManagedClientState.setTunnelUnstable = function(clientState, unstable) {
|
||||
clientState.tunnelUnstable = unstable;
|
||||
};
|
||||
|
||||
return ManagedClientState;
|
||||
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function() {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function ManagedClientThumbnail(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;
|
||||
|
||||
});
|
543
src/sunstone/public/app/utils/guacamole/types/client.js
Normal file
543
src/sunstone/public/app/utils/guacamole/types/client.js
Normal file
@ -0,0 +1,543 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function(require) {
|
||||
|
||||
require("guacamole-common-js")
|
||||
var ClientProperties = require("utils/guacamole/types/client-properties");
|
||||
var ClipboardData = require("utils/guacamole/types/clipboard-data");
|
||||
var Config = require("sunstone-config");
|
||||
var ManagedClientState = require("utils/guacamole/types/client-state");
|
||||
var ManagedClientThumbnail = require("utils/guacamole/types/client-thumbnail");
|
||||
var ManagedDisplay = require("utils/guacamole/types/display");
|
||||
|
||||
/**
|
||||
* 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
|
||||
* from different client views.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedClient|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new ManagedClient.
|
||||
*/
|
||||
function ManagedClient(template = {}) {
|
||||
/**
|
||||
* The token of the connection associated with this client.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.token = template.token;
|
||||
|
||||
/**
|
||||
* The time that the connection was last brought to the foreground of
|
||||
* the current tab, as the number of milliseconds elapsed since
|
||||
* midnight of January 1, 1970 UTC. If the connection has not yet been
|
||||
* viewed, this will be 0.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.lastUsed = template.lastUsed || 0;
|
||||
|
||||
/**
|
||||
* The actual underlying Guacamole client.
|
||||
*
|
||||
* @type Guacamole.Client
|
||||
*/
|
||||
this.client = template.client;
|
||||
|
||||
/**
|
||||
* The tunnel being used by the underlying Guacamole client.
|
||||
*
|
||||
* @type Guacamole.Tunnel
|
||||
*/
|
||||
this.tunnel = template.tunnel;
|
||||
|
||||
/**
|
||||
* The display associated with the underlying Guacamole client.
|
||||
*
|
||||
* @type ManagedDisplay
|
||||
*/
|
||||
this.managedDisplay = template.managedDisplay;
|
||||
|
||||
/**
|
||||
* The name returned associated with the connection or connection
|
||||
* group in use.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.name = template.name;
|
||||
|
||||
/**
|
||||
* The title which should be displayed as the page title for this
|
||||
* client.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.title = template.title;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @type ClipboardData
|
||||
*/
|
||||
this.clipboardData = template.clipboardData || new ClipboardData({
|
||||
type : 'text/plain',
|
||||
data : ''
|
||||
});
|
||||
|
||||
/**
|
||||
* The current state of all parameters requested by the server via
|
||||
* "required" instructions, where each object key is the name of a
|
||||
* requested parameter and each value is the current value entered by
|
||||
* the user or null if no parameters are currently being requested.
|
||||
*
|
||||
* @type Object.<String, String>
|
||||
*/
|
||||
this.requiredParameters = null;
|
||||
|
||||
/**
|
||||
* All uploaded files. As files are uploaded, their progress can be
|
||||
* observed through the elements of this array. It is intended that
|
||||
* this array be manipulated externally as needed.
|
||||
*
|
||||
* @type ManagedFileUpload[]
|
||||
*/
|
||||
this.uploads = template.uploads || [];
|
||||
|
||||
/**
|
||||
* All currently-exposed filesystems. When the Guacamole server exposes
|
||||
* a filesystem object, that object will be made available as a
|
||||
* ManagedFilesystem within this array.
|
||||
*
|
||||
* @type ManagedFilesystem[]
|
||||
*/
|
||||
this.filesystems = template.filesystems || [];
|
||||
|
||||
/**
|
||||
* All available share links generated for the this ManagedClient via
|
||||
* ManagedClient.createShareLink(). Each resulting share link is stored
|
||||
* under the identifier of its corresponding SharingProfile.
|
||||
*
|
||||
* @type Object.<String, ManagedShareLink>
|
||||
*/
|
||||
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.).
|
||||
*
|
||||
* @type ManagedClientState
|
||||
*/
|
||||
this.clientState = template.clientState || new ManagedClientState();
|
||||
|
||||
/**
|
||||
* Properties associated with the display and behavior of the Guacamole
|
||||
* client.
|
||||
*
|
||||
* @type ClientProperties
|
||||
*/
|
||||
this.clientProperties = template.clientProperties || new ClientProperties();
|
||||
|
||||
/**
|
||||
* All editable arguments (connection parameters), stored by their
|
||||
* names. Arguments will only be present within this set if their
|
||||
* current values have been exposed by the server via an inbound "argv"
|
||||
* stream and the server has confirmed that the value may be changed
|
||||
* through a successful "ack" to an outbound "argv" stream.
|
||||
*
|
||||
* @type {Object.<String, ManagedArgument>}
|
||||
*/
|
||||
this.arguments = template.arguments || {};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The mimetype of audio data to be sent along the Guacamole connection if
|
||||
* audio input is supported.
|
||||
*
|
||||
* @constant
|
||||
* @type String
|
||||
*/
|
||||
ManagedClient.AUDIO_INPUT_MIMETYPE = 'audio/L16;rate=44100,channels=2';
|
||||
|
||||
/**
|
||||
* Returns a promise which resolves with the string of connection
|
||||
* parameters to be passed to the Guacamole client during connection. This
|
||||
* string generally contains the desired connection ID, display resolution,
|
||||
* and supported audio/video/image formats. The returned promise is
|
||||
* guaranteed to resolve successfully.
|
||||
*
|
||||
* @param {String} token The identifier representing the connection or group to connect to.
|
||||
* @param {String[]} [connectionParameters] Any additional HTTP parameters to pass while connecting.
|
||||
* @param {Element} [display] Element where the connection will be displayed.
|
||||
*
|
||||
* @returns {String} A string of connection parameters to be passed to the Guacamole client.
|
||||
*/
|
||||
function getConnectString(token, connectionParameters, display = window) {
|
||||
// Calculate optimal width/height for display
|
||||
var pixel_density = window.devicePixelRatio || 1;
|
||||
var optimal_width = display.innerWidth * pixel_density;
|
||||
var optimal_height = display.innerHeight * pixel_density;
|
||||
var optimal_dpi = pixel_density * 96;
|
||||
|
||||
// Build base connect string
|
||||
var connectString = [
|
||||
"token=" + encodeURIComponent(token),
|
||||
"width=" + Math.floor(optimal_width),
|
||||
"height=" + Math.floor(optimal_height),
|
||||
"dpi=" + Math.floor(optimal_dpi)
|
||||
];
|
||||
|
||||
connectionParameters && connectString.concat(connectionParameters);
|
||||
|
||||
return connectString.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* an audio stream will be created which will remain open until the remote
|
||||
* desktop requests that it be closed. If the audio stream is successfully
|
||||
* created but is later closed, a new audio stream will automatically be
|
||||
* established to take its place. The mimetype used for all audio streams
|
||||
* produced by this function is defined by
|
||||
* ManagedClient.AUDIO_INPUT_MIMETYPE.
|
||||
*
|
||||
* @param {Guacamole.Client} client
|
||||
* The Guacamole.Client for which the audio stream is being requested.
|
||||
*/
|
||||
var requestAudioStream = function requestAudioStream(client) {
|
||||
// Create new audio stream, associating it with an AudioRecorder
|
||||
var stream = client.createAudioStream(ManagedClient.AUDIO_INPUT_MIMETYPE);
|
||||
var recorder = Guacamole.AudioRecorder.getInstance(stream, ManagedClient.AUDIO_INPUT_MIMETYPE);
|
||||
|
||||
// If creation of the AudioRecorder failed, simply end the stream
|
||||
if (!recorder) {
|
||||
stream.sendEnd();
|
||||
}
|
||||
// Otherwise, ensure that another audio stream is created after this
|
||||
// audio stream is closed
|
||||
else {
|
||||
recorder.onclose = requestAudioStream.bind(this, client);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new ManagedClient, connecting it to the specified connection
|
||||
* or group.
|
||||
*
|
||||
* @param {String} token The token of the connection.
|
||||
* @param {String[]} [connectionParameters] Any additional HTTP parameters to pass while connecting.
|
||||
* @param {Element} [display] Element where the connection will be displayed.
|
||||
*
|
||||
* @returns {ManagedClient}
|
||||
* A new ManagedClient instance which is connected to the connection or
|
||||
* connection group having the given ID.
|
||||
*/
|
||||
ManagedClient.getInstance = function getInstance(token, connectionParameters, display = window) {
|
||||
var endpoint = new URL(Config.publicFireedgeEndpoint);
|
||||
|
||||
var websocketProtocol = endpoint.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
var fireedgeWebsocket = websocketProtocol + '//' + endpoint.host + '/fireedge/guacamole'
|
||||
|
||||
// Get new websocket tunnel instance
|
||||
var tunnel = new Guacamole.WebSocketTunnel(fireedgeWebsocket);
|
||||
|
||||
// Get new client instance
|
||||
var client = new Guacamole.Client(tunnel);
|
||||
|
||||
// Associate new managed client with new client and tunnel
|
||||
var managedClient = new ManagedClient({
|
||||
id : token,
|
||||
client : client,
|
||||
tunnel : tunnel
|
||||
});
|
||||
|
||||
tunnel.onerror = function tunnelError(status) {
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.TUNNEL_ERROR,
|
||||
status.code
|
||||
);
|
||||
};
|
||||
|
||||
// Update connection state as tunnel state changes
|
||||
tunnel.onstatechange = function tunnelStateChanged(state) {
|
||||
switch (state) {
|
||||
// Connection is being established
|
||||
case Guacamole.Tunnel.State.CONNECTING:
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.CONNECTING
|
||||
);
|
||||
break;
|
||||
|
||||
// Connection is established / no longer unstable
|
||||
case Guacamole.Tunnel.State.OPEN:
|
||||
ManagedClientState.setTunnelUnstable(managedClient.clientState, false);
|
||||
break;
|
||||
|
||||
// Connection is established but misbehaving
|
||||
case Guacamole.Tunnel.State.UNSTABLE:
|
||||
ManagedClientState.setTunnelUnstable(managedClient.clientState, true);
|
||||
break;
|
||||
|
||||
// Connection has closed
|
||||
case Guacamole.Tunnel.State.CLOSED:
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.DISCONNECTED
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Update connection state as client state changes
|
||||
client.onstatechange = function clientStateChanged(clientState) {
|
||||
switch (clientState) {
|
||||
// Idle
|
||||
case 0:
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.IDLE
|
||||
);
|
||||
break;
|
||||
|
||||
// Ignore "connecting" state
|
||||
case 1: // Connecting
|
||||
break;
|
||||
|
||||
// Connected + waiting
|
||||
case 2:
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.WAITING
|
||||
);
|
||||
break;
|
||||
|
||||
// Connected
|
||||
case 3:
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.CONNECTED
|
||||
);
|
||||
|
||||
// Send any clipboard data already provided
|
||||
if (managedClient.clipboardData) {
|
||||
ManagedClient.setClipboard(managedClient, managedClient.clipboardData);
|
||||
}
|
||||
|
||||
// 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
|
||||
//ManagedClient.updateThumbnail(managedClient);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Disconnect and update status when the client receives an error
|
||||
client.onerror = function clientError(status) {
|
||||
// Disconnect, if connected
|
||||
client.disconnect();
|
||||
|
||||
// Update state
|
||||
ManagedClientState.setConnectionState(
|
||||
managedClient.clientState,
|
||||
ManagedClientState.ConnectionState.CLIENT_ERROR,
|
||||
status.code
|
||||
);
|
||||
};
|
||||
|
||||
// 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) {
|
||||
//ManagedClient.updateThumbnail(managedClient);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle any received clipboard data
|
||||
client.onclipboard = function clientClipboardReceived(stream, mimetype) {
|
||||
var reader;
|
||||
|
||||
// If the received data is text, read it as a simple string
|
||||
if (/^text\//.exec(mimetype)) {
|
||||
reader = new Guacamole.StringReader(stream);
|
||||
|
||||
// Assemble received data into a single string
|
||||
var data = '';
|
||||
reader.ontext = function textReceived(text) {
|
||||
data += text;
|
||||
};
|
||||
|
||||
// Set clipboard contents once stream is finished
|
||||
reader.onend = function textComplete() {
|
||||
managedClient.clipboardData = new ClipboardData({
|
||||
type : mimetype,
|
||||
data : data
|
||||
});
|
||||
};
|
||||
}
|
||||
// Otherwise read the clipboard data as a Blob
|
||||
else {
|
||||
reader = new Guacamole.BlobReader(stream, mimetype);
|
||||
reader.onend = function blobComplete() {
|
||||
managedClient.clipboardData = new ClipboardData({
|
||||
type : mimetype,
|
||||
data : reader.getBlob()
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Update title when a "name" instruction is received
|
||||
client.onname = function clientNameReceived(name) {
|
||||
managedClient.title = name;
|
||||
};
|
||||
|
||||
// Manage the client display
|
||||
managedClient.managedDisplay = ManagedDisplay.getInstance(client.getDisplay());
|
||||
|
||||
// Connect the Guacamole client
|
||||
var connectString = getConnectString(token, connectionParameters, display);
|
||||
client.connect(connectString);
|
||||
|
||||
return managedClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the given clipboard data over the given Guacamole client, setting
|
||||
* the contents of the remote clipboard to the data provided.
|
||||
*
|
||||
* @param {ManagedClient} managedClient
|
||||
* The ManagedClient over which the given clipboard data is to be sent.
|
||||
*
|
||||
* @param {ClipboardData} data
|
||||
* The clipboard data to send.
|
||||
*/
|
||||
ManagedClient.setClipboard = function setClipboard(managedClient, data) {
|
||||
var writer;
|
||||
|
||||
// Create stream with proper mimetype
|
||||
var stream = managedClient.client.createClipboardStream(data.type);
|
||||
|
||||
// Send data as a string if it is stored as a string
|
||||
if (typeof data.data === 'string') {
|
||||
writer = new Guacamole.StringWriter(stream);
|
||||
|
||||
for (var i = 0; i < data.data.length; i += 4096) {
|
||||
writer.sendText(data.data.substring(i, i + 4096));
|
||||
}
|
||||
|
||||
writer.sendEnd();
|
||||
}
|
||||
// Otherwise, assume the data is a File/Blob
|
||||
else {
|
||||
// Write File/Blob asynchronously
|
||||
writer = new Guacamole.BlobWriter(stream);
|
||||
writer.oncomplete = function clipboardSent() {
|
||||
writer.sendEnd();
|
||||
};
|
||||
|
||||
// Begin sending data
|
||||
writer.sendBlob(data.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.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;
|
||||
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function() {
|
||||
|
||||
/**
|
||||
* Arbitrary data which can be contained by the clipboard.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ClipboardData|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ClipboardData.
|
||||
*/
|
||||
function ClipboardData(template = {}) {
|
||||
/**
|
||||
* The mimetype of the data currently stored within the clipboard.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
this.type = template.type || 'text/plain';
|
||||
|
||||
/**
|
||||
* The data currently stored within the clipboard. Depending on the
|
||||
* nature of the stored data, this may be either a String, a Blob, or a
|
||||
* File.
|
||||
*
|
||||
* @type String|Blob|File
|
||||
*/
|
||||
this.data = template.data || '';
|
||||
};
|
||||
|
||||
return ClipboardData;
|
||||
|
||||
});
|
145
src/sunstone/public/app/utils/guacamole/types/display.js
Normal file
145
src/sunstone/public/app/utils/guacamole/types/display.js
Normal file
@ -0,0 +1,145 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function() {
|
||||
|
||||
/**
|
||||
* Object which serves as a surrogate interface, encapsulating a Guacamole
|
||||
* display while it is active, allowing it to be detached and reattached
|
||||
* from different client views.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedDisplay|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedDisplay.
|
||||
*/
|
||||
function ManagedDisplay(template = {}) {
|
||||
/**
|
||||
* The underlying Guacamole display.
|
||||
*
|
||||
* @type Guacamole.Display
|
||||
*/
|
||||
this.display = template.display;
|
||||
|
||||
/**
|
||||
* The current size of the Guacamole display.
|
||||
*
|
||||
* @type ManagedDisplay.Dimensions
|
||||
*/
|
||||
this.size = new ManagedDisplay.Dimensions(template.size);
|
||||
|
||||
/**
|
||||
* The current mouse cursor, if any.
|
||||
*
|
||||
* @type ManagedDisplay.Cursor
|
||||
*/
|
||||
this.cursor = template.cursor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Object which represents the size of the Guacamole display.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedDisplay.Dimensions|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedDisplay.Dimensions.
|
||||
*/
|
||||
ManagedDisplay.Dimensions = function Dimensions(template = {}) {
|
||||
/**
|
||||
* The current width of the Guacamole display, in pixels.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.width = template.width || 0;
|
||||
|
||||
/**
|
||||
* The current width of the Guacamole display, in pixels.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.height = template.height || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Object which represents a mouse cursor used by the Guacamole display.
|
||||
*
|
||||
* @constructor
|
||||
* @param {ManagedDisplay.Cursor|Object} [template = {}]
|
||||
* The object whose properties should be copied within the new
|
||||
* ManagedDisplay.Cursor.
|
||||
*/
|
||||
ManagedDisplay.Cursor = function Cursor(template = {}) {
|
||||
/**
|
||||
* The actual mouse cursor image.
|
||||
*
|
||||
* @type HTMLCanvasElement
|
||||
*/
|
||||
this.canvas = template.canvas;
|
||||
|
||||
/**
|
||||
* The X coordinate of the cursor hotspot.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.x = template.x;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the cursor hotspot.
|
||||
*
|
||||
* @type Number
|
||||
*/
|
||||
this.y = template.y;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new ManagedDisplay which represents the current state of the
|
||||
* given Guacamole display.
|
||||
*
|
||||
* @param {Guacamole.Display} display
|
||||
* The Guacamole display to represent. Changes to this display will
|
||||
* affect this ManagedDisplay.
|
||||
*
|
||||
* @returns {ManagedDisplay}
|
||||
* A new ManagedDisplay which represents the current state of the
|
||||
* given Guacamole display.
|
||||
*/
|
||||
ManagedDisplay.getInstance = function getInstance(display) {
|
||||
var managedDisplay = new ManagedDisplay({ display : display });
|
||||
|
||||
// Store changes to display size
|
||||
display.onresize = function setClientSize() {
|
||||
managedDisplay.size = new ManagedDisplay.Dimensions({
|
||||
width : display.getWidth(),
|
||||
height : display.getHeight()
|
||||
});
|
||||
};
|
||||
|
||||
// Store changes to display cursor
|
||||
display.oncursor = function setClientCursor(canvas, x, y) {
|
||||
managedDisplay.cursor = new ManagedDisplay.Cursor({
|
||||
canvas : canvas,
|
||||
x : x,
|
||||
y : y
|
||||
});
|
||||
};
|
||||
|
||||
return managedDisplay;
|
||||
};
|
||||
|
||||
return ManagedDisplay;
|
||||
|
||||
});
|
101
src/sunstone/public/app/utils/guacamole/utils.js
Normal file
101
src/sunstone/public/app/utils/guacamole/utils.js
Normal file
@ -0,0 +1,101 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed 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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
define(function() {
|
||||
|
||||
/**
|
||||
* Create a new function that limits calls to func to once every given time frame.
|
||||
*
|
||||
* @param {*} func Throttled function
|
||||
* @param {*} delay Time delay between calls
|
||||
*/
|
||||
function throttle(func, delay) {
|
||||
var wait = false;
|
||||
|
||||
return function() {
|
||||
if (!wait) {
|
||||
func();
|
||||
wait = true;
|
||||
|
||||
setTimeout(function() {
|
||||
wait = false;
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A function to modify the property's getters and
|
||||
// setters so that a custom callback handler can be run in the main
|
||||
// program each time the property is changed
|
||||
function observe(subject, property, callbackHandler) {
|
||||
Object.defineProperty(subject, property, {
|
||||
// Return the default value of the property
|
||||
// ("this.value" automatically gives you the property's current value)
|
||||
get: function() {
|
||||
return this.value;
|
||||
},
|
||||
|
||||
// Set the property with a new value
|
||||
set: function(newValue) {
|
||||
// Assign the new value
|
||||
this.value = newValue;
|
||||
|
||||
// Bind the observer's changeHandler to the subject
|
||||
subject.changeHandler = callbackHandler;
|
||||
|
||||
// Tell the subject to call the changeHandler when this property is changed.
|
||||
// (This is like a custom event dispatcher)
|
||||
subject.changeHandler(newValue)
|
||||
},
|
||||
|
||||
// Set the default parameters for how this property can be accessed and changed.
|
||||
// You probably don't need to change these unless you want to lock down the
|
||||
// property values to prevent your program from changing them
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writeable: true
|
||||
});
|
||||
}
|
||||
|
||||
// An optional function to stop watching properties.
|
||||
// It normalizes the getter and setter and removes the callback handler
|
||||
function unobserve(subject, property) {
|
||||
// Delete the changeHandler
|
||||
delete subject.changeHandler;
|
||||
|
||||
// Reset the getter and setter
|
||||
Object.defineProperty(subject, property, {
|
||||
get: function() {
|
||||
return this.value;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.value = newValue;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writeable: true
|
||||
});
|
||||
}
|
||||
|
||||
var Utils = {
|
||||
throttle: throttle,
|
||||
observe: observe,
|
||||
unobserve: unobserve
|
||||
}
|
||||
|
||||
return Utils;
|
||||
|
||||
});
|
@ -161,8 +161,7 @@ progress{
|
||||
}
|
||||
}
|
||||
|
||||
.guacamole-display {
|
||||
cursor: none;
|
||||
.guacamole-main {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
@ -172,4 +171,251 @@ progress{
|
||||
& > div {
|
||||
z-index: 1;
|
||||
}
|
||||
& .guacamole-display {
|
||||
cursor: none;
|
||||
}
|
||||
}
|
||||
|
||||
#guacVMDialog {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.osk-container {
|
||||
z-index: 2;
|
||||
background: rgba(0, 0, 0, 0.59);
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
left: 0;
|
||||
display: none;
|
||||
border: 1px solid #acacac;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 20px #acacac;
|
||||
& > .osk-container-header {
|
||||
background: linear-gradient(to top, #ebebeb, #d5d5d5);
|
||||
color: #4d494d;
|
||||
font-size: 11pt;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
border-top: 1px solid #f3f1f3;
|
||||
border-bottom: 1px solid #b1aeb1;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
& .buttons {
|
||||
padding-left: 8px;
|
||||
padding-top: 3px;
|
||||
float: left;
|
||||
line-height: 0;
|
||||
& .close {
|
||||
background: #ff5c5c;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
& .layouts {
|
||||
padding-right: 8px;
|
||||
padding-top: 3px;
|
||||
float: right;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.guac-keyboard {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: default;
|
||||
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.guac-keyboard,
|
||||
.guac-keyboard * {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-key-container {
|
||||
display: inline-block;
|
||||
margin: 0.05em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-key {
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
background: #444;
|
||||
|
||||
border: 0.125em solid #666;
|
||||
-moz-border-radius: 0.25em;
|
||||
-webkit-border-radius: 0.25em;
|
||||
-khtml-border-radius: 0.25em;
|
||||
border-radius: 0.25em;
|
||||
|
||||
color: white;
|
||||
font-size: 40%;
|
||||
font-weight: lighter;
|
||||
text-align: center;
|
||||
white-space: pre;
|
||||
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25),
|
||||
1px -1px 0 rgba(0, 0, 0, 0.25),
|
||||
-1px 1px 0 rgba(0, 0, 0, 0.25),
|
||||
-1px -1px 0 rgba(0, 0, 0, 0.25);
|
||||
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-key:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-key.highlight {
|
||||
background: #666;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
/* Align some keys to the left */
|
||||
.guac-keyboard .guac-keyboard-key-caps,
|
||||
.guac-keyboard .guac-keyboard-key-enter,
|
||||
.guac-keyboard .guac-keyboard-key-tab,
|
||||
.guac-keyboard .guac-keyboard-key-lalt,
|
||||
.guac-keyboard .guac-keyboard-key-ralt,
|
||||
.guac-keyboard .guac-keyboard-key-alt-gr,
|
||||
.guac-keyboard .guac-keyboard-key-lctrl,
|
||||
.guac-keyboard .guac-keyboard-key-rctrl,
|
||||
.guac-keyboard .guac-keyboard-key-lshift,
|
||||
.guac-keyboard .guac-keyboard-key-rshift {
|
||||
text-align: left;
|
||||
padding-left: 0.75em;
|
||||
}
|
||||
|
||||
/* Active shift */
|
||||
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-rshift,
|
||||
.guac-keyboard.guac-keyboard-modifier-shift .guac-keyboard-key-lshift,
|
||||
|
||||
/* Active ctrl */
|
||||
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-rctrl,
|
||||
.guac-keyboard.guac-keyboard-modifier-control .guac-keyboard-key-lctrl,
|
||||
|
||||
/* Active alt */
|
||||
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-ralt,
|
||||
.guac-keyboard.guac-keyboard-modifier-alt .guac-keyboard-key-lalt,
|
||||
|
||||
/* Active alt-gr */
|
||||
.guac-keyboard.guac-keyboard-modifier-alt-gr .guac-keyboard-key-alt-gr,
|
||||
|
||||
/* Active caps */
|
||||
.guac-keyboard.guac-keyboard-modifier-caps .guac-keyboard-key-caps,
|
||||
|
||||
/* Active super */
|
||||
.guac-keyboard.guac-keyboard-modifier-super .guac-keyboard-key-super {
|
||||
background: #882;
|
||||
border-color: #DD4;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-key.guac-keyboard-pressed {
|
||||
background: #822;
|
||||
border-color: #D44;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-group {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-group.guac-keyboard-alpha,
|
||||
.guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-group.guac-keyboard-main {
|
||||
|
||||
/* IE10 */
|
||||
display: -ms-flexbox;
|
||||
-ms-flex-align: stretch;
|
||||
-ms-flex-direction: row;
|
||||
|
||||
/* Ancient Mozilla */
|
||||
display: -moz-box;
|
||||
-moz-box-align: stretch;
|
||||
-moz-box-orient: horizontal;
|
||||
|
||||
/* Ancient WebKit */
|
||||
display: -webkit-box;
|
||||
-webkit-box-align: stretch;
|
||||
-webkit-box-orient: horizontal;
|
||||
|
||||
/* Old WebKit */
|
||||
display: -webkit-flex;
|
||||
-webkit-align-items: stretch;
|
||||
-webkit-flex-direction: row;
|
||||
|
||||
/* W3C */
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: row;
|
||||
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-group.guac-keyboard-movement {
|
||||
-ms-flex: 1 1 auto;
|
||||
-moz-box-flex: 1;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.guac-keyboard .guac-keyboard-gap {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Hide keycaps requiring modifiers which are NOT currently active. */
|
||||
.guac-keyboard:not(.guac-keyboard-modifier-caps)
|
||||
.guac-keyboard-cap.guac-keyboard-requires-caps,
|
||||
|
||||
.guac-keyboard:not(.guac-keyboard-modifier-shift)
|
||||
.guac-keyboard-cap.guac-keyboard-requires-shift,
|
||||
|
||||
.guac-keyboard:not(.guac-keyboard-modifier-alt-gr)
|
||||
.guac-keyboard-cap.guac-keyboard-requires-alt-gr,
|
||||
|
||||
/* Hide keycaps NOT requiring modifiers which ARE currently active, where that
|
||||
modifier is used to determine which cap is displayed for the current key. */
|
||||
.guac-keyboard.guac-keyboard-modifier-shift
|
||||
.guac-keyboard-key.guac-keyboard-uses-shift
|
||||
.guac-keyboard-cap:not(.guac-keyboard-requires-shift),
|
||||
|
||||
.guac-keyboard.guac-keyboard-modifier-caps
|
||||
.guac-keyboard-key.guac-keyboard-uses-caps
|
||||
.guac-keyboard-cap:not(.guac-keyboard-requires-caps),
|
||||
|
||||
.guac-keyboard.guac-keyboard-modifier-alt-gr
|
||||
.guac-keyboard-key.guac-keyboard-uses-alt-gr
|
||||
.guac-keyboard-cap:not(.guac-keyboard-requires-alt-gr) {
|
||||
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
/* Fade out keys which do not use AltGr if AltGr is active */
|
||||
.guac-keyboard.guac-keyboard-modifier-alt-gr
|
||||
.guac-keyboard-key:not(.guac-keyboard-uses-alt-gr):not(.guac-keyboard-key-alt-gr) {
|
||||
opacity: 0.5;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user