Moved guacamole-specific parts into own lib dir
authorMichael Jumper <zhangmaike@users.sourceforge.net>
Sat, 4 Dec 2010 23:29:16 +0000 (15:29 -0800)
committerMichael Jumper <zhangmaike@users.sourceforge.net>
Sat, 4 Dec 2010 23:30:10 +0000 (15:30 -0800)
25 files changed:
web-client/web/guac-web-lib/css/guacamole.css [new file with mode: 0644]
web-client/web/guac-web-lib/images/mouse/blank.cur [new file with mode: 0644]
web-client/web/guac-web-lib/images/mouse/blank.gif [new file with mode: 0644]
web-client/web/guac-web-lib/images/mouse/dot.gif [new file with mode: 0644]
web-client/web/guac-web-lib/images/noimage92.png [new file with mode: 0644]
web-client/web/guac-web-lib/images/spinner92.gif [new file with mode: 0644]
web-client/web/guac-web-lib/javascript/guacamole.js [new file with mode: 0644]
web-client/web/guac-web-lib/javascript/keyboard.js [new file with mode: 0644]
web-client/web/guac-web-lib/javascript/keymap.js [new file with mode: 0644]
web-client/web/guac-web-lib/javascript/layer.js [new file with mode: 0644]
web-client/web/guac-web-lib/javascript/mouse.js [new file with mode: 0644]
web-client/web/guac-web-lib/javascript/oskeyboard.js [new file with mode: 0644]
web-client/web/guacamole.css
web-client/web/images/mouse/blank.cur [deleted file]
web-client/web/images/mouse/blank.gif [deleted file]
web-client/web/images/mouse/dot.gif [deleted file]
web-client/web/images/noimage92.png [deleted file]
web-client/web/images/spinner92.gif [deleted file]
web-client/web/index.html
web-client/web/javascript/guacamole.js [deleted file]
web-client/web/javascript/keyboard.js [deleted file]
web-client/web/javascript/keymap.js [deleted file]
web-client/web/javascript/layer.js [deleted file]
web-client/web/javascript/mouse.js [deleted file]
web-client/web/javascript/oskeyboard.js [deleted file]

diff --git a/web-client/web/guac-web-lib/css/guacamole.css b/web-client/web/guac-web-lib/css/guacamole.css
new file mode 100644 (file)
index 0000000..5c2bc6a
--- /dev/null
@@ -0,0 +1,37 @@
+
+/*
+ *  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;
+}
+
diff --git a/web-client/web/guac-web-lib/images/mouse/blank.cur b/web-client/web/guac-web-lib/images/mouse/blank.cur
new file mode 100644 (file)
index 0000000..dfcdea8
Binary files /dev/null and b/web-client/web/guac-web-lib/images/mouse/blank.cur differ
diff --git a/web-client/web/guac-web-lib/images/mouse/blank.gif b/web-client/web/guac-web-lib/images/mouse/blank.gif
new file mode 100644 (file)
index 0000000..ec5c17c
Binary files /dev/null and b/web-client/web/guac-web-lib/images/mouse/blank.gif differ
diff --git a/web-client/web/guac-web-lib/images/mouse/dot.gif b/web-client/web/guac-web-lib/images/mouse/dot.gif
new file mode 100644 (file)
index 0000000..9ac8a00
Binary files /dev/null and b/web-client/web/guac-web-lib/images/mouse/dot.gif differ
diff --git a/web-client/web/guac-web-lib/images/noimage92.png b/web-client/web/guac-web-lib/images/noimage92.png
new file mode 100644 (file)
index 0000000..657d85e
Binary files /dev/null and b/web-client/web/guac-web-lib/images/noimage92.png differ
diff --git a/web-client/web/guac-web-lib/images/spinner92.gif b/web-client/web/guac-web-lib/images/spinner92.gif
new file mode 100644 (file)
index 0000000..0dfe069
Binary files /dev/null and b/web-client/web/guac-web-lib/images/spinner92.gif differ
diff --git a/web-client/web/guac-web-lib/javascript/guacamole.js b/web-client/web/guac-web-lib/javascript/guacamole.js
new file mode 100644 (file)
index 0000000..020e88a
--- /dev/null
@@ -0,0 +1,601 @@
+
+/*
+ *  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;
+
+}
diff --git a/web-client/web/guac-web-lib/javascript/keyboard.js b/web-client/web/guac-web-lib/javascript/keyboard.js
new file mode 100644 (file)
index 0000000..b5e6e93
--- /dev/null
@@ -0,0 +1,270 @@
+
+/*
+ *  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; };
+
+}
diff --git a/web-client/web/guac-web-lib/javascript/keymap.js b/web-client/web/guac-web-lib/javascript/keymap.js
new file mode 100644 (file)
index 0000000..016bf59
--- /dev/null
@@ -0,0 +1,72 @@
+
+/*
+ *  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;
+
+
diff --git a/web-client/web/guac-web-lib/javascript/layer.js b/web-client/web/guac-web-lib/javascript/layer.js
new file mode 100644 (file)
index 0000000..79080fe
--- /dev/null
@@ -0,0 +1,128 @@
+
+/*
+ *  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;
+
+}
+
diff --git a/web-client/web/guac-web-lib/javascript/mouse.js b/web-client/web/guac-web-lib/javascript/mouse.js
new file mode 100644 (file)
index 0000000..3a1234a
--- /dev/null
@@ -0,0 +1,205 @@
+
+/*
+ *  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;};
+
+}
diff --git a/web-client/web/guac-web-lib/javascript/oskeyboard.js b/web-client/web/guac-web-lib/javascript/oskeyboard.js
new file mode 100644 (file)
index 0000000..872bac2
--- /dev/null
@@ -0,0 +1,487 @@
+
+/*
+ *  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;
+}
+
index 2744355..7107ea8 100644 (file)
@@ -128,20 +128,6 @@ div#display {
     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;
 }
@@ -178,7 +164,3 @@ div#clipboardDiv textarea {
     width: 100%;
 }
 
-.hideCursor {
-    cursor: url('images/mouse/dot.gif'),url('images/mouse/blank.cur'),default;
-}
-
diff --git a/web-client/web/images/mouse/blank.cur b/web-client/web/images/mouse/blank.cur
deleted file mode 100644 (file)
index dfcdea8..0000000
Binary files a/web-client/web/images/mouse/blank.cur and /dev/null differ
diff --git a/web-client/web/images/mouse/blank.gif b/web-client/web/images/mouse/blank.gif
deleted file mode 100644 (file)
index ec5c17c..0000000
Binary files a/web-client/web/images/mouse/blank.gif and /dev/null differ
diff --git a/web-client/web/images/mouse/dot.gif b/web-client/web/images/mouse/dot.gif
deleted file mode 100644 (file)
index 9ac8a00..0000000
Binary files a/web-client/web/images/mouse/dot.gif and /dev/null differ
diff --git a/web-client/web/images/noimage92.png b/web-client/web/images/noimage92.png
deleted file mode 100644 (file)
index 657d85e..0000000
Binary files a/web-client/web/images/noimage92.png and /dev/null differ
diff --git a/web-client/web/images/spinner92.gif b/web-client/web/images/spinner92.gif
deleted file mode 100644 (file)
index 0dfe069..0000000
Binary files a/web-client/web/images/spinner92.gif and /dev/null differ
index 1efe834..38d0381 100644 (file)
@@ -23,6 +23,7 @@
     <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>
@@ -54,7 +55,7 @@
 
 
         <!-- 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>
diff --git a/web-client/web/javascript/guacamole.js b/web-client/web/javascript/guacamole.js
deleted file mode 100644 (file)
index 77afc17..0000000
+++ /dev/null
@@ -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 += " 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;
-
-}
diff --git a/web-client/web/javascript/keyboard.js b/web-client/web/javascript/keyboard.js
deleted file mode 100644 (file)
index b5e6e93..0000000
+++ /dev/null
@@ -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; };
-
-}
diff --git a/web-client/web/javascript/keymap.js b/web-client/web/javascript/keymap.js
deleted file mode 100644 (file)
index 016bf59..0000000
+++ /dev/null
@@ -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;
-
-
diff --git a/web-client/web/javascript/layer.js b/web-client/web/javascript/layer.js
deleted file mode 100644 (file)
index 79080fe..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-
-/*
- *  Guacamole - Clientless Remote Desktop
- *  Copyright (C) 2010  Michael Jumper
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU Affero General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Affero General Public License for more details.
- *
- *  You should have received a copy of the GNU Affero General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-function Layer(width, height) {
-
-    // Off-screen buffer
-    var display = document.createElement("canvas");
-
-    display.style.position = "absolute";
-    display.style.left = "0px";
-    display.style.right = "0px";
-
-    display.width = width;
-    display.height = height;
-
-    var displayContext = display.getContext("2d");
-
-    var nextUpdateToDraw = 0;
-    var currentUpdate = 0;
-    var updates = new Array();
-
-    // Given an update ID, either call the provided update callback, or
-    // schedule the update for later.
-    function setUpdate(updateId, update) {
-
-        // If this update is the next to draw...
-        if (updateId == nextUpdateToDraw) {
-
-            // Call provided update handler.
-            update();
-
-            // Draw all pending updates.
-            var updateCallback;
-            while (updateCallback = updates[++nextUpdateToDraw]) {
-                updateCallback();
-                delete updates[nextUpdateToDraw];
-            }
-
-        }
-
-        // If not next to draw, set callback and wait.
-        else
-            updates[updateId] = update;
-
-    }
-
-    display.drawImage = function(x, y, image) {
-        var updateId = currentUpdate++;
-
-        setUpdate(updateId, function() {
-            displayContext.drawImage(image, x, y);
-        });
-
-    }
-
-
-    display.draw = function(x, y, url) {
-        var updateId = currentUpdate++;
-
-        var image = new Image();
-        image.onload = function() {
-            setUpdate(updateId, function() {
-                displayContext.drawImage(image, x, y);
-            });
-        };
-        image.src = url;
-    };
-
-
-    display.copyRect = function(srcx, srcy, w, h, x, y) {
-        var updateId = currentUpdate++;
-    
-        setUpdate(updateId, function() {
-            displayContext.drawImage(display, srcx, srcy, w, h, x, y, w, h);
-        });
-
-    };
-
-    display.drawRect = function(x, y, w, h, color) {
-        var updateId = currentUpdate++;
-
-        setUpdate(updateId, function() {
-            displayContext.fillStyle = color;
-            displayContext.fillRect(x, y, w, h);
-        });
-
-    };
-
-    display.clearRect = function(x, y, w, h) {
-        var updateId = currentUpdate++;
-
-        setUpdate(updateId, function() {
-            displayContext.clearRect(x, y, w, h);
-        });
-
-    };
-
-    display.filter = function(filter) {
-        var updateId = currentUpdate++;
-
-        setUpdate(updateId, function() {
-            var imageData = displayContext.getImageData(0, 0, width, height);
-            filter(imageData.data, width, height);
-            displayContext.putImageData(imageData, 0, 0);
-        });
-
-    };
-
-    return display;
-
-}
-
diff --git a/web-client/web/javascript/mouse.js b/web-client/web/javascript/mouse.js
deleted file mode 100644 (file)
index 3a1234a..0000000
+++ /dev/null
@@ -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;};
-
-}
diff --git a/web-client/web/javascript/oskeyboard.js b/web-client/web/javascript/oskeyboard.js
deleted file mode 100644 (file)
index 872bac2..0000000
+++ /dev/null
@@ -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;
-}
-