1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-09 20:58:35 +03:00

After a close look at Guacomole, i noticed that i forgot a lot of things

Basically, i added the scripts, images, etc.. from Guacamole and this started to work fine!!! :-)

I had to fix a few thins that are not uset in this implementation, but not much..

* Added support for remote printing
* Added support for mobile devices
This commit is contained in:
Adolfo Gómez 2013-11-05 21:19:15 +00:00
parent 5fd0436ab6
commit 52c69a3521
30 changed files with 7063 additions and 198 deletions

View File

@ -84,6 +84,8 @@ public class TunnelServlet
info.setOptimalScreenWidth(Integer.parseInt(width));
info.setOptimalScreenHeight(Integer.parseInt(height));
System.out.println("Optiomal size: " + width + "x" + height);
// Add audio mimetypes
String[] audio_mimetypes = request.getParameterValues("audio");
if (audio_mimetypes != null)
@ -98,13 +100,14 @@ public class TunnelServlet
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol(params.get("protocol"));
System.out.println("PArsing parameters");
System.out.println("Parsing parameters");
Enumeration<String> keys = params.keys();
while( keys.hasMoreElements() ) {
String key = keys.nextElement();
if( "protocol".equals(key) )
continue;
System.out.println("Parameter " + key + ": " + params.get(key));
config.setParameter(key, params.get(key));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 B

View File

@ -28,126 +28,13 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
<link rel="stylesheet" type="text/css" href="styles/client.css"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>UDS</title>
<!-- Client core scripts -->
<link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
<link rel="stylesheet" type="text/css" href="styles/client.css"/>
<script type="text/javascript" src="guacamole-common-js/keyboard.js"></script>
<script type="text/javascript" src="guacamole-common-js/mouse.js"></script>
<script type="text/javascript" src="guacamole-common-js/layer.js"></script>
<script type="text/javascript" src="guacamole-common-js/tunnel.js"></script>
<script type="text/javascript" src="guacamole-common-js/audio.js"></script>
<script type="text/javascript" src="guacamole-common-js/guacamole.js"></script>
<script type="text/javascript" src="guacamole-common-js/oskeyboard.js"></script>
<script type="text/javascript"> /* <![CDATA[ */
/**
Get Audio supported items
*/
GuacAudio = new (function() {
var codecs = [
'audio/ogg; codecs="vorbis"',
'audio/mp4; codecs="mp4a.40.5"',
'audio/mpeg; codecs="mp3"',
'audio/webm; codecs="vorbis"',
'audio/wav; codecs=1'
];
var probably_supported = [];
var maybe_supported = [];
/**
* Array of all supported audio mimetypes, ordered by liklihood of
* working.
*/
this.supported = [];
// Build array of supported audio formats
codecs.forEach(function(mimetype) {
var audio = new Audio();
var support_level = audio.canPlayType(mimetype);
// Trim semicolon and trailer
var semicolon = mimetype.indexOf(";");
if (semicolon != -1)
mimetype = mimetype.substring(0, semicolon);
// Partition by probably/maybe
if (support_level == "probably")
probably_supported.push(mimetype);
else if (support_level == "maybe")
maybe_supported.push(mimetype);
});
// Add probably supported types first
Array.prototype.push.apply(
this.supported, probably_supported);
// Prioritize "maybe" supported types second
Array.prototype.push.apply(
this.supported, maybe_supported);
})();
GuacVideo = new (function() {
var codecs = [
'video/ogg; codecs="theora, vorbis"',
'video/mp4; codecs="avc1.4D401E, mp4a.40.5"',
'video/webm; codecs="vp8.0, vorbis"'
];
var probably_supported = [];
var maybe_supported = [];
/**
* Array of all supported video mimetypes, ordered by liklihood of
* working.
*/
this.supported = [];
// Build array of supported audio formats
codecs.forEach(function(mimetype) {
var video = document.createElement("video");
var support_level = video.canPlayType(mimetype);
// Trim semicolon and trailer
var semicolon = mimetype.indexOf(";");
if (semicolon != -1)
mimetype = mimetype.substring(0, semicolon);
// Partition by probably/maybe
if (support_level == "probably")
probably_supported.push(mimetype);
else if (support_level == "maybe")
maybe_supported.push(mimetype);
});
// Add probably supported types first
Array.prototype.push.apply(
this.supported, probably_supported);
// Prioritize "maybe" supported types second
Array.prototype.push.apply(
this.supported, maybe_supported);
})();
/* ]]> */
</script>
</head>
<body>
@ -161,7 +48,34 @@
</div>
<div id="viewportClone"/>
<!-- Notification area -->
<div id="notificationArea"/>
<!-- Images which should be preloaded -->
<div id="preload">
<img src="images/action-icons/guac-close.png"/>
<img src="images/progress.png"/>
</div>
<script type="text/javascript" src="scripts/lib/blob/blob.js"></script>
<script type="text/javascript" src="scripts/lib/filesaver/filesaver.js"></script>
<!-- Client core scripts -->
<script type="text/javascript" src="guacamole-common-js/keyboard.js"></script>
<script type="text/javascript" src="guacamole-common-js/mouse.js"></script>
<script type="text/javascript" src="guacamole-common-js/layer.js"></script>
<script type="text/javascript" src="guacamole-common-js/tunnel.js"></script>
<script type="text/javascript" src="guacamole-common-js/audio.js"></script>
<script type="text/javascript" src="guacamole-common-js/guacamole.js"></script>
<script type="text/javascript" src="guacamole-common-js/oskeyboard.js"></script>
<!-- guacamole-default-webapp scripts -->
<script type="text/javascript" src="scripts/session.js"></script>
<script type="text/javascript" src="scripts/guac-ui.js"></script>
<script type="text/javascript" src="scripts/client-ui.js"></script>
<!-- Init -->
<script type="text/javascript"> /* <![CDATA[ */
window.onload = function() {
@ -179,94 +93,72 @@
else {
data = params;
}
// Get display div from document
var display = document.getElementById("display");
var tunnel;
// If WebSocket available, try to use it.
if (window.WebSocket)
var tunnel;
GuacUI.sessionState.setProperty("auto-fit", true);
GuacUI.sessionState.setProperty("exitUrl", exiturl);
GuacUI.sessionState.setProperty("disable-sound", false);
// If WebSocket available, try to use it.
/*if (window.WebSocket)
tunnel = new Guacamole.ChainedTunnel(
new Guacamole.WebSocketTunnel("websocket-tunnel"),
new Guacamole.HTTPTunnel("tunnel")
);
// If no WebSocket, then use HTTP.
else
tunnel = new Guacamole.HTTPTunnel("tunnel")
// Instantiate client, using an HTTP tunnel for communications.
var guac = new Guacamole.Client(tunnel);
// Add client to display div
var inner = guac.getDisplay();
inner.className = "software-cursor";
// Error handler
guac.onerror = function(error) {
//alert(error);
window.location.assign(exiturl);
};
guac.onstatechange = function(state) {
if( state == 0 || state == 4 || state == 5 )
window.location.assign(exiturl);
};
var optimal_width = window.innerWidth;
var optimal_height = window.innerHeight;
// Scale width/height to be at least 600x600
if (optimal_width < 600 || optimal_height < 600) {
var scale = Math.max(600 / optimal_width, 600 / optimal_height);
optimal_width = Math.floor(optimal_width * scale);
optimal_height = Math.floor(optimal_height * scale);
}
// Get entire query string, and pass to connect().
// Normally, only the "id" parameter is required, but
// all parameters should be preserved and passed on for
// the sake of authentication.
var connect_string = "data=" + data + "&width=" + optimal_width + "&height=" + optimal_height;
// Add audio mimetypes to connect_string
GuacAudio.supported.forEach(function(mimetype) {
connect_string += "&audio=" + encodeURIComponent(mimetype);
});
else*/
tunnel = new Guacamole.HTTPTunnel("tunnel")
// Add video mimetypes to connect_string
GuacVideo.supported.forEach(function(mimetype) {
connect_string += "&video=" + encodeURIComponent(mimetype);
});
display.appendChild(inner);
guac.connect(connect_string);
// Disconnect on close
window.onunload = function() {
guac.disconnect();
}
// Mouse
var mouse = new Guacamole.Mouse(inner);
mouse.onmousedown =
mouse.onmouseup =
mouse.onmousemove = function(mouseState) {
guac.sendMouseState(mouseState);
};
// Keyboard
var keyboard = new Guacamole.Keyboard(document);
keyboard.onkeydown = function (keysym) {
guac.sendKeyEvent(1, keysym);
};
keyboard.onkeyup = function (keysym) {
guac.sendKeyEvent(0, keysym);
};
}, 0);
// Instantiate client
var guac = new Guacamole.Client(tunnel);
// Add client to UI
guac.getDisplay().className = "software-cursor";
GuacUI.Client.display.appendChild(guac.getDisplay());
// Tie UI to client
GuacUI.Client.attach(guac);
try {
// Calculate optimal width/height for display
var optimal_width = window.innerWidth;
var optimal_height = window.innerHeight;
// Scale width/height to be at least 600x600
if (optimal_width < 600 || optimal_height < 600) {
var scale = Math.max(600 / optimal_width, 600 / optimal_height);
optimal_width = Math.floor(optimal_width * scale);
optimal_height = Math.floor(optimal_height * scale);
}
// Get entire query string, and pass to connect().
// Normally, only the "id" parameter is required, but
// all parameters should be preserved and passed on for
// the sake of authentication.
var connect_string = "data=" + data + "&width=" + optimal_width + "&height=" + optimal_height;
// Add audio mimetypes to connect_string
GuacUI.Audio.supported.forEach(function(mimetype) {
connect_string += "&audio=" + encodeURIComponent(mimetype);
});
// Add video mimetypes to connect_string
GuacUI.Video.supported.forEach(function(mimetype) {
connect_string += "&video=" + encodeURIComponent(mimetype);
});
guac.connect(connect_string);
}
catch (e) {
GuacUI.Client.showError(e.message);
}
}, 0);
};
/* ]]> */ </script>

View File

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard PUBLIC
"-//Guacamole/Guacamole Onscreen Keyboard DTD 0.6.0//EN"
"http://guac-dev.org/pub/dtd/guacamole-osk-0.6.0.dtd">
<!--
Guacamole - Clientless Remote Desktop
Copyright (C) 2010 Michael Jumper
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<keyboard lang="en_US" layout="qwerty" size="16.3">
<row>
<key size="1.5">
<cap keysym="0xFF09">Tab</cap>
</key>
<gap size="0.1"/>
<key>
<cap>q</cap>
<cap if="numsym">1</cap>
<cap if="shift">Q</cap>
<cap if="numsym,shift">q</cap>
</key>
<gap size="0.1"/>
<key>
<cap>w</cap>
<cap if="numsym">2</cap>
<cap if="shift">W</cap>
<cap if="numsym,shift">w</cap>
</key>
<gap size="0.1"/>
<key>
<cap>e</cap>
<cap if="numsym">3</cap>
<cap if="shift">E</cap>
<cap if="numsym,shift">e</cap>
</key>
<gap size="0.1"/>
<key>
<cap>r</cap>
<cap if="numsym">4</cap>
<cap if="shift">R</cap>
<cap if="numsym,shift">r</cap>
</key>
<gap size="0.1"/>
<key>
<cap>t</cap>
<cap if="numsym">5</cap>
<cap if="shift">T</cap>
<cap if="numsym,shift">t</cap>
</key>
<gap size="0.1"/>
<key>
<cap>y</cap>
<cap if="numsym">6</cap>
<cap if="shift">Y</cap>
<cap if="numsym,shift">y</cap>
</key>
<gap size="0.1"/>
<key>
<cap>u</cap>
<cap if="numsym">7</cap>
<cap if="shift">U</cap>
<cap if="numsym,shift">u</cap>
</key>
<gap size="0.1"/>
<key>
<cap>i</cap>
<cap if="numsym">8</cap>
<cap if="shift">I</cap>
<cap if="numsym,shift">i</cap>
</key>
<gap size="0.1"/>
<key>
<cap>o</cap>
<cap if="numsym">9</cap>
<cap if="shift">O</cap>
<cap if="numsym,shift">o</cap>
</key>
<gap size="0.1"/>
<key>
<cap>p</cap>
<cap if="numsym">0</cap>
<cap if="shift">P</cap>
<cap if="numsym,shift">p</cap>
</key>
<gap size="0.1"/>
<key>
<cap>[</cap>
<cap if="shift">{</cap>
</key>
<gap size="0.1"/>
<key>
<cap>]</cap>
<cap if="shift">}</cap>
</key>
<gap size="0.1"/>
<key size="1.5">
<cap keysym="0xFF08">Back</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="1.85" class="numsym">
<cap modifier="numsym" sticky="true">?123</cap>
</key>
<gap size="0.1"/>
<key>
<cap>a</cap>
<cap if="numsym">#</cap>
<cap if="shift">A</cap>
<cap if="numsym,shift">a</cap>
</key>
<gap size="0.1"/>
<key>
<cap>s</cap>
<cap if="numsym">$</cap>
<cap if="shift">S</cap>
<cap if="numsym,shift">s</cap>
</key>
<gap size="0.1"/>
<key>
<cap>d</cap>
<cap if="numsym">%</cap>
<cap if="shift">D</cap>
<cap if="numsym,shift">d</cap>
</key>
<gap size="0.1"/>
<key>
<cap>f</cap>
<cap if="numsym">&amp;</cap>
<cap if="shift">F</cap>
<cap if="numsym,shift">f</cap>
</key>
<gap size="0.1"/>
<key>
<cap>g</cap>
<cap if="numsym">*</cap>
<cap if="shift">G</cap>
<cap if="numsym,shift">g</cap>
</key>
<gap size="0.1"/>
<key>
<cap>h</cap>
<cap if="numsym">-</cap>
<cap if="shift">H</cap>
<cap if="numsym,shift">h</cap>
</key>
<gap size="0.1"/>
<key>
<cap>j</cap>
<cap if="numsym">+</cap>
<cap if="shift">J</cap>
<cap if="numsym,shift">j</cap>
</key>
<gap size="0.1"/>
<key>
<cap>k</cap>
<cap if="numsym">(</cap>
<cap if="shift">K</cap>
<cap if="numsym,shift">k</cap>
</key>
<gap size="0.1"/>
<key>
<cap>l</cap>
<cap if="numsym">)</cap>
<cap if="shift">L</cap>
<cap if="numsym,shift">l</cap>
</key>
<gap size="0.1"/>
<key>
<cap>;</cap>
<cap if="shift">:</cap>
</key>
<gap size="0.1"/>
<key>
<cap>'</cap>
<cap if="shift">"</cap>
</key>
<gap size="0.1"/>
<key size="2.25">
<cap keysym="0xFF0D">Enter</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="2.1" class="shift">
<cap modifier="shift" keysym="0xFFE1">Shift</cap>
</key>
<gap size="0.1"/>
<key>
<cap>z</cap>
<cap if="numsym">&lt;</cap>
<cap if="shift">Z</cap>
<cap if="numsym,shift">z</cap>
</key>
<gap size="0.1"/>
<key>
<cap>x</cap>
<cap if="numsym">&gt;</cap>
<cap if="shift">X</cap>
<cap if="numsym,shift">x</cap>
</key>
<gap size="0.1"/>
<key>
<cap>c</cap>
<cap if="numsym">=</cap>
<cap if="shift">C</cap>
<cap if="numsym,shift">c</cap>
</key>
<gap size="0.1"/>
<key>
<cap>v</cap>
<cap if="numsym">'</cap>
<cap if="shift">V</cap>
<cap if="numsym,shift">v</cap>
</key>
<gap size="0.1"/>
<key>
<cap>b</cap>
<cap if="numsym">;</cap>
<cap if="shift">B</cap>
<cap if="numsym,shift">b</cap>
</key>
<gap size="0.1"/>
<key>
<cap>n</cap>
<cap if="numsym">,</cap>
<cap if="shift">N</cap>
<cap if="numsym,shift">n</cap>
</key>
<gap size="0.1"/>
<key>
<cap>m</cap>
<cap if="numsym">.</cap>
<cap if="shift">M</cap>
<cap if="numsym,shift">m</cap>
</key>
<gap size="0.1"/>
<key>
<cap>,</cap>
<cap if="numsym">!</cap>
<cap if="shift">!</cap>
<cap if="numsym,shift">!</cap>
</key>
<gap size="0.1"/>
<key>
<cap>.</cap>
<cap if="numsym">?</cap>
<cap if="shift">?</cap>
<cap if="numsym,shift">?</cap>
</key>
<gap size="0.1"/>
<key>
<cap>/</cap>
<cap if="shift">?</cap>
</key>
<gap size="0.1"/>
<key size="3.1" class="shift">
<cap modifier="shift" keysym="0xFFE2">Shift</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="1.6" class="control">
<cap modifier="control" keysym="0xFFE3">Ctrl</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="super">
<cap modifier="super" keysym="0xFFEB">Super</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="alt">
<cap modifier="alt" keysym="0xFFE9">Alt</cap>
</key>
<gap size="0.1"/>
<key size="6.1">
<cap> </cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="alt">
<cap modifier="alt" keysym="0xFFEA">Alt</cap>
</key>
<gap size="0.1"/>
<key size="1.6">
<cap keysym="0xFF67">Menu</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="control">
<cap modifier="control" keysym="0xFFE4">Ctrl</cap>
</key>
</row>
</keyboard>

View File

@ -0,0 +1,496 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboard PUBLIC
"-//Guacamole/Guacamole Onscreen Keyboard DTD 0.6.0//EN"
"http://guac-dev.org/pub/dtd/guacamole-osk-0.6.0.dtd">
<!--
Guacamole - Clientless Remote Desktop
Copyright (C) 2010 Michael Jumper
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<keyboard lang="en_US" layout="qwerty" size="22">
<row>
<key>
<cap keysym="0xFF1B">Esc</cap>
</key>
<gap size="0.8"/>
<key>
<cap keysym="0xFFBE">F1</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFBF">F2</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC0">F3</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC1">F4</cap>
</key>
<gap size="0.8"/>
<key>
<cap keysym="0xFFC2">F5</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC3">F6</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC4">F7</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC5">F8</cap>
</key>
<gap size="0.8"/>
<key>
<cap keysym="0xFFC6">F9</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC7">F10</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC8">F11</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFFC9">F12</cap>
</key>
</row>
<row>
<gap size="0.25"/>
</row>
<column>
<row>
<key>
<cap>`</cap>
<cap if="shift">~</cap>
</key>
<gap size="0.1"/>
<key>
<cap>1</cap>
<cap if="shift">!</cap>
</key>
<gap size="0.1"/>
<key>
<cap>2</cap>
<cap if="shift">@</cap>
</key>
<gap size="0.1"/>
<key>
<cap>3</cap>
<cap if="shift">#</cap>
</key>
<gap size="0.1"/>
<key>
<cap>4</cap>
<cap if="shift">$</cap>
</key>
<gap size="0.1"/>
<key>
<cap>5</cap>
<cap if="shift">%</cap>
</key>
<gap size="0.1"/>
<key>
<cap>6</cap>
<cap if="shift">^</cap>
</key>
<gap size="0.1"/>
<key>
<cap>7</cap>
<cap if="shift">&amp;</cap>
</key>
<gap size="0.1"/>
<key>
<cap>8</cap>
<cap if="shift">*</cap>
</key>
<gap size="0.1"/>
<key>
<cap>9</cap>
<cap if="shift">(</cap>
</key>
<gap size="0.1"/>
<key>
<cap>0</cap>
<cap if="shift">)</cap>
</key>
<gap size="0.1"/>
<key>
<cap>-</cap>
<cap if="shift">_</cap>
</key>
<gap size="0.1"/>
<key>
<cap>=</cap>
<cap if="shift">+</cap>
</key>
<gap size="0.1"/>
<key size="2">
<cap keysym="0xFF08">Back</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="1.5">
<cap keysym="0xFF09">Tab</cap>
</key>
<gap size="0.1"/>
<key>
<cap>q</cap>
<cap if="caps">Q</cap>
<cap if="shift">Q</cap>
<cap if="caps,shift">q</cap>
</key>
<gap size="0.1"/>
<key>
<cap>w</cap>
<cap if="caps">W</cap>
<cap if="shift">W</cap>
<cap if="caps,shift">w</cap>
</key>
<gap size="0.1"/>
<key>
<cap>e</cap>
<cap if="caps">E</cap>
<cap if="shift">E</cap>
<cap if="caps,shift">e</cap>
</key>
<gap size="0.1"/>
<key>
<cap>r</cap>
<cap if="caps">R</cap>
<cap if="shift">R</cap>
<cap if="caps,shift">r</cap>
</key>
<gap size="0.1"/>
<key>
<cap>t</cap>
<cap if="caps">T</cap>
<cap if="shift">T</cap>
<cap if="caps,shift">t</cap>
</key>
<gap size="0.1"/>
<key>
<cap>y</cap>
<cap if="caps">Y</cap>
<cap if="shift">Y</cap>
<cap if="caps,shift">y</cap>
</key>
<gap size="0.1"/>
<key>
<cap>u</cap>
<cap if="caps">U</cap>
<cap if="shift">U</cap>
<cap if="caps,shift">u</cap>
</key>
<gap size="0.1"/>
<key>
<cap>i</cap>
<cap if="caps">I</cap>
<cap if="shift">I</cap>
<cap if="caps,shift">i</cap>
</key>
<gap size="0.1"/>
<key>
<cap>o</cap>
<cap if="caps">O</cap>
<cap if="shift">O</cap>
<cap if="caps,shift">o</cap>
</key>
<gap size="0.1"/>
<key>
<cap>p</cap>
<cap if="caps">P</cap>
<cap if="shift">P</cap>
<cap if="caps,shift">p</cap>
</key>
<gap size="0.1"/>
<key>
<cap>[</cap>
<cap if="shift">{</cap>
</key>
<gap size="0.1"/>
<key>
<cap>]</cap>
<cap if="shift">}</cap>
</key>
<gap size="0.1"/>
<key size="1.5">
<cap>\</cap>
<cap if="shift">|</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="1.85">
<cap modifier="caps" keysym="0xFFE5" sticky="true">Caps</cap>
</key>
<gap size="0.1"/>
<key>
<cap>a</cap>
<cap if="caps">A</cap>
<cap if="shift">A</cap>
<cap if="caps,shift">a</cap>
</key>
<gap size="0.1"/>
<key>
<cap>s</cap>
<cap if="caps">S</cap>
<cap if="shift">S</cap>
<cap if="caps,shift">s</cap>
</key>
<gap size="0.1"/>
<key>
<cap>d</cap>
<cap if="caps">D</cap>
<cap if="shift">D</cap>
<cap if="caps,shift">d</cap>
</key>
<gap size="0.1"/>
<key>
<cap>f</cap>
<cap if="caps">F</cap>
<cap if="shift">F</cap>
<cap if="caps,shift">f</cap>
</key>
<gap size="0.1"/>
<key>
<cap>g</cap>
<cap if="caps">G</cap>
<cap if="shift">G</cap>
<cap if="caps,shift">g</cap>
</key>
<gap size="0.1"/>
<key>
<cap>h</cap>
<cap if="caps">H</cap>
<cap if="shift">H</cap>
<cap if="caps,shift">h</cap>
</key>
<gap size="0.1"/>
<key>
<cap>j</cap>
<cap if="caps">J</cap>
<cap if="shift">J</cap>
<cap if="caps,shift">j</cap>
</key>
<gap size="0.1"/>
<key>
<cap>k</cap>
<cap if="caps">K</cap>
<cap if="shift">K</cap>
<cap if="caps,shift">k</cap>
</key>
<gap size="0.1"/>
<key>
<cap>l</cap>
<cap if="caps">L</cap>
<cap if="shift">L</cap>
<cap if="caps,shift">l</cap>
</key>
<gap size="0.1"/>
<key>
<cap>;</cap>
<cap if="shift">:</cap>
</key>
<gap size="0.1"/>
<key>
<cap>'</cap>
<cap if="shift">"</cap>
</key>
<gap size="0.1"/>
<key size="2.25">
<cap keysym="0xFF0D">Enter</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="2.1" class="shift">
<cap modifier="shift" keysym="0xFFE1">Shift</cap>
</key>
<gap size="0.1"/>
<key>
<cap>z</cap>
<cap if="caps">Z</cap>
<cap if="shift">Z</cap>
<cap if="caps,shift">z</cap>
</key>
<gap size="0.1"/>
<key>
<cap>x</cap>
<cap if="caps">X</cap>
<cap if="shift">X</cap>
<cap if="caps,shift">x</cap>
</key>
<gap size="0.1"/>
<key>
<cap>c</cap>
<cap if="caps">C</cap>
<cap if="shift">C</cap>
<cap if="caps,shift">c</cap>
</key>
<gap size="0.1"/>
<key>
<cap>v</cap>
<cap if="caps">V</cap>
<cap if="shift">V</cap>
<cap if="caps,shift">v</cap>
</key>
<gap size="0.1"/>
<key>
<cap>b</cap>
<cap if="caps">B</cap>
<cap if="shift">B</cap>
<cap if="caps,shift">b</cap>
</key>
<gap size="0.1"/>
<key>
<cap>n</cap>
<cap if="caps">N</cap>
<cap if="shift">N</cap>
<cap if="caps,shift">n</cap>
</key>
<gap size="0.1"/>
<key>
<cap>m</cap>
<cap if="caps">M</cap>
<cap if="shift">M</cap>
<cap if="caps,shift">m</cap>
</key>
<gap size="0.1"/>
<key>
<cap>,</cap>
<cap if="shift">&lt;</cap>
</key>
<gap size="0.1"/>
<key>
<cap>.</cap>
<cap if="shift">&gt;</cap>
</key>
<gap size="0.1"/>
<key>
<cap>/</cap>
<cap if="shift">?</cap>
</key>
<gap size="0.1"/>
<key size="3.1" class="shift">
<cap modifier="shift" keysym="0xFFE2">Shift</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="1.6" class="control">
<cap modifier="control" keysym="0xFFE3">Ctrl</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="super">
<cap modifier="super" keysym="0xFFEB">Super</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="alt">
<cap modifier="alt" keysym="0xFFE9">Alt</cap>
</key>
<gap size="0.1"/>
<key size="6.1">
<cap> </cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="alt">
<cap modifier="alt" keysym="0xFFE3">Alt</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="super">
<cap modifier="super" keysym="0xFF67">Menu</cap>
</key>
<gap size="0.1"/>
<key size="1.6" class="control">
<cap modifier="control" keysym="0xFFE4">Ctrl</cap>
</key>
</row>
</column>
<column>
<row>
<gap size="0.25"/>
</row>
</column>
<column align="center">
<row>
<key size="1.75">
<cap keysym="0xFF63">Ins</cap>
</key>
<gap size="0.1"/>
<key size="1.75">
<cap keysym="0xFF50">Home</cap>
</key>
<gap size="0.1"/>
<key size="1.75">
<cap keysym="0xFF55">PgUp</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key size="1.75">
<cap keysym="0xFFFF">Del</cap>
</key>
<gap size="0.1"/>
<key size="1.75">
<cap keysym="0xFF57">End</cap>
</key>
<gap size="0.1"/>
<key size="1.75">
<cap keysym="0xFF56">PgDn</cap>
</key>
</row>
<row>
<gap/>
</row>
<row>
<key>
<cap keysym="0xFF52">&#x2191;</cap>
</key>
</row>
<row><gap size="0.1"/></row>
<row>
<key>
<cap keysym="0xFF51">&#x2190;</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFF54">&#x2193;</cap>
</key>
<gap size="0.1"/>
<key>
<cap keysym="0xFF53">&#x2192;</cap>
</key>
</row>
</column>
</keyboard>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,995 @@
/**
* Client UI root object.
*/
GuacUI.Client = {
/**
* Collection of all Guacamole client UI states.
*/
"states": {
/**
* The normal default Guacamole client UI mode
*/
"INTERACTIVE" : 0,
/**
* Same as INTERACTIVE except with visible on-screen keyboard.
*/
"OSK" : 1,
/**
* No on-screen keyboard, but a visible magnifier.
*/
"MAGNIFIER" : 2,
/**
* Arrows and a draggable view.
*/
"PAN" : 3,
/**
* Same as PAN, but with visible native OSK.
*/
"PAN_TYPING" : 4,
/**
* Precursor to PAN_TYPING, like PAN, except does not pan the
* screen, but rather hints at how to start typing.
*/
"WAIT_TYPING" : 5
},
/* Constants */
"LONG_PRESS_DETECT_TIMEOUT" : 800, /* milliseconds */
"LONG_PRESS_MOVEMENT_THRESHOLD" : 10, /* pixels */
"KEYBOARD_AUTO_RESIZE_INTERVAL" : 30, /* milliseconds */
/* UI Components */
"viewport" : document.getElementById("viewportClone"),
"display" : document.getElementById("display"),
"notification_area" : document.getElementById("notificationArea"),
/* Expected Input Rectangle */
"expected_input_x" : 0,
"expected_input_y" : 0,
"expected_input_width" : 1,
"expected_input_height" : 1,
"connectionName" : "Guacamole",
"overrideAutoFit" : false,
"attachedClient" : null
};
/**
* Component which displays a magnified (100% zoomed) client display.
*
* @constructor
* @augments GuacUI.DraggableComponent
*/
GuacUI.Client.Magnifier = function() {
/**
* Reference to this magnifier.
* @private
*/
var guac_magnifier = this;
/**
* Large background div which will block touch events from reaching the
* client while also providing a click target to deactivate the
* magnifier.
* @private
*/
var magnifier_background = GuacUI.createElement("div", "magnifier-background");
/**
* Container div for the magnifier, providing a clipping rectangle.
* @private
*/
var magnifier = GuacUI.createChildElement(magnifier_background,
"div", "magnifier");
/**
* Canvas which will contain the static image copy of the display at time
* of show.
* @private
*/
var magnifier_display = GuacUI.createChildElement(magnifier, "canvas");
/**
* Context of magnifier display.
* @private
*/
var magnifier_context = magnifier_display.getContext("2d");
/*
* This component is draggable.
*/
GuacUI.DraggableComponent.apply(this, [magnifier]);
// Ensure transformations on display originate at 0,0
magnifier.style.transformOrigin =
magnifier.style.webkitTransformOrigin =
magnifier.style.MozTransformOrigin =
magnifier.style.OTransformOrigin =
magnifier.style.msTransformOrigin =
"0 0";
/*
* Reposition magnifier display relative to own position on screen.
*/
this.onmove = function(x, y) {
var width = magnifier.offsetWidth;
var height = magnifier.offsetHeight;
// Update contents relative to new position
var clip_x = x
/ (window.innerWidth - width) * (GuacUI.Client.attachedClient.getWidth() - width);
var clip_y = y
/ (window.innerHeight - height) * (GuacUI.Client.attachedClient.getHeight() - height);
magnifier_display.style.WebkitTransform =
magnifier_display.style.MozTransform =
magnifier_display.style.OTransform =
magnifier_display.style.msTransform =
magnifier_display.style.transform = "translate("
+ (-clip_x) + "px, " + (-clip_y) + "px)";
/* Update expected input rectangle */
GuacUI.Client.expected_input_x = clip_x;
GuacUI.Client.expected_input_y = clip_y;
GuacUI.Client.expected_input_width = width;
GuacUI.Client.expected_input_height = height;
};
/*
* Copy display and add self to body on show.
*/
this.show = function() {
// Copy displayed image
magnifier_display.width = GuacUI.Client.attachedClient.getWidth();
magnifier_display.height = GuacUI.Client.attachedClient.getHeight();
magnifier_context.drawImage(GuacUI.Client.attachedClient.flatten(), 0, 0);
// Show magnifier container
document.body.appendChild(magnifier_background);
};
/*
* Remove self from body on hide.
*/
this.hide = function() {
// Hide magnifier container
document.body.removeChild(magnifier_background);
};
/*
* If the user clicks on the background, switch to INTERACTIVE mode.
*/
magnifier_background.addEventListener("click", function() {
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
}, true);
/*
* If the user clicks on the magnifier, switch to PAN_TYPING mode.
*/
magnifier.addEventListener("click", function(e) {
GuacUI.StateManager.setState(GuacUI.Client.states.PAN_TYPING);
e.stopPropagation();
}, true);
};
/*
* We inherit from GuacUI.DraggableComponent.
*/
GuacUI.Client.Magnifier.prototype = new GuacUI.DraggableComponent();
GuacUI.StateManager.registerComponent(
new GuacUI.Client.Magnifier(),
GuacUI.Client.states.MAGNIFIER
);
/**
* Zoomed Display, a pseudo-component.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.Client.ZoomedDisplay = function() {
this.show = function() {
GuacUI.Client.overrideAutoFit = true;
GuacUI.Client.updateDisplayScale();
};
this.hide = function() {
GuacUI.Client.overrideAutoFit = false;
GuacUI.Client.updateDisplayScale();
};
};
GuacUI.Client.ZoomedDisplay.prototype = new GuacUI.Component();
/*
* Zoom the main display during PAN and PAN_TYPING modes.
*/
GuacUI.StateManager.registerComponent(
new GuacUI.Client.ZoomedDisplay(),
GuacUI.Client.states.PAN,
GuacUI.Client.states.PAN_TYPING
);
/**
* Type overlay UI. This component functions to provide a means of activating
* the keyboard, when neither panning nor magnification make sense.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.Client.TypeOverlay = function() {
/**
* Overlay which will provide the means of scrolling the screen.
*/
var type_overlay = GuacUI.createElement("div", "type-overlay");
/*
* Add exit button
*/
var start = GuacUI.createChildElement(type_overlay, "p", "hint");
start.textContent = "Tap here to type, or tap the screen to cancel.";
// Begin typing when user clicks hint
start.addEventListener("click", function(e) {
GuacUI.StateManager.setState(GuacUI.Client.states.PAN_TYPING);
e.stopPropagation();
}, false);
this.show = function() {
document.body.appendChild(type_overlay);
};
this.hide = function() {
document.body.removeChild(type_overlay);
};
/*
* Cancel when user taps screen
*/
type_overlay.addEventListener("click", function(e) {
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
e.stopPropagation();
}, false);
};
GuacUI.Client.TypeOverlay.prototype = new GuacUI.Component();
/*
* Show the type overlay during WAIT_TYPING mode only
*/
GuacUI.StateManager.registerComponent(
new GuacUI.Client.TypeOverlay(),
GuacUI.Client.states.WAIT_TYPING
);
/**
* Pan overlay UI. This component functions to receive touch events and
* translate them into scrolling of the main UI.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.Client.PanOverlay = function() {
/**
* Overlay which will provide the means of scrolling the screen.
*/
var pan_overlay = GuacUI.createElement("div", "pan-overlay");
/*
* Add arrows
*/
GuacUI.createChildElement(pan_overlay, "div", "indicator up");
GuacUI.createChildElement(pan_overlay, "div", "indicator down");
GuacUI.createChildElement(pan_overlay, "div", "indicator right");
GuacUI.createChildElement(pan_overlay, "div", "indicator left");
/*
* Add exit button
*/
var back = GuacUI.createChildElement(pan_overlay, "p", "hint");
back.textContent = "Tap here to exit panning mode";
// Return to interactive when back is clicked
back.addEventListener("click", function() {
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
}, false);
this.show = function() {
document.body.appendChild(pan_overlay);
};
this.hide = function() {
document.body.removeChild(pan_overlay);
};
/*
* Transition to PAN_TYPING when the user taps on the overlay.
*/
pan_overlay.addEventListener("click", function(e) {
GuacUI.StateManager.setState(GuacUI.Client.states.PAN_TYPING);
e.stopPropagation();
}, true);
};
GuacUI.Client.PanOverlay.prototype = new GuacUI.Component();
/*
* Show the pan overlay during PAN or PAN_TYPING modes.
*/
GuacUI.StateManager.registerComponent(
new GuacUI.Client.PanOverlay(),
GuacUI.Client.states.PAN,
GuacUI.Client.states.PAN_TYPING
);
/**
* Native Keyboard. This component uses a hidden textarea field to show the
* platforms native on-screen keyboard (if any) or otherwise enable typing,
* should the platform require a text field with focus for keyboard events to
* register.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.Client.NativeKeyboard = function() {
/**
* Event target. This is a hidden textarea element which will receive
* key events.
* @private
*/
var eventTarget = GuacUI.createElement("textarea", "event-target");
eventTarget.setAttribute("autocorrect", "off");
eventTarget.setAttribute("autocapitalize", "off");
this.show = function() {
// Move to location of expected input
eventTarget.style.left = GuacUI.Client.expected_input_x + "px";
eventTarget.style.top = GuacUI.Client.expected_input_y + "px";
eventTarget.style.width = GuacUI.Client.expected_input_width + "px";
eventTarget.style.height = GuacUI.Client.expected_input_height + "px";
// Show and focus target
document.body.appendChild(eventTarget);
eventTarget.focus();
};
this.hide = function() {
// Hide and blur target
eventTarget.blur();
document.body.removeChild(eventTarget);
};
/*
* Automatically switch to INTERACTIVE mode after target loses focus
*/
eventTarget.addEventListener("blur", function() {
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
}, false);
};
GuacUI.Client.NativeKeyboard.prototype = new GuacUI.Component();
/*
* Show native keyboard during PAN_TYPING mode only.
*/
GuacUI.StateManager.registerComponent(
new GuacUI.Client.NativeKeyboard(),
GuacUI.Client.states.PAN_TYPING
);
/**
* On-screen Keyboard. This component provides a clickable/touchable keyboard
* which sends key events to the Guacamole client.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.Client.OnScreenKeyboard = function() {
/**
* Event target. This is a hidden textarea element which will receive
* key events.
* @private
*/
var keyboard_container = GuacUI.createElement("div", "keyboard-container");
var keyboard_resize_interval = null;
// On-screen keyboard
var keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty.xml");
keyboard_container.appendChild(keyboard.getElement());
var last_keyboard_width = 0;
// Function for automatically updating keyboard size
function updateKeyboardSize() {
var currentSize = keyboard.getElement().offsetWidth;
if (last_keyboard_width != currentSize) {
keyboard.resize(currentSize);
last_keyboard_width = currentSize;
}
}
keyboard.onkeydown = function(keysym) {
GuacUI.Client.attachedClient.sendKeyEvent(1, keysym);
};
keyboard.onkeyup = function(keysym) {
GuacUI.Client.attachedClient.sendKeyEvent(0, keysym);
};
this.show = function() {
// Show keyboard
document.body.appendChild(keyboard_container);
// Start periodic update of keyboard size
keyboard_resize_interval = window.setInterval(
updateKeyboardSize,
GuacUI.Client.KEYBOARD_AUTO_RESIZE_INTERVAL);
// Resize on window resize
window.addEventListener("resize", updateKeyboardSize, true);
// Initialize size
updateKeyboardSize();
};
this.hide = function() {
// Hide keyboard
document.body.removeChild(keyboard_container);
window.clearInterval(keyboard_resize_interval);
window.removeEventListener("resize", updateKeyboardSize, true);
};
};
GuacUI.Client.OnScreenKeyboard.prototype = new GuacUI.Component();
/*
* Show on-screen keyboard during OSK mode only.
*/
GuacUI.StateManager.registerComponent(
new GuacUI.Client.OnScreenKeyboard(),
GuacUI.Client.states.OSK
);
/*
* Set initial state
*/
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
/**
* Modal status display. Displays a message to the user, covering the entire
* screen.
*
* Normally, this should only be used when user interaction with other
* components is impossible.
*
* @constructor
* @augments GuacUI.Component
*/
GuacUI.Client.ModalStatus = function(text, classname) {
// Create element hierarchy
var outer = GuacUI.createElement("div", "dialogOuter");
var middle = GuacUI.createChildElement(outer, "div", "dialogMiddle");
var dialog = GuacUI.createChildElement(middle, "div", "dialog");
var status = GuacUI.createChildElement(dialog, "p", "status");
// Set classname if given
if (classname)
GuacUI.addClass(outer, classname);
// Set status text
status.textContent = text;
this.show = function() {
document.body.appendChild(outer);
};
this.hide = function() {
document.body.removeChild(outer);
};
};
GuacUI.Client.ModalStatus.prototype = new GuacUI.Component();
/**
* Updates the scale of the attached Guacamole.Client based on current window
* size and "auto-fit" setting.
*/
GuacUI.Client.updateDisplayScale = function() {
// Currently attacched client
var guac = GuacUI.Client.attachedClient;
// If auto-fit is enabled, scale display
if (!GuacUI.Client.overrideAutoFit
&& GuacUI.sessionState.getProperty("auto-fit")) {
// Calculate scale to fit screen
var fit_scale = Math.min(
window.innerWidth / guac.getWidth(),
window.innerHeight / guac.getHeight()
);
// Scale client
if (fit_scale != guac.getScale())
guac.scale(fit_scale);
}
// Otherwise, scale to 100%
else if (guac.getScale() != 1.0)
guac.scale(1.0);
};
/**
* Updates the document title based on the connection name.
*/
GuacUI.Client.updateTitle = function () {
if (GuacUI.Client.titlePrefix)
document.title = GuacUI.Client.titlePrefix + " " + GuacUI.Client.connectionName;
else
document.title = GuacUI.Client.connectionName;
};
/**
* Hides the currently-visible status overlay, if any.
*/
GuacUI.Client.hideStatus = function() {
if (GuacUI.Client.visibleStatus)
GuacUI.Client.visibleStatus.hide();
GuacUI.Client.visibleStatus = null;
};
/**
* Displays a status overlay with the given text.
*/
GuacUI.Client.showStatus = function(status) {
GuacUI.Client.hideStatus();
GuacUI.Client.visibleStatus = new GuacUI.Client.ModalStatus(status);
GuacUI.Client.visibleStatus.show();
};
/**
* Displays an error status overlay with the given text.
*/
GuacUI.Client.showError = function(status) {
GuacUI.Client.hideStatus();
GuacUI.Client.visibleStatus =
new GuacUI.Client.ModalStatus(status, "guac-error");
GuacUI.Client.visibleStatus.show();
}
/**
* Attaches a Guacamole.Client to the client UI, such that Guacamole events
* affect the UI, and local events affect the Guacamole.Client.
*
* @param {Guacamole.Client} guac The Guacamole.Client to attach to the UI.
*/
GuacUI.Client.attach = function(guac) {
// Store attached client
GuacUI.Client.attachedClient = guac;
// Get display element
var guac_display = guac.getDisplay();
/*
* Update the scale of the display when the client display size changes.
*/
guac.onresize = function(width, height) {
GuacUI.Client.updateDisplayScale();
}
/*
* Update UI when the state of the Guacamole.Client changes.
*/
guac.onstatechange = function(clientState) {
switch (clientState) {
// Idle
case 0:
GuacUI.Client.showStatus("Idle.");
GuacUI.Client.titlePrefix = "[Idle]";
break;
// Connecting
case 1:
GuacUI.Client.showStatus("Connecting...");
GuacUI.Client.titlePrefix = "[Connecting...]";
break;
// Connected + waiting
case 2:
GuacUI.Client.showStatus("Connected, waiting for first update...");
GuacUI.Client.titlePrefix = "[Waiting...]";
break;
// Connected
case 3:
GuacUI.Client.hideStatus();
GuacUI.Client.titlePrefix = null;
// Update clipboard with current data
if (GuacUI.sessionState.getProperty("clipboard"))
guac.setClipboard(GuacUI.sessionState.getProperty("clipboard"));
break;
// Disconnecting
case 4:
GuacUI.Client.showStatus("Disconnecting...");
GuacUI.Client.titlePrefix = "[Disconnecting...]";
break;
// Disconnected
case 5:
GuacUI.Client.showStatus("Disconnected.");
GuacUI.Client.titlePrefix = "[Disconnected]";
window.location.assign(GuacUI.sessionState.getProperty("exitUrl"));
break;
// Unknown status code
default:
GuacUI.Client.showStatus("[UNKNOWN STATUS]");
}
GuacUI.Client.updateTitle();
};
/*
* Change UI to reflect the connection name
*/
guac.onname = function(name) {
GuacUI.Client.connectionName = name;
GuacUI.Client.updateTitle();
};
/*
* Disconnect and display an error message when the Guacamole.Client
* receives an error.
*/
guac.onerror = function(error) {
// Disconnect, if connected
guac.disconnect();
// Display error message
GuacUI.Client.showError(error);
window.location.assign(GuacUI.sessionState.getProperty("exitUrl"));
};
// Server copy handler
guac.onclipboard = function(data) {
GuacUI.sessionState.setProperty("clipboard", data);
};
/*
* Prompt to download file when file received.
*/
function getSizeString(bytes) {
if (bytes > 1000000000)
return (bytes / 1000000000).toFixed(1) + " GB";
else if (bytes > 1000000)
return (bytes / 1000000).toFixed(1) + " MB";
else if (bytes > 1000)
return (bytes / 1000).toFixed(1) + " KB";
else
return bytes + " B";
}
guac.onblob = function(blob) {
var download = new GuacUI.Download(blob.name);
download.updateProgress(getSizeString(0));
GuacUI.Client.notification_area.appendChild(download.getElement());
// Update progress as data is received
blob.ondata = function() {
download.updateProgress(getSizeString(blob.getLength()));
};
// When complete, prompt for download
blob.oncomplete = function() {
download.ondownload = function() {
saveAs(blob.getBlob(), blob.name);
};
download.complete();
};
// When close clicked, remove from notification area
download.onclose = function() {
GuacUI.Client.notification_area.removeChild(download.getElement());
};
};
/*
* Do nothing when the display element is clicked on.
*/
guac_display.onclick = function(e) {
e.preventDefault();
return false;
};
/*
* Handle mouse and touch events relative to the display element.
*/
// Mouse
var mouse = new Guacamole.Mouse(guac_display);
var touch = new Guacamole.Mouse.Touchpad(guac_display);
touch.onmousedown = touch.onmouseup = touch.onmousemove =
mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
function(mouseState) {
// Determine mouse position within view
var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
var mouse_view_y = mouseState.y + guac_display.offsetTop - window.pageYOffset;
// Determine viewport dimensioins
var view_width = GuacUI.Client.viewport.offsetWidth;
var view_height = GuacUI.Client.viewport.offsetHeight;
// Determine scroll amounts based on mouse position relative to document
var scroll_amount_x;
if (mouse_view_x > view_width)
scroll_amount_x = mouse_view_x - view_width;
else if (mouse_view_x < 0)
scroll_amount_x = mouse_view_x;
else
scroll_amount_x = 0;
var scroll_amount_y;
if (mouse_view_y > view_height)
scroll_amount_y = mouse_view_y - view_height;
else if (mouse_view_y < 0)
scroll_amount_y = mouse_view_y;
else
scroll_amount_y = 0;
// Scroll (if necessary) to keep mouse on screen.
window.scrollBy(scroll_amount_x, scroll_amount_y);
// Scale event by current scale
var scaledState = new Guacamole.Mouse.State(
mouseState.x / guac.getScale(),
mouseState.y / guac.getScale(),
mouseState.left,
mouseState.middle,
mouseState.right,
mouseState.up,
mouseState.down);
// Send mouse event
guac.sendMouseState(scaledState);
};
/*
* Route document-level keyboard events to the client.
*/
var keyboard = new Guacamole.Keyboard(document);
var show_keyboard_gesture_possible = true;
keyboard.onkeydown = function (keysym) {
guac.sendKeyEvent(1, keysym);
// If key is NOT one of the expected keys, gesture not possible
if (keysym != 0xFFE3 && keysym != 0xFFE9 && keysym != 0xFFE1)
show_keyboard_gesture_possible = false;
};
keyboard.onkeyup = function (keysym) {
guac.sendKeyEvent(0, keysym);
// If lifting up on shift, toggle keyboard if rest of gesture
// conditions satisfied
if (show_keyboard_gesture_possible && keysym == 0xFFE1) {
if (keyboard.pressed[0xFFE3] && keyboard.pressed[0xFFE9]) {
// If in INTERACTIVE mode, switch to OSK
if (GuacUI.StateManager.getState() == GuacUI.Client.states.INTERACTIVE)
GuacUI.StateManager.setState(GuacUI.Client.states.OSK);
// If in OSK mode, switch to INTERACTIVE
else if (GuacUI.StateManager.getState() == GuacUI.Client.states.OSK)
GuacUI.StateManager.setState(GuacUI.Client.states.INTERACTIVE);
}
}
// Detect if no keys are pressed
var reset_gesture = true;
for (var pressed in keyboard.pressed) {
reset_gesture = false;
break;
}
// Reset gesture state if possible
if (reset_gesture)
show_keyboard_gesture_possible = true;
};
/*
* Disconnect and update thumbnail on close
*/
window.onunload = function() {
guac.disconnect();
};
/*
* Send size events on resize
*/
window.onresize = function() {
guac.sendSize(window.innerWidth, window.innerHeight);
GuacUI.Client.updateDisplayScale();
};
GuacUI.sessionState.onchange = function(old_state, new_state, name) {
if (name == "clipboard")
guac.setClipboard(new_state[name]);
else if (name == "auto-fit")
GuacUI.Client.updateDisplayScale();
};
var long_press_start_x = 0;
var long_press_start_y = 0;
var longPressTimeout = null;
GuacUI.Client.startLongPressDetect = function() {
if (!longPressTimeout) {
longPressTimeout = window.setTimeout(function() {
longPressTimeout = null;
// If screen shrunken, show magnifier
if (GuacUI.Client.attachedClient.getScale() < 1.0)
GuacUI.StateManager.setState(GuacUI.Client.states.MAGNIFIER);
// Otherwise, if screen too big to fit, use panning mode
else if (
GuacUI.Client.attachedClient.getWidth() > window.innerWidth
|| GuacUI.Client.attachedClient.getHeight() > window.innerHeight
)
GuacUI.StateManager.setState(GuacUI.Client.states.PAN);
// Otherwise, just show a hint
else
GuacUI.StateManager.setState(GuacUI.Client.states.WAIT_TYPING);
}, GuacUI.Client.LONG_PRESS_DETECT_TIMEOUT);
}
};
GuacUI.Client.stopLongPressDetect = function() {
window.clearTimeout(longPressTimeout);
longPressTimeout = null;
};
// Detect long-press at bottom of screen
GuacUI.Client.display.addEventListener('touchstart', function(e) {
// Record touch location
if (e.touches.length == 1) {
var touch = e.touches[0];
long_press_start_x = touch.screenX;
long_press_start_y = touch.screenY;
}
// Start detection
GuacUI.Client.startLongPressDetect();
}, true);
// Stop detection if touch moves significantly
GuacUI.Client.display.addEventListener('touchmove', function(e) {
// If touch distance from start exceeds threshold, cancel long press
var touch = e.touches[0];
if (Math.abs(touch.screenX - long_press_start_x) >= GuacUI.Client.LONG_PRESS_MOVEMENT_THRESHOLD
|| Math.abs(touch.screenY - long_press_start_y) >= GuacUI.Client.LONG_PRESS_MOVEMENT_THRESHOLD)
GuacUI.Client.stopLongPressDetect();
}, true);
// Stop detection if press stops
GuacUI.Client.display.addEventListener('touchend', GuacUI.Client.stopLongPressDetect, true);
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
This software is licensed under the MIT/X11 license.
MIT/X11 license
---------------
Copyright &copy; 2011 [Eli Grey][1].
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
[1]: http://eligrey.com

View File

@ -0,0 +1,178 @@
/* Blob.js
* A Blob implementation.
* 2013-06-20
*
* By Eli Grey, http://eligrey.com
* By Devin Samarin, https://github.com/eboyjr
* License: X11/MIT
* See LICENSE.md
*/
/*global self, unescape */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
plusplus: true */
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
if (typeof Blob !== "function" || typeof URL === "undefined")
if (typeof Blob === "function" && typeof webkitURL !== "undefined") var URL = webkitURL;
else var Blob = (function (view) {
"use strict";
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) {
var
get_class = function(object) {
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
}
, FakeBlobBuilder = function BlobBuilder() {
this.data = [];
}
, FakeBlob = function Blob(data, type, encoding) {
this.data = data;
this.size = data.length;
this.type = type;
this.encoding = encoding;
}
, FBB_proto = FakeBlobBuilder.prototype
, FB_proto = FakeBlob.prototype
, FileReaderSync = view.FileReaderSync
, FileException = function(type) {
this.code = this[this.name = type];
}
, file_ex_codes = (
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
).split(" ")
, file_ex_code = file_ex_codes.length
, real_URL = view.URL || view.webkitURL || view
, real_create_object_URL = real_URL.createObjectURL
, real_revoke_object_URL = real_URL.revokeObjectURL
, URL = real_URL
, btoa = view.btoa
, atob = view.atob
, can_apply_typed_arrays = false
, can_apply_typed_arrays_test = function(pass) {
can_apply_typed_arrays = !pass;
}
, ArrayBuffer = view.ArrayBuffer
, Uint8Array = view.Uint8Array
;
FakeBlob.fake = FB_proto.fake = true;
while (file_ex_code--) {
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
}
try {
if (Uint8Array) {
can_apply_typed_arrays_test.apply(0, new Uint8Array(1));
}
} catch (ex) {}
if (!real_URL.createObjectURL) {
URL = view.URL = {};
}
URL.createObjectURL = function(blob) {
var
type = blob.type
, data_URI_header
;
if (type === null) {
type = "application/octet-stream";
}
if (blob instanceof FakeBlob) {
data_URI_header = "data:" + type;
if (blob.encoding === "base64") {
return data_URI_header + ";base64," + blob.data;
} else if (blob.encoding === "URI") {
return data_URI_header + "," + decodeURIComponent(blob.data);
} if (btoa) {
return data_URI_header + ";base64," + btoa(blob.data);
} else {
return data_URI_header + "," + encodeURIComponent(blob.data);
}
} else if (real_create_object_URL) {
return real_create_object_URL.call(real_URL, blob);
}
};
URL.revokeObjectURL = function(object_URL) {
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
real_revoke_object_URL.call(real_URL, object_URL);
}
};
FBB_proto.append = function(data/*, endings*/) {
var bb = this.data;
// decode data to a binary string
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
if (can_apply_typed_arrays) {
bb.push(String.fromCharCode.apply(String, new Uint8Array(data)));
} else {
var
str = ""
, buf = new Uint8Array(data)
, i = 0
, buf_len = buf.length
;
for (; i < buf_len; i++) {
str += String.fromCharCode(buf[i]);
}
}
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
if (FileReaderSync) {
var fr = new FileReaderSync;
bb.push(fr.readAsBinaryString(data));
} else {
// async FileReader won't work as BlobBuilder is sync
throw new FileException("NOT_READABLE_ERR");
}
} else if (data instanceof FakeBlob) {
if (data.encoding === "base64" && atob) {
bb.push(atob(data.data));
} else if (data.encoding === "URI") {
bb.push(decodeURIComponent(data.data));
} else if (data.encoding === "raw") {
bb.push(data.data);
}
} else {
if (typeof data !== "string") {
data += ""; // convert unsupported types to strings
}
// decode UTF-16 to binary string
bb.push(unescape(encodeURIComponent(data)));
}
};
FBB_proto.getBlob = function(type) {
if (!arguments.length) {
type = null;
}
return new FakeBlob(this.data.join(""), type, "raw");
};
FBB_proto.toString = function() {
return "[object BlobBuilder]";
};
FB_proto.slice = function(start, end, type) {
var args = arguments.length;
if (args < 3) {
type = null;
}
return new FakeBlob(
this.data.slice(start, args > 1 ? end : this.data.length)
, type
, this.encoding
);
};
FB_proto.toString = function() {
return "[object Blob]";
};
return FakeBlobBuilder;
}(view));
return function Blob(blobParts, options) {
var type = options ? (options.type || "") : "";
var builder = new BlobBuilder();
if (blobParts) {
for (var i = 0, len = blobParts.length; i < len; i++) {
builder.append(blobParts[i]);
}
}
return builder.getBlob(type);
};
}(self));

View File

@ -0,0 +1,30 @@
This software is licensed under the MIT/X11 license.
MIT/X11 license
---------------
Copyright &copy; 2011 [Eli Grey][1].
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
[1]: http://eligrey.com

View File

@ -0,0 +1,216 @@
/* FileSaver.js
* A saveAs() FileSaver implementation.
* 2013-01-23
*
* By Eli Grey, http://eligrey.com
* License: X11/MIT
* See LICENSE.md
*/
/*global self */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
plusplus: true */
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs = saveAs
|| (navigator.msSaveBlob && navigator.msSaveBlob.bind(navigator))
|| (function(view) {
"use strict";
var
doc = view.document
// only get URL when necessary in case BlobBuilder.js hasn't overridden it yet
, get_URL = function() {
return view.URL || view.webkitURL || view;
}
, URL = view.URL || view.webkitURL || view
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
, can_use_save_link = "download" in save_link
, click = function(node) {
var event = doc.createEvent("MouseEvents");
event.initMouseEvent(
"click", true, false, view, 0, 0, 0, 0, 0
, false, false, false, false, 0, null
);
node.dispatchEvent(event);
}
, webkit_req_fs = view.webkitRequestFileSystem
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
, throw_outside = function (ex) {
(view.setImmediate || view.setTimeout)(function() {
throw ex;
}, 0);
}
, force_saveable_type = "application/octet-stream"
, fs_min_size = 0
, deletion_queue = []
, process_deletion_queue = function() {
var i = deletion_queue.length;
while (i--) {
var file = deletion_queue[i];
if (typeof file === "string") { // file is an object URL
URL.revokeObjectURL(file);
} else { // file is a File
file.remove();
}
}
deletion_queue.length = 0; // clear queue
}
, dispatch = function(filesaver, event_types, event) {
event_types = [].concat(event_types);
var i = event_types.length;
while (i--) {
var listener = filesaver["on" + event_types[i]];
if (typeof listener === "function") {
try {
listener.call(filesaver, event || filesaver);
} catch (ex) {
throw_outside(ex);
}
}
}
}
, FileSaver = function(blob, name) {
// First try a.download, then web filesystem, then object URLs
var
filesaver = this
, type = blob.type
, blob_changed = false
, object_url
, target_view
, get_object_url = function() {
var object_url = get_URL().createObjectURL(blob);
deletion_queue.push(object_url);
return object_url;
}
, dispatch_all = function() {
dispatch(filesaver, "writestart progress write writeend".split(" "));
}
// on any filesys errors revert to saving with object URLs
, fs_error = function() {
// don't create more object URLs than needed
if (blob_changed || !object_url) {
object_url = get_object_url(blob);
}
if (target_view) {
target_view.location.href = object_url;
} else {
window.open(object_url, "_blank");
}
filesaver.readyState = filesaver.DONE;
dispatch_all();
}
, abortable = function(func) {
return function() {
if (filesaver.readyState !== filesaver.DONE) {
return func.apply(this, arguments);
}
};
}
, create_if_not_found = {create: true, exclusive: false}
, slice
;
filesaver.readyState = filesaver.INIT;
if (!name) {
name = "download";
}
if (can_use_save_link) {
object_url = get_object_url(blob);
save_link.href = object_url;
save_link.download = name;
click(save_link);
filesaver.readyState = filesaver.DONE;
dispatch_all();
return;
}
// Object and web filesystem URLs have a problem saving in Google Chrome when
// viewed in a tab, so I force save with application/octet-stream
// http://code.google.com/p/chromium/issues/detail?id=91158
if (view.chrome && type && type !== force_saveable_type) {
slice = blob.slice || blob.webkitSlice;
blob = slice.call(blob, 0, blob.size, force_saveable_type);
blob_changed = true;
}
// Since I can't be sure that the guessed media type will trigger a download
// in WebKit, I append .download to the filename.
// https://bugs.webkit.org/show_bug.cgi?id=65440
if (webkit_req_fs && name !== "download") {
name += ".download";
}
if (type === force_saveable_type || webkit_req_fs) {
target_view = view;
}
if (!req_fs) {
fs_error();
return;
}
fs_min_size += blob.size;
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
var save = function() {
dir.getFile(name, create_if_not_found, abortable(function(file) {
file.createWriter(abortable(function(writer) {
writer.onwriteend = function(event) {
target_view.location.href = file.toURL();
deletion_queue.push(file);
filesaver.readyState = filesaver.DONE;
dispatch(filesaver, "writeend", event);
};
writer.onerror = function() {
var error = writer.error;
if (error.code !== error.ABORT_ERR) {
fs_error();
}
};
"writestart progress write abort".split(" ").forEach(function(event) {
writer["on" + event] = filesaver["on" + event];
});
writer.write(blob);
filesaver.abort = function() {
writer.abort();
filesaver.readyState = filesaver.DONE;
};
filesaver.readyState = filesaver.WRITING;
}), fs_error);
}), fs_error);
};
dir.getFile(name, {create: false}, abortable(function(file) {
// delete file if it already exists
file.remove();
save();
}), abortable(function(ex) {
if (ex.code === ex.NOT_FOUND_ERR) {
save();
} else {
fs_error();
}
}));
}), fs_error);
}), fs_error);
}
, FS_proto = FileSaver.prototype
, saveAs = function(blob, name) {
return new FileSaver(blob, name);
}
;
FS_proto.abort = function() {
var filesaver = this;
filesaver.readyState = filesaver.DONE;
dispatch(filesaver, "abort");
};
FS_proto.readyState = FS_proto.INIT = 0;
FS_proto.WRITING = 1;
FS_proto.DONE = 2;
FS_proto.error =
FS_proto.onwritestart =
FS_proto.onprogress =
FS_proto.onwrite =
FS_proto.onabort =
FS_proto.onerror =
FS_proto.onwriteend =
null;
view.addEventListener("unload", process_deletion_queue, false);
return saveAs;
}(self));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Maintains state across multiple Guacamole pages via HTML5 Web Storage.
* @constructor
*/
function GuacamoleSessionState() {
/**
* Reference to this GuacamoleSessionState.
* @private
*/
var guac_state = this;
/**
* The last read state object.
* @private
*/
var state = localStorage.getItem("GUACAMOLE_STATE") || {};
/**
* Reloads the internal state, sending onchange events for all changed,
* deleted, or new properties.
*/
this.reload = function() {
// Pull current state
var new_state = JSON.parse(localStorage.getItem("GUACAMOLE_STATE") || "{}");
// Assign new state
var old_state = state;
state = new_state;
// Check if any values are different
for (var name in new_state) {
// If value changed, call handler
var old = old_state[name];
if (old != new_state[name]) {
// Call change handler
if (guac_state.onchange)
guac_state.onchange(state, new_state, name);
}
}
};
/**
* Sets the given property to the given value.
*
* @param {String} name The name of the property to change.
* @param value An arbitrary value.
*/
this.setProperty = function(name, value) {
state[name] = value;
localStorage.setItem("GUACAMOLE_STATE", JSON.stringify(state));
};
/**
* Returns the value stored under the property having the given name.
*
* @param {String} name The name of the property to read.
* @return The value of the given property.
*/
this.getProperty = function(name) {
return state[name];
};
/**
* Event which is fired whenever a property value is changed externally.
*
* @event
* @param old_state An object whose properties' values are the old values
* of this GuacamoleSessionState.
* @param new_state An object whose properties' values are the new values
* of this GuacamoleSessionState.
* @param {String} name The name of the property that is being changed.
*/
this.onchange = null;
// Reload when modified
window.addEventListener("storage", guac_state.reload, false);
// Initial load
guac_state.reload();
}

View File

@ -33,6 +33,103 @@ img {
overflow: hidden;
}
.guac-error .software-cursor {
cursor: default;
}
* {
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
.event-target {
position: fixed;
opacity: 0;
}
/* Dialogs */
div.dialogOuter {
display: table;
height: 100%;
width: 100%;
position: fixed;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.75);
}
div.dialogMiddle {
width: 100%;
text-align: center;
display: table-cell;
vertical-align: middle;
}
div.dialog {
padding: 1em;
max-width: 75%;
text-align: left;
display: inline-block;
}
div.dialog h1 {
margin: 0;
margin-bottom: 0.25em;
text-align: center;
}
div.dialog div.buttons {
margin: 0;
margin-top: 0.5em;
text-align: center;
}
button {
border-style: solid;
border-width: 1px;
padding: 0.25em;
padding-right: 1em;
padding-left: 1em;
}
button:active {
padding-top: 0.35em;
padding-left: 1.1em;
padding-bottom: 0.15em;
padding-right: 0.9em;
}
button#reconnect {
display: none;
}
.guac-error button#reconnect {
display: inline;
background: #200;
border-color: #822;
color: #944;
}
.guac-error button#reconnect:hover {
background: #822;
border-color: #B33;
color: black;
}
div.dialog p {
margin: 0;
}
div.displayOuter {
height: 100%;
width: 100%;
@ -58,3 +155,266 @@ div#display > * {
margin-right: auto;
}
div.magnifier-background {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
}
div.magnifier {
position: absolute;
left: 0;
top: 0;
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.75);
width: 50%;
height: 50%;
overflow: hidden;
}
.pan-overlay,
.type-overlay {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.pan-overlay .indicator {
position: fixed;
background-size: 32px 32px;
-moz-background-size: 32px 32px;
-webkit-background-size: 32px 32px;
-khtml-background-size: 32px 32px;
background-position: center;
background-repeat: no-repeat;
opacity: 0.8;
}
.pan-overlay .indicator.up {
top: 0;
left: 0;
right: 0;
height: 32px;
background-image: url('../images/arrows/arrows-u.png');
}
.pan-overlay .indicator.down {
bottom: 0;
left: 0;
right: 0;
height: 32px;
background-image: url('../images/arrows/arrows-d.png');
}
.pan-overlay .indicator.left {
top: 0;
bottom: 0;
left: 0;
width: 32px;
background-image: url('../images/arrows/arrows-l.png');
}
.pan-overlay .indicator.right {
top: 0;
bottom: 0;
right: 0;
width: 32px;
background-image: url('../images/arrows/arrows-r.png');
}
/* Viewport Clone */
div#viewportClone {
display: table;
height: 100%;
width: 100%;
position: fixed;
left: 0;
top: 0;
visibility: hidden;
}
.status {
text-shadow: 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black;
font-size: xx-large;
color: white;
}
.guac-error .status {
text-shadow: 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black, 0 0 0.25em black;
color: #D44;
}
p.hint {
border: 0.25em solid rgba(255, 255, 255, 0.25);
background: black;
opacity: 0.75;
color: white;
max-width: 10em;
padding: 1em;
margin: 1em;
position: absolute;
left: 0;
top: 0;
box-shadow: 0.25em 0.25em 0.25em rgba(0, 0, 0, 0.75);
}
#notificationArea {
position: fixed;
right: 0.5em;
bottom: 0.5em;
max-width: 25%;
min-width: 10em;
}
.notification {
font-size: 0.9em;
border: 1px solid rgba(255, 255, 255, 0.25);
background: black;
opacity: 0.9;
color: white;
padding: 0.5em;
margin: 1em;
overflow: hidden;
box-shadow: 0.25em 0.25em 0.25em rgba(0, 0, 0, 0.75);
}
.notification div {
display: inline-block;
}
.notification .title-bar {
display: block;
white-space: nowrap;
font-weight: bold;
border-bottom: 1px solid white;
padding-bottom: 0.5em;
margin-bottom: 0.5em;
}
.notification .title-bar * {
vertical-align: middle;
}
.notification .caption {
color: silver;
}
.notification .close {
background: url('../images/action-icons/guac-close.png');
background-size: 10px 10px;
-moz-background-size: 10px 10px;
-webkit-background-size: 10px 10px;
-khtml-background-size: 10px 10px;
width: 10px;
height: 10px;
float: right;
cursor: pointer;
}
@keyframes progress {
from {background-position: 0px 0px;}
to {background-position: 64px 0px;}
}
@-webkit-keyframes progress {
from {background-position: 0px 0px;}
to {background-position: 64px 0px;}
}
.download.notification .caption {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.download.notification .progress,
.download.notification .download {
margin-top: 1em;
margin-left: 0.75em;
padding: 0.25em;
min-width: 5em;
border: 1px solid gray;
border-radius: 0.2em;
text-align: center;
float: right;
}
.download.notification .progress {
background: #444 url('../images/progress.png');
background-size: 16px 16px;
-moz-background-size: 16px 16px;
-webkit-background-size: 16px 16px;
-khtml-background-size: 16px 16px;
animation-name: progress;
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
-webkit-animation-name: progress;
-webkit-animation-duration: 2s;
-webkit-animation-timing-function: linear;
-webkit-animation-iteration-count: infinite;
}
.download.notification .download {
background: rgb(16, 87, 153);
cursor: pointer;
}
#preload {
visibility: hidden;
position: absolute;
left: 0;
right: 0;
width: 0;
height: 0;
overflow: hidden;
}