Merge branch 'master' of ssh://guacamole.git.sourceforge.net/gitroot/guacamole/guacamole

This commit is contained in:
Michael Jumper
2011-01-24 14:16:10 -08:00
22 changed files with 96 additions and 1856 deletions

View File

@@ -1 +1,2 @@
target/
*~

View File

@@ -20,10 +20,7 @@
guacd-hostname: localhost
guacd-port: 4822
# Client provider class (provides and configures a guacamole client based on authentication information)
client-provider: net.sourceforge.guacamole.net.authentication.basic.BasicGuacamoleClientProvider
# Auth provider class (authenticates user/pass combination, needed if using the provided login screen)
auth-provider: net.sourceforge.guacamole.net.authentication.basic.BasicFileAuthenticationProvider
auth-provider: net.sourceforge.guacamole.net.basic.BasicFileAuthenticationProvider
basic-user-mapping: /path/to/user-mapping.xml

View File

@@ -9,8 +9,13 @@
<name>guacamole-default-webapp</name>
<url>http://guacamole.sourceforge.net/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -19,6 +24,21 @@
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>net.sourceforge.guacamole</groupId>
<artifactId>guacamole-common-js</artifactId>
<type>zip</type>
</overlay>
</overlays>
</configuration>
</plugin>
</plugins>
<extensions>
@@ -47,6 +67,14 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.guacamole</groupId>
<artifactId>guacamole-common-js</artifactId>
<version>0.3.0-SNAPSHOT</version>
<type>zip</type>
<scope>runtime</scope>
</dependency>
</dependencies>
<repositories>

View File

@@ -1,5 +1,5 @@
package net.sourceforge.guacamole.net.authentication.basic;
package net.sourceforge.guacamole.net.basic;
/*
* Guacamole - Clientless Remote Desktop

View File

@@ -1,12 +1,4 @@
package net.sourceforge.guacamole.net.authentication.basic;
import javax.servlet.http.HttpSession;
import net.sourceforge.guacamole.GuacamoleTCPClient;
import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.net.Configuration;
import net.sourceforge.guacamole.net.GuacamoleProperties;
import net.sourceforge.guacamole.net.authentication.GuacamoleClientProvider;
package net.sourceforge.guacamole.net.basic;
/*
* Guacamole - Clientless Remote Desktop
@@ -26,12 +18,26 @@ import net.sourceforge.guacamole.net.authentication.GuacamoleClientProvider;
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
public class BasicGuacamoleClientProvider implements GuacamoleClientProvider {
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.GuacamoleTCPClient;
import net.sourceforge.guacamole.net.Configuration;
import net.sourceforge.guacamole.net.GuacamoleProperties;
import net.sourceforge.guacamole.net.GuacamoleSession;
import net.sourceforge.guacamole.net.tunnel.GuacamoleTunnelServlet;
public GuacamoleTCPClient createClient(HttpSession session) throws GuacamoleException {
public class BasicGuacamoleTunnelServlet extends GuacamoleTunnelServlet {
@Override
protected void doConnect(HttpServletRequest request, HttpServletResponse response) throws GuacamoleException {
// Session must already exist from login
HttpSession httpSession = request.getSession(false);
// Retrieve authorized config data from session
Configuration config = (Configuration) session.getAttribute("BASIC-LOGIN-AUTH");
Configuration config = (Configuration) httpSession.getAttribute("BASIC-LOGIN-AUTH");
// If no data, not authorized
if (config == null)
@@ -43,9 +49,11 @@ public class BasicGuacamoleClientProvider implements GuacamoleClientProvider {
GuacamoleTCPClient client = new GuacamoleTCPClient(hostname, port);
client.connect(config);
// Return authorized session
return client;
// Set client for session
GuacamoleSession session = new GuacamoleSession(httpSession);
session.attachClient(client);
}
}

View File

@@ -1,5 +1,5 @@
package net.sourceforge.guacamole.net.authentication.basic;
package net.sourceforge.guacamole.net.basic;
/*
* Guacamole - Clientless Remote Desktop

View File

@@ -26,41 +26,26 @@
30
</session-timeout>
</session-config>
<!-- Guacamole Tunnel Servlets -->
<!-- Guacamole Tunnel Servlet -->
<servlet>
<description>Connect servlet.</description>
<servlet-name>Connect</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.tunnel.Connect</servlet-class>
<description>Tunnel servlet.</description>
<servlet-name>Tunnel</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.basic.BasicGuacamoleTunnelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Connect</servlet-name>
<url-pattern>/connect</url-pattern>
<servlet-name>Tunnel</servlet-name>
<url-pattern>/tunnel</url-pattern>
</servlet-mapping>
<servlet>
<description>Outbound servlet.</description>
<servlet-name>Outbound</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.tunnel.Outbound</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Outbound</servlet-name>
<url-pattern>/outbound</url-pattern>
</servlet-mapping>
<servlet>
<description>Input servlet.</description>
<servlet-name>Inbound</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.tunnel.Inbound</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Inbound</servlet-name>
<url-pattern>/inbound</url-pattern>
</servlet-mapping>
<!-- Basic Login Servlets -->
<!-- Basic Login Servlet -->
<servlet>
<servlet-name>BasicLogin</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.authentication.basic.BasicLogin</servlet-class>
<servlet-class>net.sourceforge.guacamole.net.basic.BasicLogin</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BasicLogin</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>

View File

@@ -1,37 +0,0 @@
/*
* 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/>.
*/
.guac-display.guac-loading {
border: 1px dotted gray;
background-image: url('../images/spinner92.gif');
background-position: center;
background-repeat: no-repeat;
}
.guac-display.guac-error {
border: 1px dotted red;
background-image: url('../images/noimage92.png');
background-position: center;
background-repeat: no-repeat;
}
.guac-hide-cursor {
cursor: url('../images/mouse/dot.gif'),url('../images/mouse/blank.cur'),default;
}

View File

@@ -1,601 +0,0 @@
/*
* 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/>.
*/
function GuacamoleClient(display) {
var STATE_IDLE = 0;
var STATE_CONNECTING = 1;
var STATE_WAITING = 2;
var STATE_CONNECTED = 3;
var STATE_DISCONNECTING = 4;
var STATE_DISCONNECTED = 5;
var currentState = STATE_IDLE;
var stateChangeHandler = null;
function setState(state) {
if (state != currentState) {
currentState = state;
if (stateChangeHandler)
stateChangeHandler(currentState);
}
}
this.setOnStateChangeHandler = function(handler) {
stateChangeHandler = handler;
}
function isConnected() {
return currentState == STATE_CONNECTED
|| currentState == STATE_WAITING;
}
// Layers
var background = null;
var cursor = null;
var cursorImage = null;
var cursorHotspotX = 0;
var cursorHotspotY = 0;
// FIXME: Make object. Clean up.
var cursorRectX = 0;
var cursorRectY = 0;
var cursorRectW = 0;
var cursorRectH = 0;
var cursorHidden = 0;
function redrawCursor() {
// Hide hardware cursor
if (cursorHidden == 0) {
display.className += " guac-hide-cursor";
cursorHidden = 1;
}
// Erase old cursor
cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);
// Update rect
cursorRectX = mouse.getX() - cursorHotspotX;
cursorRectY = mouse.getY() - cursorHotspotY;
cursorRectW = cursorImage.width;
cursorRectH = cursorImage.height;
// Draw new cursor
cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
}
/*****************************************/
/*** Keyboard ***/
/*****************************************/
var keyboard = new GuacamoleKeyboard(document);
this.disableKeyboard = function() {
keyboard.setKeyPressedHandler(null);
keyboard.setKeyReleasedHandler(null);
};
this.enableKeyboard = function() {
keyboard.setKeyPressedHandler(
function (keysym) {
sendKeyEvent(1, keysym);
}
);
keyboard.setKeyReleasedHandler(
function (keysym) {
sendKeyEvent(0, keysym);
}
);
};
// Enable keyboard by default
this.enableKeyboard();
function sendKeyEvent(pressed, keysym) {
// Do not send requests if not connected
if (!isConnected())
return;
sendMessage("key:" + keysym + "," + pressed + ";");
}
this.pressKey = function(keysym) {
sendKeyEvent(1, keysym);
};
this.releaseKey = function(keysym) {
sendKeyEvent(0, keysym);
};
/*****************************************/
/*** Mouse ***/
/*****************************************/
var mouse = new GuacamoleMouse(display);
mouse.setButtonPressedHandler(
function(mouseState) {
sendMouseState(mouseState);
}
);
mouse.setButtonReleasedHandler(
function(mouseState) {
sendMouseState(mouseState);
}
);
mouse.setMovementHandler(
function(mouseState) {
// Draw client-side cursor
if (cursorImage != null) {
redrawCursor();
}
sendMouseState(mouseState);
}
);
function sendMouseState(mouseState) {
// Do not send requests if not connected
if (!isConnected())
return;
// Build mask
var buttonMask = 0;
if (mouseState.getLeft()) buttonMask |= 1;
if (mouseState.getMiddle()) buttonMask |= 2;
if (mouseState.getRight()) buttonMask |= 4;
if (mouseState.getUp()) buttonMask |= 8;
if (mouseState.getDown()) buttonMask |= 16;
// Send message
sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";");
}
var sendingMessages = 0;
var outputMessageBuffer = "";
function sendMessage(message) {
// Add event to queue, restart send loop if finished.
outputMessageBuffer += message;
if (sendingMessages == 0)
sendPendingMessages();
}
function sendPendingMessages() {
if (outputMessageBuffer.length > 0) {
sendingMessages = 1;
var message_xmlhttprequest = new XMLHttpRequest();
message_xmlhttprequest.open("POST", "inbound");
message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
message_xmlhttprequest.setRequestHeader("Content-length", outputMessageBuffer.length);
// Once response received, send next queued event.
message_xmlhttprequest.onreadystatechange = function() {
if (message_xmlhttprequest.readyState == 4)
sendPendingMessages();
}
message_xmlhttprequest.send(outputMessageBuffer);
outputMessageBuffer = ""; // Clear buffer
}
else
sendingMessages = 0;
}
/*****************************************/
/*** Clipboard ***/
/*****************************************/
this.setClipboard = function(data) {
// Do not send requests if not connected
if (!isConnected())
return;
sendMessage("clipboard:" + escapeGuacamoleString(data) + ";");
}
function desaturateFilter(data, width, height) {
for (var i=0; i<data.length; i+=4) {
// Get RGB values
var r = data[i];
var g = data[i+1];
var b = data[i+2];
// Desaturate
var v = Math.max(r, g, b) / 2;
data[i] = v;
data[i+1] = v;
data[i+2] = v;
}
}
var errorHandler = null;
this.setErrorHandler = function(handler) {
errorHandler = handler;
};
var errorEncountered = 0;
function showError(error) {
// Only display first error (avoid infinite error loops)
if (errorEncountered == 0) {
errorEncountered = 1;
disconnect();
// In case nothing has been rendered yet, use error style
display.className += " guac-error";
// Show error by desaturating display
if (background)
background.filter(desaturateFilter);
if (errorHandler)
errorHandler(error);
}
}
function handleErrors(message) {
var errors = message.getErrors();
for (var errorIndex=0; errorIndex<errors.length; errorIndex++)
showError(errors[errorIndex].getMessage());
}
var clipboardHandler = null;
var requests = 0;
this.setClipboardHandler = function(handler) {
clipboardHandler = handler;
};
function handleResponse(xmlhttprequest) {
var nextRequest = null;
var instructionStart = 0;
var startIndex = 0;
function parseResponse() {
// Start next request as soon as possible
if (xmlhttprequest.readyState >= 2 && nextRequest == null)
nextRequest = makeRequest();
// Parse stream when data is received and when complete.
if (xmlhttprequest.readyState == 3 ||
xmlhttprequest.readyState == 4) {
// Halt on error during request
if (xmlhttprequest.status == 0) {
showError("Request canceled by browser.");
return;
}
else if (xmlhttprequest.status != 200) {
showError("Error during request (HTTP " + xmlhttprequest.status + "): " + xmlhttprequest.statusText);
return;
}
var current = xmlhttprequest.responseText;
var instructionEnd;
while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {
// Start next search at next instruction
startIndex = instructionEnd+1;
var instruction = current.substr(instructionStart,
instructionEnd - instructionStart);
instructionStart = startIndex;
var opcodeEnd = instruction.indexOf(":");
var opcode;
var parameters;
if (opcodeEnd == -1) {
opcode = instruction;
parameters = new Array();
}
else {
opcode = instruction.substr(0, opcodeEnd);
parameters = instruction.substr(opcodeEnd+1).split(",");
}
// If we're done parsing, handle the next response.
if (opcode.length == 0) {
if (isConnected()) {
delete xmlhttprequest;
if (nextRequest)
handleResponse(nextRequest);
}
break;
}
// Call instruction handler.
doInstruction(opcode, parameters);
}
// Start search at end of string.
startIndex = current.length;
delete instruction;
delete parameters;
}
}
xmlhttprequest.onreadystatechange = parseResponse;
parseResponse();
}
function makeRequest() {
// Download self
var xmlhttprequest = new XMLHttpRequest();
xmlhttprequest.open("POST", "outbound");
xmlhttprequest.send(null);
return xmlhttprequest;
}
function escapeGuacamoleString(str) {
var escapedString = "";
for (var i=0; i<str.length; i++) {
var c = str.charAt(i);
if (c == ",")
escapedString += "\\c";
else if (c == ";")
escapedString += "\\s";
else if (c == "\\")
escapedString += "\\\\";
else
escapedString += c;
}
return escapedString;
}
function unescapeGuacamoleString(str) {
var unescapedString = "";
for (var i=0; i<str.length; i++) {
var c = str.charAt(i);
if (c == "\\" && i<str.length-1) {
var escapeChar = str.charAt(++i);
if (escapeChar == "c")
unescapedString += ",";
else if (escapeChar == "s")
unescapedString += ";";
else if (escapeChar == "\\")
unescapedString += "\\";
else
unescapedString += "\\" + escapeChar;
}
else
unescapedString += c;
}
return unescapedString;
}
var instructionHandlers = {
"error": function(parameters) {
showError(unescapeGuacamoleString(parameters[0]));
},
"name": function(parameters) {
document.title = unescapeGuacamoleString(parameters[0]);
},
"clipboard": function(parameters) {
clipboardHandler(unescapeGuacamoleString(parameters[0]));
},
"size": function(parameters) {
var width = parseInt(parameters[0]);
var height = parseInt(parameters[1]);
// Update (set) display size
if (display && (background == null || cursor == null)) {
display.style.width = width + "px";
display.style.height = height + "px";
background = new Layer(width, height);
cursor = new Layer(width, height);
display.appendChild(background);
display.appendChild(cursor);
}
},
"rect": function(parameters) {
var x = parseInt(parameters[0]);
var y = parseInt(parameters[1]);
var w = parseInt(parameters[2]);
var h = parseInt(parameters[3]);
var color = parameters[4];
background.drawRect(
x,
y,
w,
h,
color
);
},
"png": function(parameters) {
var x = parseInt(parameters[0]);
var y = parseInt(parameters[1]);
var data = parameters[2];
background.draw(
x,
y,
"data:image/png;base64," + data
);
// If received first update, no longer waiting.
if (currentState == STATE_WAITING)
setState(STATE_CONNECTED);
},
"copy": function(parameters) {
var srcX = parseInt(parameters[0]);
var srcY = parseInt(parameters[1]);
var srcWidth = parseInt(parameters[2]);
var srcHeight = parseInt(parameters[3]);
var dstX = parseInt(parameters[4]);
var dstY = parseInt(parameters[5]);
background.copyRect(
srcX,
srcY,
srcWidth,
srcHeight,
dstX,
dstY
);
},
"cursor": function(parameters) {
var x = parseInt(parameters[0]);
var y = parseInt(parameters[1]);
var data = parameters[2];
// Start cursor image load
var image = new Image();
image.onload = function() {
cursorImage = image;
cursorHotspotX = x;
cursorHotspotY = y;
redrawCursor();
};
image.src = "data:image/png;base64," + data
}
};
function doInstruction(opcode, parameters) {
var handler = instructionHandlers[opcode];
if (handler)
handler(parameters);
}
this.connect = function() {
setState(STATE_CONNECTING);
// Start tunnel and connect synchronously
var connect_xmlhttprequest = new XMLHttpRequest();
connect_xmlhttprequest.open("POST", "connect", false);
connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
connect_xmlhttprequest.setRequestHeader("Content-length", 0);
connect_xmlhttprequest.send(null);
// Start reading data
setState(STATE_WAITING);
handleResponse(makeRequest());
};
function disconnect() {
// Only attempt disconnection not disconnected.
if (currentState != STATE_DISCONNECTED
&& currentState != STATE_DISCONNECTING) {
var message = "disconnect;";
setState(STATE_DISCONNECTING);
// Send disconnect message (synchronously... as necessary until handoff is implemented)
var disconnect_xmlhttprequest = new XMLHttpRequest();
disconnect_xmlhttprequest.open("POST", "inbound", false);
disconnect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
disconnect_xmlhttprequest.setRequestHeader("Content-length", message.length);
disconnect_xmlhttprequest.send(message);
setState(STATE_DISCONNECTED);
}
}
this.disconnect = disconnect;
}

View File

@@ -1,270 +0,0 @@
/*
* 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/>.
*/
function GuacamoleKeyboard(element) {
/*****************************************/
/*** Keyboard Handler ***/
/*****************************************/
// Single key state/modifier buffer
var modShift = 0;
var modCtrl = 0;
var modAlt = 0;
var keydownChar = new Array();
// ID of routine repeating keystrokes. -1 = not repeating.
var repeatKeyTimeoutId = -1;
var repeatKeyIntervalId = -1;
// Starts repeating keystrokes
function startRepeat(keySym) {
repeatKeyIntervalId = setInterval(function() {
sendKeyReleased(keySym);
sendKeyPressed(keySym);
}, 50);
}
// Stops repeating keystrokes
function stopRepeat() {
if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
}
function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
var unicodePrefixLocation = keyIdentifier.indexOf("U+");
if (unicodePrefixLocation >= 0) {
var hex = keyIdentifier.substring(unicodePrefixLocation+2);
var codepoint = parseInt(hex, 16);
var typedCharacter;
// Convert case if shifted
if (shifted == 0)
typedCharacter = String.fromCharCode(codepoint).toLowerCase();
else
typedCharacter = String.fromCharCode(codepoint).toUpperCase();
// Get codepoint
codepoint = typedCharacter.charCodeAt(0);
return getKeySymFromCharCode(codepoint);
}
return null;
}
function getKeySymFromCharCode(keyCode) {
if (keyCode >= 0x0000 && keyCode <= 0x00FF)
return keyCode;
if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
return 0x01000000 | keyCode;
return null;
}
function getKeySymFromKeyCode(keyCode) {
var keysym = null;
if (modShift == 0) keysym = unshiftedKeySym[keyCode];
else {
keysym = shiftedKeySym[keyCode];
if (keysym == null) keysym = unshiftedKeySym[keyCode];
}
return keysym;
}
// Sends a single keystroke over the network
function sendKeyPressed(keysym) {
if (keysym != null && keyPressedHandler)
keyPressedHandler(keysym);
}
// Sends a single keystroke over the network
function sendKeyReleased(keysym) {
if (keysym != null)
keyReleasedHandler(keysym);
}
var KEYDOWN = 1;
var KEYPRESS = 2;
var keySymSource = null;
// When key pressed
var keydownCode = null;
element.onkeydown = function(e) {
// Only intercept if handler set
if (!keyPressedHandler) return true;
var keynum;
if (window.event) keynum = window.event.keyCode;
else if (e.which) keynum = e.which;
// Ctrl/Alt/Shift
if (keynum == 16)
modShift = 1;
else if (keynum == 17)
modCtrl = 1;
else if (keynum == 18)
modAlt = 1;
var keysym = getKeySymFromKeyCode(keynum);
if (keysym) {
// Get keysyms and events from KEYDOWN
keySymSource = KEYDOWN;
}
// If modifier keys are held down, and we have keyIdentifier
else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) {
// Get keysym from keyIdentifier
keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
// Get keysyms and events from KEYDOWN
keySymSource = KEYDOWN;
}
else
// Get keysyms and events from KEYPRESS
keySymSource = KEYPRESS;
keydownCode = keynum;
// Ignore key if we don't need to use KEYPRESS.
// Send key event here
if (keySymSource == KEYDOWN) {
if (keydownChar[keynum] != keysym) {
// Send event
keydownChar[keynum] = keysym;
sendKeyPressed(keysym);
// Clear old key repeat, if any.
stopRepeat();
// Start repeating (if not a modifier key) after a short delay
if (keynum != 16 && keynum != 17 && keynum != 18)
repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
}
return false;
}
};
// When key pressed
element.onkeypress = function(e) {
// Only intercept if handler set
if (!keyPressedHandler) return true;
if (keySymSource != KEYPRESS) return false;
var keynum;
if (window.event) keynum = window.event.keyCode;
else if (e.which) keynum = e.which;
var keysym = getKeySymFromCharCode(keynum);
if (keysym && keydownChar[keynum] != keysym) {
// If this button already pressed, release first
var lastKeyDownChar = keydownChar[keydownCode];
if (lastKeyDownChar)
sendKeyReleased(lastKeyDownChar);
keydownChar[keydownCode] = keysym;
// Clear old key repeat, if any.
stopRepeat();
// Send key event
sendKeyPressed(keysym);
// Start repeating (if not a modifier key) after a short delay
repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
}
return false;
};
// When key released
element.onkeyup = function(e) {
// Only intercept if handler set
if (!keyReleasedHandler) return true;
var keynum;
if (window.event) keynum = window.event.keyCode;
else if (e.which) keynum = e.which;
// Ctrl/Alt/Shift
if (keynum == 16)
modShift = 0;
else if (keynum == 17)
modCtrl = 0;
else if (keynum == 18)
modAlt = 0;
else
stopRepeat();
// Get corresponding character
var lastKeyDownChar = keydownChar[keynum];
// Clear character record
keydownChar[keynum] = null;
// Send release event
sendKeyReleased(lastKeyDownChar);
return false;
};
// When focus is lost, clear modifiers.
var docOnblur = element.onblur;
element.onblur = function() {
modAlt = 0;
modCtrl = 0;
modShift = 0;
if (docOnblur != null) docOnblur();
};
var keyPressedHandler = null;
var keyReleasedHandler = null;
this.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
this.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
}

View File

@@ -1,72 +0,0 @@
/*
* 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/>.
*/
// Keymap
var unshiftedKeySym = new Array();
unshiftedKeySym[8] = 0xFF08; // backspace
unshiftedKeySym[9] = 0xFF09; // tab
unshiftedKeySym[13] = 0xFF0D; // enter
unshiftedKeySym[16] = 0xFFE1; // shift
unshiftedKeySym[17] = 0xFFE3; // ctrl
unshiftedKeySym[18] = 0xFFE9; // alt
unshiftedKeySym[19] = 0xFF13; // pause/break
unshiftedKeySym[20] = 0xFFE5; // caps lock
unshiftedKeySym[27] = 0xFF1B; // escape
unshiftedKeySym[33] = 0xFF55; // page up
unshiftedKeySym[34] = 0xFF56; // page down
unshiftedKeySym[35] = 0xFF57; // end
unshiftedKeySym[36] = 0xFF50; // home
unshiftedKeySym[37] = 0xFF51; // left arrow
unshiftedKeySym[38] = 0xFF52; // up arrow
unshiftedKeySym[39] = 0xFF53; // right arrow
unshiftedKeySym[40] = 0xFF54; // down arrow
unshiftedKeySym[45] = 0xFF63; // insert
unshiftedKeySym[46] = 0xFFFF; // delete
unshiftedKeySym[91] = 0xFFEB; // left window key (super_l)
unshiftedKeySym[92] = 0xFF67; // right window key (menu key?)
unshiftedKeySym[93] = null; // select key
unshiftedKeySym[112] = 0xFFBE; // f1
unshiftedKeySym[113] = 0xFFBF; // f2
unshiftedKeySym[114] = 0xFFC0; // f3
unshiftedKeySym[115] = 0xFFC1; // f4
unshiftedKeySym[116] = 0xFFC2; // f5
unshiftedKeySym[117] = 0xFFC3; // f6
unshiftedKeySym[118] = 0xFFC4; // f7
unshiftedKeySym[119] = 0xFFC5; // f8
unshiftedKeySym[120] = 0xFFC6; // f9
unshiftedKeySym[121] = 0xFFC7; // f10
unshiftedKeySym[122] = 0xFFC8; // f11
unshiftedKeySym[123] = 0xFFC9; // f12
unshiftedKeySym[144] = 0xFF7F; // num lock
unshiftedKeySym[145] = 0xFF14; // scroll lock
// Shifted versions, IF DIFFERENT FROM UNSHIFTED!
// If any of these are null, the unshifted one will be used.
var shiftedKeySym = new Array();
shiftedKeySym[18] = 0xFFE7; // alt
// Constants for keysyms for special keys
var KEYSYM_CTRL = 65507;
var KEYSYM_ALT = 65513;
var KEYSYM_DELETE = 65535;
var KEYSYM_SHIFT = 65505;

View File

@@ -1,128 +0,0 @@
/*
* 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/>.
*/
function Layer(width, height) {
// Off-screen buffer
var display = document.createElement("canvas");
display.style.position = "absolute";
display.style.left = "0px";
display.style.right = "0px";
display.width = width;
display.height = height;
var displayContext = display.getContext("2d");
var nextUpdateToDraw = 0;
var currentUpdate = 0;
var updates = new Array();
// Given an update ID, either call the provided update callback, or
// schedule the update for later.
function setUpdate(updateId, update) {
// If this update is the next to draw...
if (updateId == nextUpdateToDraw) {
// Call provided update handler.
update();
// Draw all pending updates.
var updateCallback;
while (updateCallback = updates[++nextUpdateToDraw]) {
updateCallback();
delete updates[nextUpdateToDraw];
}
}
// If not next to draw, set callback and wait.
else
updates[updateId] = update;
}
display.drawImage = function(x, y, image) {
var updateId = currentUpdate++;
setUpdate(updateId, function() {
displayContext.drawImage(image, x, y);
});
}
display.draw = function(x, y, url) {
var updateId = currentUpdate++;
var image = new Image();
image.onload = function() {
setUpdate(updateId, function() {
displayContext.drawImage(image, x, y);
});
};
image.src = url;
};
display.copyRect = function(srcx, srcy, w, h, x, y) {
var updateId = currentUpdate++;
setUpdate(updateId, function() {
displayContext.drawImage(display, srcx, srcy, w, h, x, y, w, h);
});
};
display.drawRect = function(x, y, w, h, color) {
var updateId = currentUpdate++;
setUpdate(updateId, function() {
displayContext.fillStyle = color;
displayContext.fillRect(x, y, w, h);
});
};
display.clearRect = function(x, y, w, h) {
var updateId = currentUpdate++;
setUpdate(updateId, function() {
displayContext.clearRect(x, y, w, h);
});
};
display.filter = function(filter) {
var updateId = currentUpdate++;
setUpdate(updateId, function() {
var imageData = displayContext.getImageData(0, 0, width, height);
filter(imageData.data, width, height);
displayContext.putImageData(imageData, 0, 0);
});
};
return display;
}

View File

@@ -1,205 +0,0 @@
/*
* 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/>.
*/
function GuacamoleMouse(element) {
/*****************************************/
/*** Mouse Handler ***/
/*****************************************/
var mouseIndex = 0;
var mouseLeftButton = 0;
var mouseMiddleButton = 0;
var mouseRightButton = 0;
var mouseX = 0;
var mouseY = 0;
var absoluteMouseX = 0;
var absoluteMouseY = 0;
function getMouseState(up, down) {
var mouseState = new MouseEvent(mouseX, mouseY,
mouseLeftButton, mouseMiddleButton, mouseRightButton, up, down);
return mouseState;
}
// Block context menu so right-click gets sent properly
element.oncontextmenu = function(e) {return false;};
element.onmousemove = function(e) {
e.stopPropagation();
absoluteMouseX = e.pageX;
absoluteMouseY = e.pageY;
mouseX = absoluteMouseX - element.offsetLeft;
mouseY = absoluteMouseY - element.offsetTop;
// This is all JUST so we can get the mouse position within the element
var parent = element.offsetParent;
while (parent) {
if (parent.offsetLeft && parent.offsetTop) {
mouseX -= parent.offsetLeft;
mouseY -= parent.offsetTop;
}
parent = parent.offsetParent;
}
movementHandler(getMouseState(0, 0));
};
element.onmousedown = function(e) {
e.stopPropagation();
switch (e.button) {
case 0:
mouseLeftButton = 1;
break;
case 1:
mouseMiddleButton = 1;
break;
case 2:
mouseRightButton = 1;
break;
}
buttonPressedHandler(getMouseState(0, 0));
};
element.onmouseup = function(e) {
e.stopPropagation();
switch (e.button) {
case 0:
mouseLeftButton = 0;
break;
case 1:
mouseMiddleButton = 0;
break;
case 2:
mouseRightButton = 0;
break;
}
buttonReleasedHandler(getMouseState(0, 0));
};
// Override selection on mouse event element.
element.onselectstart = function() {
return false;
};
// Scroll wheel support
function handleScroll(e) {
var delta = 0;
if (e.detail)
delta = e.detail;
else if (e.wheelDelta)
delta = -event.wheelDelta;
// Up
if (delta < 0) {
buttonPressedHandler(getMouseState(1, 0));
buttonReleasedHandler(getMouseState(0, 0));
}
// Down
if (delta > 0) {
buttonPressedHandler(getMouseState(0, 1));
buttonReleasedHandler(getMouseState(0, 0));
}
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
element.addEventListener('DOMMouseScroll', handleScroll, false);
element.onmousewheel = function(e) {
handleScroll(e);
}
function MouseEvent(x, y, left, middle, right, up, down) {
this.getX = function() {
return x;
};
this.getY = function() {
return y;
};
this.getLeft = function() {
return left;
};
this.getMiddle = function() {
return middle;
};
this.getRight = function() {
return right;
};
this.getUp = function() {
return up;
};
this.getDown = function() {
return down;
};
this.toString = function() {
return (mouseIndex++) + "," + x + "," + y + "," + left + "," + middle + "," + right + "," + up + "," + down;
};
}
var buttonPressedHandler = null;
var buttonReleasedHandler = null;
var movementHandler = null;
this.setButtonPressedHandler = function(mh) {buttonPressedHandler = mh;};
this.setButtonReleasedHandler = function(mh) {buttonReleasedHandler = mh;};
this.setMovementHandler = function(mh) {movementHandler = mh;};
this.getX = function() {return mouseX;};
this.getY = function() {return mouseY;};
this.getLeftButton = function() {return mouseLeftButton;};
this.getMiddleButton = function() {return mouseMiddleButton;};
this.getRightButton = function() {return mouseRightButton;};
}

View File

@@ -1,487 +0,0 @@
/*
* 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/>.
*/
function GuacamoleOnScreenKeyboard(url) {
var tabIndex = 1;
var allKeys = new Array();
var modifierState = new function() {};
function getKeySize(size) {
return (5*size) + "ex";
}
function getCapSize(size) {
return (5*size - 0.5) + "ex";
}
function clearModifiers() {
// Send key release events for all pressed modifiers
for (var k=0; k<allKeys.length; k++) {
var key = allKeys[k];
var cap = key.getCap();
var modifier = cap.getModifier();
if (modifier && isModifierActive(modifier) && !cap.isSticky() && key.isPressed())
key.release();
}
}
function setModifierReleased(modifier) {
if (isModifierActive(modifier))
modifierState[modifier]--;
}
function setModifierPressed(modifier) {
if (modifierState[modifier] == null)
modifierState[modifier] = 1;
else
modifierState[modifier]++;
}
function isModifierActive(modifier) {
if (modifierState[modifier] > 0)
return true;
return false;
}
function toggleModifierPressed(modifier) {
if (isModifierActive(modifier))
setModifierReleased(modifier);
else
setModifierPressed(modifier);
}
function refreshAllKeysState() {
for (var k=0; k<allKeys.length; k++)
allKeys[k].refreshState();
}
function Key(key) {
function Cap(cap) {
// Displayed text
var displayText = cap.textContent;
// Keysym
var keysym = null;
if (cap.attributes["keysym"])
keysym = parseInt(cap.attributes["keysym"].value);
// If keysym not specified, get keysym from display text.
else if (displayText.length == 1) {
var charCode = displayText.charCodeAt(0);
if (charCode >= 0x0000 && charCode <= 0x00FF)
keysym = charCode;
else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
keysym = 0x01000000 | charCode;
}
// Required modifiers for this keycap
var reqMod = null;
if (cap.attributes["if"])
reqMod = cap.attributes["if"].value.split(",");
// Modifier represented by this keycap
var modifier = null;
if (cap.attributes["modifier"])
modifier = cap.attributes["modifier"].value;
// Whether this key is sticky (toggles)
// Currently only valid for modifiers.
var sticky = false;
if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
sticky = true;
this.getDisplayText = function() {
return cap.textContent;
};
this.getKeySym = function() {
return keysym;
};
this.getRequiredModifiers = function() {
return reqMod;
};
this.getModifier = function() {
return modifier;
};
this.isSticky = function() {
return sticky;
};
}
var size = null;
if (key.attributes["size"])
size = parseFloat(key.attributes["size"].value);
var caps = key.getElementsByTagName("cap");
var keycaps = new Array();
for (var i=0; i<caps.length; i++)
keycaps.push(new Cap(caps[i]));
var rowKey = document.createElement("div");
rowKey.className = "key";
var keyCap = document.createElement("div");
keyCap.className = "cap";
rowKey.appendChild(keyCap);
var STATE_RELEASED = 0;
var STATE_PRESSED = 1;
var state = STATE_RELEASED;
rowKey.isPressed = function() {
return state == STATE_PRESSED;
}
var currentCap = null;
function refreshState(modifier) {
// Find current cap
currentCap = null;
for (var j=0; j<keycaps.length; j++) {
var keycap = keycaps[j];
var required = keycap.getRequiredModifiers();
var matches = true;
// If modifiers required, make sure all modifiers are active
if (required) {
for (var k=0; k<required.length; k++) {
if (!isModifierActive(required[k])) {
matches = false;
break;
}
}
}
if (matches)
currentCap = keycap;
}
rowKey.className = "key";
if (currentCap.getModifier())
rowKey.className += " modifier";
if (currentCap.isSticky())
rowKey.className += " sticky";
if (isModifierActive(currentCap.getModifier()))
rowKey.className += " active";
if (state == STATE_PRESSED)
rowKey.className += " pressed";
keyCap.textContent = currentCap.getDisplayText();
}
rowKey.refreshState = refreshState;
rowKey.getCap = function() {
return currentCap;
};
refreshState();
// Set size
if (size) {
rowKey.style.width = getKeySize(size);
keyCap.style.width = getCapSize(size);
}
// Set pressed, if released
function press() {
if (state == STATE_RELEASED) {
state = STATE_PRESSED;
var keysym = currentCap.getKeySym();
var modifier = currentCap.getModifier();
var sticky = currentCap.isSticky();
if (keyPressedHandler && keysym)
keyPressedHandler(keysym);
if (modifier) {
// If sticky modifier, toggle
if (sticky)
toggleModifierPressed(modifier);
// Otherwise, just set on.
else
setModifierPressed(modifier);
refreshAllKeysState();
}
else
refreshState();
}
}
rowKey.press = press;
// Set released, if pressed
function release() {
if (state == STATE_PRESSED) {
state = STATE_RELEASED;
var keysym = currentCap.getKeySym();
var modifier = currentCap.getModifier();
var sticky = currentCap.isSticky();
if (keyReleasedHandler && keysym)
keyReleasedHandler(keysym);
if (modifier) {
// If not sticky modifier, release modifier
if (!sticky) {
setModifierReleased(modifier);
refreshAllKeysState();
}
else
refreshState();
}
else {
refreshState();
// If not a modifier, also release all pressed modifiers
clearModifiers();
}
}
}
rowKey.release = release;
// Toggle press/release states
function toggle() {
if (state == STATE_PRESSED)
release();
else
press();
}
// Send key press on mousedown
rowKey.onmousedown = function(e) {
e.stopPropagation();
var modifier = currentCap.getModifier();
var sticky = currentCap.isSticky();
// Toggle non-sticky modifiers
if (modifier && !sticky)
toggle();
// Press all others
else
press();
return false;
};
// Send key release on mouseup/out
rowKey.onmouseout =
rowKey.onmouseout =
rowKey.onmouseup = function(e) {
e.stopPropagation();
var modifier = currentCap.getModifier();
var sticky = currentCap.isSticky();
// Release non-modifiers and sticky modifiers
if (!modifier || sticky)
release();
return false;
};
rowKey.onselectstart = function() { return false; };
return rowKey;
}
function Gap(gap) {
var keyboardGap = document.createElement("div");
keyboardGap.className = "gap";
keyboardGap.textContent = " ";
var size = null;
if (gap.attributes["size"])
size = parseFloat(gap.attributes["size"].value);
if (size) {
keyboardGap.style.width = getKeySize(size);
keyboardGap.style.height = getKeySize(size);
}
return keyboardGap;
}
function Row(row) {
var keyboardRow = document.createElement("div");
keyboardRow.className = "row";
var children = row.childNodes;
for (var j=0; j<children.length; j++) {
var child = children[j];
// <row> can contain <key> or <column>
if (child.tagName == "key") {
var key = new Key(child);
keyboardRow.appendChild(key);
allKeys.push(key);
}
else if (child.tagName == "gap") {
var gap = new Gap(child);
keyboardRow.appendChild(gap);
}
else if (child.tagName == "column") {
var col = new Column(child);
keyboardRow.appendChild(col);
}
}
return keyboardRow;
}
function Column(col) {
var keyboardCol = document.createElement("div");
keyboardCol.className = "col";
var align = null;
if (col.attributes["align"])
align = col.attributes["align"].value;
var children = col.childNodes;
for (var j=0; j<children.length; j++) {
var child = children[j];
// <column> can only contain <row>
if (child.tagName == "row") {
var row = new Row(child);
keyboardCol.appendChild(row);
}
}
if (align)
keyboardCol.style.textAlign = align;
return keyboardCol;
}
// Create keyboard
var keyboard = document.createElement("div");
keyboard.className = "keyboard";
// Retrieve keyboard XML
var xmlhttprequest = new XMLHttpRequest();
xmlhttprequest.open("GET", url, false);
xmlhttprequest.send(null);
var xml = xmlhttprequest.responseXML;
if (xml) {
// Parse document
var root = xml.documentElement;
if (root) {
var children = root.childNodes;
for (var i=0; i<children.length; i++) {
var child = children[i];
// <keyboard> can contain <row> or <column>
if (child.tagName == "row") {
keyboard.appendChild(new Row(child));
}
else if (child.tagName == "column") {
keyboard.appendChild(new Column(child));
}
}
}
}
var keyPressedHandler = null;
var keyReleasedHandler = null;
keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
// Do not allow selection or mouse movement to propagate/register.
keyboard.onselectstart =
keyboard.onmousemove =
keyboard.onmouseup =
keyboard.onmousedown =
function(e) {
e.stopPropagation();
return false;
};
return keyboard;
}

View File

Before

Width:  |  Height:  |  Size: 766 B

After

Width:  |  Height:  |  Size: 766 B

View File

Before

Width:  |  Height:  |  Size: 71 B

After

Width:  |  Height:  |  Size: 71 B

View File

Before

Width:  |  Height:  |  Size: 72 B

After

Width:  |  Height:  |  Size: 72 B

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -22,9 +22,8 @@
<head>
<link rel="icon" type="image/png" href="images/guacamole-icon-64.png"/>
<link rel="stylesheet" type="text/css" href="guacamole.css"/>
<link rel="stylesheet" type="text/css" href="guac-web-lib/css/guacamole.css"/>
<link rel="stylesheet" type="text/css" href="keyboard.css"/>
<link rel="stylesheet" type="text/css" href="styles/guacamole.css"/>
<link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
<title>Guacamole</title>
</head>
@@ -112,12 +111,12 @@
<!-- Scripts -->
<script type="text/javascript" src="guac-web-lib/javascript/keymap.js"></script>
<script type="text/javascript" src="guac-web-lib/javascript/keyboard.js"></script>
<script type="text/javascript" src="guac-web-lib/javascript/mouse.js"></script>
<script type="text/javascript" src="guac-web-lib/javascript/layer.js"></script>
<script type="text/javascript" src="guac-web-lib/javascript/guacamole.js"></script>
<script type="text/javascript" src="guac-web-lib/javascript/oskeyboard.js"></script>
<script type="text/javascript" src="guacamole-common-js/keymap.js"></script>
<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/guacamole.js"></script>
<script type="text/javascript" src="guacamole-common-js/oskeyboard.js"></script>
<!-- Init -->
<script type="text/javascript"> /* <![CDATA[ */
@@ -179,7 +178,7 @@
window.onresize();
// Instantiate client
var guac = new GuacamoleClient(display);
var guac = new GuacamoleClient(display, "tunnel");
var state = document.getElementById("state");
guac.setOnStateChangeHandler(function(clientState) {
@@ -219,10 +218,14 @@
});
guac.setErrorHandler(function(error) {
menu.className = "error";
display.className += " guac-error";
logo.src = guacErrorImage.src;
errorDialogText.textContent = error;
errorDialog.style.visibility = "visible";
});
// Reconnect button

View File

@@ -234,3 +234,21 @@ div#clipboardDiv textarea {
width: 100%;
}
.guac-display.guac-loading {
border: 1px dotted gray;
background-image: url('../images/spinner92.gif');
background-position: center;
background-repeat: no-repeat;
}
.guac-display.guac-error {
border: 1px dotted red;
background-image: url('../images/noimage92.png');
background-position: center;
background-repeat: no-repeat;
}
.guac-hide-cursor {
cursor: url('../images/mouse/dot.gif'),url('../images/mouse/blank.cur'),default;
}