4 Guacamole - Clientless Remote Desktop
5 Copyright (C) 2010 Michael Jumper
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU Affero General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Affero General Public License for more details.
17 You should have received a copy of the GNU Affero General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 <link rel="icon" type="image/png" href="images/guacamole-logo-64.png"/>
25 <link rel="stylesheet" type="text/css" href="styles/guacamole.css"/>
26 <link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
27 <title>Guacamole</title>
33 <div id="login-dialog-middle">
35 <div id="login-dialog">
37 <p id="login-error"></p>
39 <form id="login-form" action="#" method="post">
41 <div id="login-fields">
45 <td><input type="text" name="username" id="username"/></td>
49 <td><input type="password" name="password" id="password"/></td>
53 <img class="logo" src="images/guacamole-logo-64.png" alt=""/>
57 <input type="submit" name="login" id="login" value="Login"/>
63 <div id="version-dialog">
64 Guacamole ${project.version}
70 <!-- Main UI - hidden until login succeeds -->
71 <div id="main-guacamole-ui" style="display: none">
77 <button id="showClipboard">Show Clipboard</button>
78 <div id="clipboardDiv">
81 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.
83 <textarea rows="10" cols="40" id="clipboard"></textarea>
86 <button id="showKeyboard">Show Keyboard</button>
87 <button id="ctrlAltDelete">Ctrl-Alt-Delete</button>
89 <!-- Logo and status -->
90 <img id="status-logo" class="logo" src="images/guacamole-logo-24.png" alt="Guacamole" title="Guacamole ${project.version}"/>
91 <span id="state"></span>
93 <a href="agpl-3.0-standalone.html"><img id="license" src="images/agpl-logo.png" alt="AGPLv3"/></a>
98 <div id="display" class="guac-display guac-loading">
99 <!-- On-screen keyboard -->
100 <div id="keyboardContainer"></div>
105 <div id="errorDialog" class="errorDialogOuter">
106 <div class="errorDialogMiddle">
107 <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.setOnStateChangeHandler(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.setNameHandler(function(name) {
225 document.title = name;
228 guac.setErrorHandler(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.setClipboardHandler(
345 clipboardElement.value = data;
350 // Show/Hide clipboard
351 var clipboardDiv = document.getElementById("clipboardDiv");
352 var showClipboard = document.getElementById("showClipboard");
353 showClipboard.onclick = function() {
355 var displayed = clipboardDiv.style.display;
356 if (displayed != "block") {
357 clipboardDiv.style.display = "block";
358 showClipboard.innerHTML = "Hide Clipboard";
361 clipboardDiv.style.display = "none";
362 showClipboard.innerHTML = "Show Clipboard";
363 clipboardElement.onchange();
369 // Show/Hide keyboard
370 var keyboardContainer = document.getElementById("keyboardContainer");
371 var showKeyboard = document.getElementById("showKeyboard");
372 showKeyboard.onclick = function() {
374 var displayed = keyboardContainer.style.display;
375 if (displayed != "block") {
376 keyboardContainer.style.display = "block";
377 showKeyboard.textContent = "Hide Keyboard";
380 keyboardContainer.style.display = "none";
381 showKeyboard.textContent = "Show Keyboard";
386 // On-screen keyboard
387 var osKeyboard = new GuacamoleOnScreenKeyboard("layouts/en-us-qwerty.xml");
388 keyboardContainer.appendChild(osKeyboard);
390 osKeyboard.setKeyPressedHandler(
392 guac.sendKeyEvent(1, keysym);
396 osKeyboard.setKeyReleasedHandler(
398 guac.sendKeyEvent(0, keysym);
402 // Send Ctrl-Alt-Delete
403 var ctrlAltDelete = document.getElementById("ctrlAltDelete");
405 ctrlAltDelete.onclick = function() {
407 var KEYSYM_CTRL = 0xFF03;
408 var KEYSYM_ALT = 0xFFE9;
409 var KEYSYM_DELETE = 0xFFFF;
411 guac.sendKeyEvent(1, KEYSYM_CTRL);
412 guac.sendKeyEvent(1, KEYSYM_ALT);
413 guac.sendKeyEvent(1, KEYSYM_DELETE);
414 guac.sendKeyEvent(0, KEYSYM_DELETE);
415 guac.sendKeyEvent(0, KEYSYM_ALT);
416 guac.sendKeyEvent(0, KEYSYM_CTRL);