1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-23 22:50:09 +03:00

F #5071: Improvement in guacamole client (#1118)

(cherry picked from commit 9935822e52cf057e70053ea152c3421f77d8e79f)
This commit is contained in:
Sergio Betanzos 2021-04-21 10:47:24 +02:00 committed by Ruben S. Montero
parent 2e7b2231db
commit b4d71562a5
No known key found for this signature in database
GPG Key ID: A0CEA6FA880A1D87
19 changed files with 4635 additions and 90 deletions

View File

@ -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;
}
});

View File

@ -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>

View File

@ -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";
}
}
});

View 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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

View File

@ -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;
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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;
});

View 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;
});

View File

@ -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;
});

View 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;
});

View File

@ -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;
});

View 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;
});

View 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;
});

View File

@ -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;
}