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
@ -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));
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 690 B |
After Width: | Height: | Size: 707 B |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 648 B |
After Width: | Height: | Size: 971 B |
BIN
guacamole-tunnel/src/main/webapp/images/arrows/arrows-d.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
guacamole-tunnel/src/main/webapp/images/arrows/arrows-l.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
guacamole-tunnel/src/main/webapp/images/arrows/arrows-r.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
guacamole-tunnel/src/main/webapp/images/arrows/arrows-u.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
guacamole-tunnel/src/main/webapp/images/mouse/blank.cur
Normal file
After Width: | Height: | Size: 584 B |
BIN
guacamole-tunnel/src/main/webapp/images/mouse/blank.gif
Normal file
After Width: | Height: | Size: 71 B |
BIN
guacamole-tunnel/src/main/webapp/images/mouse/dot.gif
Normal file
After Width: | Height: | Size: 72 B |
@ -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>
|
||||
|
||||
|
312
guacamole-tunnel/src/main/webapp/layouts/en-us-qwerty-mobile.xml
Normal 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">&</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"><</cap>
|
||||
<cap if="shift">Z</cap>
|
||||
<cap if="numsym,shift">z</cap>
|
||||
</key>
|
||||
<gap size="0.1"/>
|
||||
<key>
|
||||
<cap>x</cap>
|
||||
<cap if="numsym">></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>
|
496
guacamole-tunnel/src/main/webapp/layouts/en-us-qwerty.xml
Normal 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">&</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"><</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="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">↑</cap>
|
||||
</key>
|
||||
</row>
|
||||
<row><gap size="0.1"/></row>
|
||||
<row>
|
||||
<key>
|
||||
<cap keysym="0xFF51">←</cap>
|
||||
</key>
|
||||
<gap size="0.1"/>
|
||||
<key>
|
||||
<cap keysym="0xFF54">↓</cap>
|
||||
</key>
|
||||
<gap size="0.1"/>
|
||||
<key>
|
||||
<cap keysym="0xFF53">→</cap>
|
||||
</key>
|
||||
</row>
|
||||
</column>
|
||||
</keyboard>
|
1472
guacamole-tunnel/src/main/webapp/scripts/admin-ui.js
Normal file
995
guacamole-tunnel/src/main/webapp/scripts/client-ui.js
Normal 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);
|
||||
|
||||
};
|
||||
|
1376
guacamole-tunnel/src/main/webapp/scripts/guac-ui.js
Normal file
30
guacamole-tunnel/src/main/webapp/scripts/lib/blob/LICENSE.md
Normal file
@ -0,0 +1,30 @@
|
||||
This software is licensed under the MIT/X11 license.
|
||||
|
||||
MIT/X11 license
|
||||
---------------
|
||||
|
||||
Copyright © 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
|
178
guacamole-tunnel/src/main/webapp/scripts/lib/blob/blob.js
Normal 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));
|
@ -0,0 +1,30 @@
|
||||
This software is licensed under the MIT/X11 license.
|
||||
|
||||
MIT/X11 license
|
||||
---------------
|
||||
|
||||
Copyright © 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
|
@ -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));
|
1398
guacamole-tunnel/src/main/webapp/scripts/service.js
Normal file
107
guacamole-tunnel/src/main/webapp/scripts/session.js
Normal 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();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|