1 <?xml version="1.0" encoding="UTF-8"?>
5 Guacamole - Clientless Remote Desktop
6 Copyright (C) 2010 Michael Jumper
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU Affero General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Affero General Public License for more details.
18 You should have received a copy of the GNU Affero General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 <html xmlns="http://www.w3.org/1999/xhtml">
25 <link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
26 <link rel="stylesheet" type="text/css" href="styles/guacamole.css"/>
27 <link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
28 <title>Guacamole</title>
34 <div id="login-dialog-middle">
36 <div id="login-dialog">
38 <p id="login-error"></p>
40 <form id="login-form" action="#" method="post">
42 <div id="login-fields">
46 <td><input type="text" name="username" id="username" autofocus="autofocus"/></td>
50 <td><input type="password" name="password" id="password"/></td>
54 <img class="logo" src="images/guacamole-logo-64.png" alt=""/>
58 <input type="submit" name="login" id="login" value="Login"/>
64 <div id="version-dialog">
65 Guacamole ${project.version}
71 <!-- Main UI - hidden until login succeeds -->
72 <div id="main-guacamole-ui" style="display: none">
78 <button id="showClipboard">Show Clipboard</button>
79 <div id="clipboardDiv">
82 Text copied/cut within Guacamole will appear here. Changes to the text will affect the remote clipboard, and will be pastable within the remote desktop. Use the textbox below as an interface between the client and server clipboards.
84 <textarea rows="10" cols="40" id="clipboard"></textarea>
87 <button id="showKeyboard">Show Keyboard</button>
88 <button id="ctrlAltDelete">Ctrl-Alt-Delete</button>
90 <!-- Logo and status -->
91 <img id="status-logo" class="logo" src="images/guacamole-logo-24.png" alt="Guacamole" title="Guacamole ${project.version}"/>
92 <span id="state"></span>
94 <a href="agpl-3.0-standalone.html"><img id="license" src="images/agpl-logo.png" alt="AGPLv3"/></a>
99 <div id="display" class="guac-display guac-loading">
100 <!-- On-screen keyboard -->
101 <div id="keyboardContainer"></div>
106 <div id="errorDialog" class="errorDialogOuter">
107 <div class="errorDialogMiddle">
108 <div class="errorDialog">
109 <p id="errorText"></p>
110 <div class="buttons"><button id="reconnect">Reconnect</button></div>
118 <script type="text/javascript" src="guacamole-common-js/keyboard.js"></script>
119 <script type="text/javascript" src="guacamole-common-js/mouse.js"></script>
120 <script type="text/javascript" src="guacamole-common-js/layer.js"></script>
121 <script type="text/javascript" src="guacamole-common-js/tunnel.js"></script>
122 <script type="text/javascript" src="guacamole-common-js/guacamole.js"></script>
123 <script type="text/javascript" src="guacamole-common-js/oskeyboard.js"></script>
126 <script type="text/javascript"> /* <![CDATA[ */
128 var loginForm = document.getElementById("login-form");
129 var loginUI = document.getElementById("login-ui");
130 var display = document.getElementById("display");
132 loginForm.onsubmit = function() {
134 var username = document.getElementById("username");
135 var password = document.getElementById("password");
138 "username=" + encodeURIComponent(username.value)
139 + "&password=" + encodeURIComponent(password.value)
141 // Instantiate client
142 var guac = new GuacamoleClient(
144 new GuacamoleHTTPTunnel("tunnel")
155 var loginError = document.getElementById("login-error");
157 // Display error, reset and refocus password field
158 loginError.textContent = e.message;
166 // On success, display UI
167 startGuacamole(guac);
172 // Shows guacamole interface and initiates connection to guacamole
173 function startGuacamole(guac) {
175 loginUI.style.display = "none";
176 document.getElementById("main-guacamole-ui").style.display = "block";
178 var menu = document.getElementById("menu");
179 var logo = document.getElementById("status-logo");
181 var errorDialog = document.getElementById("errorDialog");
182 var errorDialogText = document.getElementById("errorText");
184 // Position display correctly
185 window.onresize = function() {
186 display.style.top = menu.offsetHeight + "px";
191 var state = document.getElementById("state");
192 guac.onstatechange = function(clientState) {
194 switch (clientState) {
196 state.textContent = "Idle."
199 state.textContent = "Connecting...";
202 state.textContent = "Connected, waiting for first update...";
205 display.className = display.className.replace(/guac-loading/, '');
206 menu.className = "connected";
207 state.textContent = "Connected.";
210 state.textContent = "Disconnecting...";
213 state.textContent = "Disconnected.";
216 state.textContent = "Unknown";
220 // Cache error image (might not be available when error occurs)
221 var guacErrorImage = new Image();
222 guacErrorImage.src = "images/noguacamole-logo-24.png";
224 guac.onname = function(name) {
225 document.title = name;
228 guac.onerror = function(error) {
232 menu.className = "error";
233 display.className += " guac-error";
235 logo.src = guacErrorImage.src;
236 errorDialogText.textContent = error;
237 errorDialog.style.visibility = "visible";
239 // Show error by desaturating display
240 var layers = guac.getLayers();
241 for (var i=0; i<layers.length; i++) {
242 layers[i].filter(desaturateFilter);
245 // Filter for desaturation
246 function desaturateFilter(data, width, height) {
248 for (var i=0; i<data.length; i+=4) {
256 var v = Math.max(r, g, b) / 2;
268 var mouse = new GuacamoleMouse(display);
269 mouse.setButtonPressedHandler(
270 function(mouseState) {
271 guac.sendMouseState(mouseState);
275 mouse.setButtonReleasedHandler(
276 function(mouseState) {
277 guac.sendMouseState(mouseState);
281 mouse.setMovementHandler(
282 function(mouseState) {
283 guac.sendMouseState(mouseState);
288 var keyboard = new GuacamoleKeyboard(document);
290 function disableKeyboard() {
291 keyboard.setKeyPressedHandler(null);
292 keyboard.setKeyReleasedHandler(null);
295 function enableKeyboard() {
296 keyboard.setKeyPressedHandler(
298 guac.sendKeyEvent(1, keysym);
302 keyboard.setKeyReleasedHandler(
304 guac.sendKeyEvent(0, keysym);
309 // Enable keyboard by default
313 var reconnect = document.getElementById("reconnect");
314 reconnect.onclick = function() {
315 window.location.reload();
318 // Disconnect on close
319 window.onunload = function() {
323 // Handle clipboard events
324 var clipboardElement = document.getElementById("clipboard");
325 clipboardElement.onchange = function() {
327 var text = clipboardElement.value;
328 guac.setClipboard(text);
332 // Ignore keypresses when clipboard is focused
333 clipboardElement.onfocus = function() {
337 // Capture keypresses when clipboard is not focused
338 clipboardElement.onblur = function() {
342 // Server copy handler
343 guac.onclipboard = function(data) {
344 clipboardElement.value = data;
348 // Show/Hide clipboard
349 var clipboardDiv = document.getElementById("clipboardDiv");
350 var showClipboard = document.getElementById("showClipboard");
351 showClipboard.onclick = function() {
353 var displayed = clipboardDiv.style.display;
354 if (displayed != "block") {
355 clipboardDiv.style.display = "block";
356 showClipboard.innerHTML = "Hide Clipboard";
359 clipboardDiv.style.display = "none";
360 showClipboard.innerHTML = "Show Clipboard";
361 clipboardElement.onchange();
367 // Show/Hide keyboard
368 var keyboardContainer = document.getElementById("keyboardContainer");
369 var showKeyboard = document.getElementById("showKeyboard");
370 showKeyboard.onclick = function() {
372 var displayed = keyboardContainer.style.display;
373 if (displayed != "block") {
374 keyboardContainer.style.display = "block";
375 showKeyboard.textContent = "Hide Keyboard";
378 keyboardContainer.style.display = "none";
379 showKeyboard.textContent = "Show Keyboard";
384 // On-screen keyboard
385 var osKeyboard = new GuacamoleOnScreenKeyboard("layouts/en-us-qwerty.xml");
386 keyboardContainer.appendChild(osKeyboard);
388 osKeyboard.setKeyPressedHandler(
390 guac.sendKeyEvent(1, keysym);
394 osKeyboard.setKeyReleasedHandler(
396 guac.sendKeyEvent(0, keysym);
400 // Send Ctrl-Alt-Delete
401 var ctrlAltDelete = document.getElementById("ctrlAltDelete");
403 ctrlAltDelete.onclick = function() {
405 var KEYSYM_CTRL = 0xFF03;
406 var KEYSYM_ALT = 0xFFE9;
407 var KEYSYM_DELETE = 0xFFFF;
409 guac.sendKeyEvent(1, KEYSYM_CTRL);
410 guac.sendKeyEvent(1, KEYSYM_ALT);
411 guac.sendKeyEvent(1, KEYSYM_DELETE);
412 guac.sendKeyEvent(0, KEYSYM_DELETE);
413 guac.sendKeyEvent(0, KEYSYM_ALT);
414 guac.sendKeyEvent(0, KEYSYM_CTRL);