Migrating to separate Guacamole javascript library
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
@@ -19,6 +20,21 @@
|
|||||||
<target>1.6</target>
|
<target>1.6</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</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>
|
</plugins>
|
||||||
|
|
||||||
<extensions>
|
<extensions>
|
||||||
@@ -47,6 +63,14 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
@@ -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; };
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
|
|
||||||
|
|
@@ -1,142 +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 readyHandler = null;
|
|
||||||
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 done with updates, call ready handler
|
|
||||||
if (display.isReady() && readyHandler != null)
|
|
||||||
readyHandler();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not next to draw, set callback and wait.
|
|
||||||
else
|
|
||||||
updates[updateId] = update;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
display.isReady = function() {
|
|
||||||
return currentUpdate == nextUpdateToDraw;
|
|
||||||
}
|
|
||||||
|
|
||||||
display.setReadyHandler = function(handler) {
|
|
||||||
readyHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@@ -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;};
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 71 B After Width: | Height: | Size: 71 B |
Before Width: | Height: | Size: 72 B After Width: | Height: | Size: 72 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@@ -22,9 +22,8 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="images/guacamole-icon-64.png"/>
|
<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="styles/guacamole.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="guac-web-lib/css/guacamole.css"/>
|
<link rel="stylesheet" type="text/css" href="styles/keyboard.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="keyboard.css"/>
|
|
||||||
<title>Guacamole</title>
|
<title>Guacamole</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -112,12 +111,12 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script type="text/javascript" src="guac-web-lib/javascript/keymap.js"></script>
|
<script type="text/javascript" src="guacamole-common-js/keymap.js"></script>
|
||||||
<script type="text/javascript" src="guac-web-lib/javascript/keyboard.js"></script>
|
<script type="text/javascript" src="guacamole-common-js/keyboard.js"></script>
|
||||||
<script type="text/javascript" src="guac-web-lib/javascript/mouse.js"></script>
|
<script type="text/javascript" src="guacamole-common-js/mouse.js"></script>
|
||||||
<script type="text/javascript" src="guac-web-lib/javascript/layer.js"></script>
|
<script type="text/javascript" src="guacamole-common-js/layer.js"></script>
|
||||||
<script type="text/javascript" src="guac-web-lib/javascript/guacamole.js"></script>
|
<script type="text/javascript" src="guacamole-common-js/guacamole.js"></script>
|
||||||
<script type="text/javascript" src="guac-web-lib/javascript/oskeyboard.js"></script>
|
<script type="text/javascript" src="guacamole-common-js/oskeyboard.js"></script>
|
||||||
|
|
||||||
<!-- Init -->
|
<!-- Init -->
|
||||||
<script type="text/javascript"> /* <![CDATA[ */
|
<script type="text/javascript"> /* <![CDATA[ */
|
||||||
|
@@ -234,3 +234,21 @@ div#clipboardDiv textarea {
|
|||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
|