--- /dev/null
+
+/*
+ * 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;
+}
+
--- /dev/null
+
+/*
+ * 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;
+
+}
--- /dev/null
+
+/*
+ * 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; };
+
+}
--- /dev/null
+
+/*
+ * 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;
+
+
--- /dev/null
+
+/*
+ * 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;
+
+}
+
--- /dev/null
+
+/*
+ * 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;};
+
+}
--- /dev/null
+
+/*
+ * 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;
+}
+
margin-right: auto;
}
-div#display.loading {
- border: 1px dotted gray;
- background-image: url('images/spinner92.gif');
- background-position: center;
- background-repeat: no-repeat;
-}
-
-div#display.error {
- border: 1px dotted red;
- background-image: url('images/noimage92.png');
- background-position: center;
- background-repeat: no-repeat;
-}
-
#menu img {
vertical-align: middle;
}
width: 100%;
}
-.hideCursor {
- cursor: url('images/mouse/dot.gif'),url('images/mouse/blank.cur'),default;
-}
-
<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"/>
<title>Guacamole</title>
</head>
<!-- Display -->
- <div id="display" class="loading">
+ <div id="display" class="guac-loading">
<!-- On-screen keyboard -->
<div id="keyboardContainer"></div>
</div>
<!-- Scripts -->
- <script type="text/javascript" src="javascript/keymap.js"></script>
- <script type="text/javascript" src="javascript/keyboard.js"></script>
- <script type="text/javascript" src="javascript/mouse.js"></script>
- <script type="text/javascript" src="javascript/layer.js"></script>
- <script type="text/javascript" src="javascript/guacamole.js"></script>
- <script type="text/javascript" src="javascript/oskeyboard.js"></script>
+ <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>
<!-- Init -->
<script type="text/javascript">
window.onresize();
// Instantiate client
- var vncClient = new GuacamoleClient(display);
+ var guac = new GuacamoleClient(display);
var state = document.getElementById("state");
- vncClient.setOnStateChangeHandler(function(clientState) {
+ guac.setOnStateChangeHandler(function(clientState) {
switch (clientState) {
case 0:
state.textContent = "Connected, waiting for first update...";
break;
case 3:
- display.className = display.className.replace(/loading/, '');
+ display.className = display.className.replace(/guac-loading/, '');
menu.className = "connected";
state.textContent = "Connected.";
break;
var guacErrorImage = new Image();
guacErrorImage.src = "images/noguacamole-logo.png";
- vncClient.setErrorHandler(function(error) {
+ guac.setErrorHandler(function(error) {
menu.className = "error";
logo.src = guacErrorImage.src;
errorDialogText.textContent = error;
};
// Connect
- vncClient.connect();
+ guac.connect();
// Disconnect on close
window.onunload = function() {
- vncClient.disconnect();
+ guac.disconnect();
}
// Handle clipboard events
clipboardElement.onchange = function() {
var text = clipboardElement.value;
- vncClient.setClipboard(text);
+ guac.setClipboard(text);
};
// Ignore keypresses when clipboard is focused
clipboardElement.onfocus = function() {
- vncClient.disableKeyboard();
+ guac.disableKeyboard();
};
// Capture keypresses when clipboard is not focused
clipboardElement.onblur = function() {
- vncClient.enableKeyboard();
+ guac.enableKeyboard();
};
// Server copy handler
- vncClient.setClipboardHandler(
+ guac.setClipboardHandler(
function(data) {
clipboardElement.value = data;
}
osKeyboard.setKeyPressedHandler(
function(keysym) {
- vncClient.pressKey(keysym);
+ guac.pressKey(keysym);
}
);
osKeyboard.setKeyReleasedHandler(
function(keysym) {
- vncClient.releaseKey(keysym);
+ guac.releaseKey(keysym);
}
);
var CtrlAltDelete = document.getElementById("CtrlAltDelete");
CtrlAltDelete.onclick = function() {
- vncClient.pressKey(KEYSYM_CTRL);
- vncClient.pressKey(KEYSYM_ALT);
- vncClient.pressKey(KEYSYM_DELETE);
- vncClient.releaseKey(KEYSYM_DELETE);
- vncClient.releaseKey(KEYSYM_ALT);
- vncClient.releaseKey(KEYSYM_CTRL);
+ guac.pressKey(KEYSYM_CTRL);
+ guac.pressKey(KEYSYM_ALT);
+ guac.pressKey(KEYSYM_DELETE);
+ guac.releaseKey(KEYSYM_DELETE);
+ guac.releaseKey(KEYSYM_ALT);
+ guac.releaseKey(KEYSYM_CTRL);
}
</script>
+++ /dev/null
-
-/*
- * 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 += " hideCursor";
- 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 += " 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;
-
-}
+++ /dev/null
-
-/*
- * 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; };
-
-}
+++ /dev/null
-
-/*
- * 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;
-
-
+++ /dev/null
-
-/*
- * 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;
-
-}
-
+++ /dev/null
-
-/*
- * 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;};
-
-}
+++ /dev/null
-
-/*
- * 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;
-}
-