From fbce63ea640164be1426300882cef56877a4401b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 3 Jul 2011 15:32:06 -0700 Subject: [PATCH 01/19] Removed keymap (merged into keyboard), fixed JS semicolons, etc. --- .../src/main/resources/guacamole.js | 5 +- .../src/main/resources/keyboard.js | 78 +++++++++++++++---- .../src/main/resources/keymap.js | 72 ----------------- .../src/main/resources/layer.js | 15 +--- 4 files changed, 68 insertions(+), 102 deletions(-) delete mode 100644 guacamole-common-js/src/main/resources/keymap.js diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index ead548450..54153c573 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -54,7 +54,6 @@ function GuacamoleClient(display, tunnel) { var cursorHotspotX = 0; var cursorHotspotY = 0; - // FIXME: Make object. Clean up. var cursorRectX = 0; var cursorRectY = 0; var cursorRectW = 0; @@ -131,7 +130,7 @@ function GuacamoleClient(display, tunnel) { var nameHandler = null; this.setNameHandler = function(handler) { nameHandler = handler; - } + }; var errorHandler = null; this.setErrorHandler = function(handler) { @@ -354,7 +353,7 @@ function GuacamoleClient(display, tunnel) { if (layersToSync == 0) tunnel.sendMessage("sync:" + timestamp + ";"); - }, + } }; diff --git a/guacamole-common-js/src/main/resources/keyboard.js b/guacamole-common-js/src/main/resources/keyboard.js index b5e6e935c..e0f784db0 100644 --- a/guacamole-common-js/src/main/resources/keyboard.js +++ b/guacamole-common-js/src/main/resources/keyboard.js @@ -19,6 +19,52 @@ function GuacamoleKeyboard(element) { + // 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 + + /*****************************************/ /*** Keyboard Handler ***/ /*****************************************/ @@ -52,27 +98,27 @@ function GuacamoleKeyboard(element) { function getKeySymFromKeyIdentifier(shifted, keyIdentifier) { - var unicodePrefixLocation = keyIdentifier.indexOf("U+"); - if (unicodePrefixLocation >= 0) { + var unicodePrefixLocation = keyIdentifier.indexOf("U+"); + if (unicodePrefixLocation >= 0) { - var hex = keyIdentifier.substring(unicodePrefixLocation+2); - var codepoint = parseInt(hex, 16); - var typedCharacter; + 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(); + // Convert case if shifted + if (shifted == 0) + typedCharacter = String.fromCharCode(codepoint).toLowerCase(); + else + typedCharacter = String.fromCharCode(codepoint).toUpperCase(); - // Get codepoint - codepoint = typedCharacter.charCodeAt(0); + // Get codepoint + codepoint = typedCharacter.charCodeAt(0); - return getKeySymFromCharCode(codepoint); + return getKeySymFromCharCode(codepoint); - } + } - return null; + return null; } @@ -183,6 +229,8 @@ function GuacamoleKeyboard(element) { return false; } + return true; + }; // When key pressed diff --git a/guacamole-common-js/src/main/resources/keymap.js b/guacamole-common-js/src/main/resources/keymap.js deleted file mode 100644 index 016bf5939..000000000 --- a/guacamole-common-js/src/main/resources/keymap.js +++ /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 . - */ - - -// 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/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 6921eb490..775b23683 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -68,7 +68,6 @@ function Layer(width, height) { resize(width, height); - var readyHandler = null; var updates = new Array(); var autosize = 0; @@ -84,7 +83,7 @@ function Layer(width, height) { this.handle = function() { updateHandler(); - } + }; } @@ -117,20 +116,12 @@ function Layer(width, height) { updates.shift(); } - // If done with updates, call ready handler - if (display.isReady() && readyHandler != null) - readyHandler(); - } display.isReady = function() { return updates.length == 0; }; - display.setReadyHandler = function(handler) { - readyHandler = handler; - }; - display.drawImage = function(x, y, image) { reserveJob(function() { @@ -165,7 +156,7 @@ function Layer(width, height) { // the ready handler, which requires no pending updates) display.sync = function(handler) { reserveJob(handler); - } + }; display.copyRect = function(srcLayer, srcx, srcy, w, h, x, y) { @@ -226,7 +217,7 @@ function Layer(width, height) { 0xC: "copy", /* 0xD NOT IMPLEMENTED */ 0xE: "source-over", - 0xF: "lighter", + 0xF: "lighter" }; display.setChannelMask = function(mask) { From 5815f79eef303a7fcf77ec8b91199c17fd62fb4b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 4 Jul 2011 14:25:52 -0700 Subject: [PATCH 02/19] Migrating to traditional JS handlers. --- .../src/main/resources/guacamole.js | 54 +++++++------------ .../src/main/resources/tunnel.js | 31 +++++++---- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index 54153c573..b13a80a56 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -18,6 +18,8 @@ function GuacamoleClient(display, tunnel) { + var guac_client = this; + var STATE_IDLE = 0; var STATE_CONNECTING = 1; var STATE_WAITING = 2; @@ -26,9 +28,8 @@ function GuacamoleClient(display, tunnel) { var STATE_DISCONNECTED = 5; var currentState = STATE_IDLE; - var stateChangeHandler = null; - tunnel.setInstructionHandler(doInstruction); + tunnel.oninstruction = doInstruction; // Display must be relatively positioned for mouse to be handled properly display.style.position = "relative"; @@ -36,15 +37,11 @@ function GuacamoleClient(display, tunnel) { function setState(state) { if (state != currentState) { currentState = state; - if (stateChangeHandler) - stateChangeHandler(currentState); + if (guac_client.onstatechange) + guac_client.onstatechange(currentState); } } - this.setOnStateChangeHandler = function(handler) { - stateChangeHandler = handler; - }; - function isConnected() { return currentState == STATE_CONNECTED || currentState == STATE_WAITING; @@ -82,7 +79,7 @@ function GuacamoleClient(display, tunnel) { cursor.drawImage(cursorRectX, cursorRectY, cursorImage); } - this.sendKeyEvent = function(pressed, keysym) { + guac_client.sendKeyEvent = function(pressed, keysym) { // Do not send requests if not connected if (!isConnected()) return; @@ -90,7 +87,7 @@ function GuacamoleClient(display, tunnel) { tunnel.sendMessage("key:" + keysym + "," + pressed + ";"); }; - this.sendMouseState = function(mouseState) { + guac_client.sendMouseState = function(mouseState) { // Do not send requests if not connected if (!isConnected()) @@ -116,7 +113,7 @@ function GuacamoleClient(display, tunnel) { tunnel.sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";"); }; - this.setClipboard = function(data) { + guac_client.setClipboard = function(data) { // Do not send requests if not connected if (!isConnected()) @@ -126,21 +123,10 @@ function GuacamoleClient(display, tunnel) { }; // Handlers - - var nameHandler = null; - this.setNameHandler = function(handler) { - nameHandler = handler; - }; - - var errorHandler = null; - this.setErrorHandler = function(handler) { - errorHandler = handler; - }; - - var clipboardHandler = null; - this.setClipboardHandler = function(handler) { - clipboardHandler = handler; - }; + guac_client.onstatechange = null; + guac_client.onname = null; + guac_client.onerror = null; + guac_client.onclipboard = null; // Layers var displayWidth = 0; @@ -150,7 +136,7 @@ function GuacamoleClient(display, tunnel) { var buffers = new Array(); var cursor = null; - this.getLayers = function() { + guac_client.getLayers = function() { return layers; }; @@ -216,16 +202,16 @@ function GuacamoleClient(display, tunnel) { var instructionHandlers = { "error": function(parameters) { - if (errorHandler) errorHandler(unescapeGuacamoleString(parameters[0])); + if (guac_client.onerror) guac_client.onerror(unescapeGuacamoleString(parameters[0])); disconnect(); }, "name": function(parameters) { - if (nameHandler) nameHandler(unescapeGuacamoleString(parameters[0])); + if (guac_client.onname) guac_client.onname(unescapeGuacamoleString(parameters[0])); }, "clipboard": function(parameters) { - if (clipboardHandler) clipboardHandler(unescapeGuacamoleString(parameters[0])); + if (guac_client.onclipboard) guac_client.onclipboard(unescapeGuacamoleString(parameters[0])); }, "size": function(parameters) { @@ -432,8 +418,8 @@ function GuacamoleClient(display, tunnel) { } - this.disconnect = disconnect; - this.connect = function(data) { + guac_client.disconnect = disconnect; + guac_client.connect = function(data) { setState(STATE_CONNECTING); @@ -448,7 +434,7 @@ function GuacamoleClient(display, tunnel) { setState(STATE_WAITING); }; - this.escapeGuacamoleString = escapeGuacamoleString; - this.unescapeGuacamoleString = unescapeGuacamoleString; + guac_client.escapeGuacamoleString = escapeGuacamoleString; + guac_client.unescapeGuacamoleString = unescapeGuacamoleString; } diff --git a/guacamole-common-js/src/main/resources/tunnel.js b/guacamole-common-js/src/main/resources/tunnel.js index d5a785ac3..bb89b241f 100644 --- a/guacamole-common-js/src/main/resources/tunnel.js +++ b/guacamole-common-js/src/main/resources/tunnel.js @@ -18,6 +18,8 @@ function GuacamoleHTTPTunnel(tunnelURL) { + var tunnel = this; + var tunnel_uuid; var TUNNEL_CONNECT = tunnelURL + "?connect"; @@ -36,11 +38,13 @@ function GuacamoleHTTPTunnel(tunnelURL) { // Default to polling - will be turned off automatically if not needed var pollingMode = POLLING_ENABLED; - var instructionHandler = null; - var sendingMessages = 0; var outputMessageBuffer = ""; + // Handlers + tunnel.onerror = null; + tunnel.oninstruction = null; + function sendMessage(message) { // Do not attempt to send messages if not connected @@ -119,6 +123,16 @@ function GuacamoleHTTPTunnel(tunnelURL) { // Halt on error during request if (xmlhttprequest.status == 0 || xmlhttprequest.status != 200) { + + // Get error message (if any) + var message = xmlhttprequest.getResponseHeader("X-Guacamole-Error-Message"); + if (message) + message = "Internal server error"; + + // Call error handler + if (tunnel.onerror) tunnel.onerror(message); + + // Finish disconnect(); return; } @@ -160,8 +174,8 @@ function GuacamoleHTTPTunnel(tunnelURL) { } // Call instruction handler. - if (instructionHandler != null) - instructionHandler(opcode, parameters); + if (tunnel.oninstruction != null) + tunnel.oninstruction(opcode, parameters); } // Start search at end of string. @@ -246,11 +260,8 @@ function GuacamoleHTTPTunnel(tunnelURL) { } // External API - this.connect = connect; - this.disconnect = disconnect; - this.sendMessage = sendMessage; - this.setInstructionHandler = function(handler) { - instructionHandler = handler; - }; + tunnel.connect = connect; + tunnel.disconnect = disconnect; + tunnel.sendMessage = sendMessage; } From bd477daa2c6ecc5ace6cf0307b45f9113b80dbf5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 5 Jul 2011 10:16:03 -0700 Subject: [PATCH 03/19] More migration to traditional JS events. --- .../src/main/resources/keyboard.js | 28 ++++------ .../src/main/resources/mouse.js | 54 +++++++++---------- 2 files changed, 38 insertions(+), 44 deletions(-) diff --git a/guacamole-common-js/src/main/resources/keyboard.js b/guacamole-common-js/src/main/resources/keyboard.js index e0f784db0..edbe313fa 100644 --- a/guacamole-common-js/src/main/resources/keyboard.js +++ b/guacamole-common-js/src/main/resources/keyboard.js @@ -19,6 +19,8 @@ function GuacamoleKeyboard(element) { + var guac_keyboard = this; + // Keymap var unshiftedKeySym = new Array(); @@ -64,11 +66,6 @@ function GuacamoleKeyboard(element) { var shiftedKeySym = new Array(); shiftedKeySym[18] = 0xFFE7; // alt - - /*****************************************/ - /*** Keyboard Handler ***/ - /*****************************************/ - // Single key state/modifier buffer var modShift = 0; var modCtrl = 0; @@ -150,14 +147,14 @@ function GuacamoleKeyboard(element) { // Sends a single keystroke over the network function sendKeyPressed(keysym) { - if (keysym != null && keyPressedHandler) - keyPressedHandler(keysym); + if (keysym != null && guac_keyboard.onkeydown) + guac_keyboard.onkeydown(keysym); } // Sends a single keystroke over the network function sendKeyReleased(keysym) { - if (keysym != null) - keyReleasedHandler(keysym); + if (keysym != null && guac_keyboard.onkeyup) + guac_keyboard.onkeyup(keysym); } @@ -171,7 +168,7 @@ function GuacamoleKeyboard(element) { element.onkeydown = function(e) { // Only intercept if handler set - if (!keyPressedHandler) return true; + if (!guac_keyboard.onkeydown) return true; var keynum; if (window.event) keynum = window.event.keyCode; @@ -237,7 +234,7 @@ function GuacamoleKeyboard(element) { element.onkeypress = function(e) { // Only intercept if handler set - if (!keyPressedHandler) return true; + if (!guac_keyboard.onkeydown) return true; if (keySymSource != KEYPRESS) return false; @@ -272,7 +269,7 @@ function GuacamoleKeyboard(element) { element.onkeyup = function(e) { // Only intercept if handler set - if (!keyReleasedHandler) return true; + if (!guac_keyboard.onkeyup) return true; var keynum; if (window.event) keynum = window.event.keyCode; @@ -309,10 +306,7 @@ function GuacamoleKeyboard(element) { if (docOnblur != null) docOnblur(); }; - var keyPressedHandler = null; - var keyReleasedHandler = null; - - this.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; }; - this.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; }; + guac_keyboard.onkeydown = null; + guac_keyboard.onkeyup = null; } diff --git a/guacamole-common-js/src/main/resources/mouse.js b/guacamole-common-js/src/main/resources/mouse.js index fb8600519..31d5185d0 100644 --- a/guacamole-common-js/src/main/resources/mouse.js +++ b/guacamole-common-js/src/main/resources/mouse.js @@ -20,12 +20,7 @@ function GuacamoleMouse(element) { - /*****************************************/ - /*** Mouse Handler ***/ - /*****************************************/ - - - var mouseIndex = 0; + var guac_mouse = this; var mouseLeftButton = 0; var mouseMiddleButton = 0; @@ -69,7 +64,8 @@ function GuacamoleMouse(element) { parent = parent.offsetParent; } - movementHandler(getMouseState(0, 0)); + if (guac_mouse.onmousemove) + guac_mouse.onmousemove(getMouseState(0, 0)); }; @@ -89,7 +85,8 @@ function GuacamoleMouse(element) { break; } - buttonPressedHandler(getMouseState(0, 0)); + if (guac_mouse.onmousedown) + guac_mouse.onmousedown(getMouseState(0, 0)); }; @@ -109,7 +106,8 @@ function GuacamoleMouse(element) { break; } - buttonReleasedHandler(getMouseState(0, 0)); + if (guac_mouse.onmouseup) + guac_mouse.onmouseup(getMouseState(0, 0)); }; element.onmouseout = function(e) { @@ -122,7 +120,8 @@ function GuacamoleMouse(element) { mouseMiddleButton = 0; mouseRightButton = 0; - buttonReleasedHandler(getMouseState(0, 0)); + if (guac_mouse.onmouseup) + guac_mouse.onmouseup(getMouseState(0, 0)); } }; @@ -143,14 +142,20 @@ function GuacamoleMouse(element) { // Up if (delta < 0) { - buttonPressedHandler(getMouseState(1, 0)); - buttonReleasedHandler(getMouseState(0, 0)); + if (guac_mouse.onmousedown) + guac_mouse.onmousedown(getMouseState(1, 0)); + + if (guac_mouse.onmouseup) + guac_mouse.onmouseup(getMouseState(0, 0)); } // Down if (delta > 0) { - buttonPressedHandler(getMouseState(0, 1)); - buttonReleasedHandler(getMouseState(0, 0)); + if (guac_mouse.onmousedown) + guac_mouse.onmousedown(getMouseState(0, 1)); + + if (guac_mouse.onmouseup) + guac_mouse.onmouseup(getMouseState(0, 0)); } if (e.preventDefault) @@ -165,20 +170,15 @@ function GuacamoleMouse(element) { handleScroll(e); } - var buttonPressedHandler = null; - var buttonReleasedHandler = null; - var movementHandler = null; + guac_mouse.onmousedown = null; + guac_mouse.onmouseup = null; + guac_mouse.onmousemove = 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;}; + guac_mouse.getX = function() {return mouseX;}; + guac_mouse.getY = function() {return mouseY;}; + guac_mouse.getLeftButton = function() {return mouseLeftButton;}; + guac_mouse.getMiddleButton = function() {return mouseMiddleButton;}; + guac_mouse.getRightButton = function() {return mouseRightButton;}; } From 3e65c4620442167c08e4a8880bb453e895acf207 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 5 Jul 2011 22:13:45 -0700 Subject: [PATCH 04/19] JSDoc + namespace, some cleanup. --- .../src/main/resources/guacamole.js | 21 +++--- .../src/main/resources/keyboard.js | 5 +- .../src/main/resources/layer.js | 74 ++++++++++++++----- .../src/main/resources/mouse.js | 4 +- .../src/main/resources/oskeyboard.js | 5 +- .../src/main/resources/tunnel.js | 5 +- 6 files changed, 82 insertions(+), 32 deletions(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index b13a80a56..371ba73e6 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -16,7 +16,10 @@ * You should have received a copy of the GNU Affero General Public License */ -function GuacamoleClient(display, tunnel) { +// Guacamole namespace +var Guacamole = Guacamole || {}; + +Guacamole.Client = function(display, tunnel) { var guac_client = this; @@ -150,7 +153,7 @@ function GuacamoleClient(display, tunnel) { // Create buffer if necessary if (buffer == null) { - buffer = new Layer(0, 0); + buffer = new Guacamole.Layer(0, 0); buffer.setAutosize(1); buffers[index] = buffer; } @@ -165,7 +168,7 @@ function GuacamoleClient(display, tunnel) { if (layer == null) { // Add new layer - layer = new Layer(displayWidth, displayHeight); + layer = new Guacamole.Layer(displayWidth, displayHeight); layers[index] = layer; // (Re)-add existing layers in order @@ -174,18 +177,18 @@ function GuacamoleClient(display, tunnel) { // If already present, remove if (layers[i].parentNode === display) - display.removeChild(layers[i]); + display.removeChild(layers[i].getCanvas()); // Add to end - display.appendChild(layers[i]); + display.appendChild(layers[i].getCanvas()); } } // Add cursor layer last if (cursor != null) { if (cursor.parentNode === display) - display.removeChild(cursor); - display.appendChild(cursor); + display.removeChild(cursor.getCanvas()); + display.appendChild(cursor.getCanvas()); } } @@ -284,8 +287,8 @@ function GuacamoleClient(display, tunnel) { var data = parameters[2]; if (cursor == null) { - cursor = new Layer(displayWidth, displayHeight); - display.appendChild(cursor); + cursor = new Guacamole.Layer(displayWidth, displayHeight); + display.appendChild(cursor.getCanvas()); } // Start cursor image load diff --git a/guacamole-common-js/src/main/resources/keyboard.js b/guacamole-common-js/src/main/resources/keyboard.js index edbe313fa..e798f59f7 100644 --- a/guacamole-common-js/src/main/resources/keyboard.js +++ b/guacamole-common-js/src/main/resources/keyboard.js @@ -17,7 +17,10 @@ * along with this program. If not, see . */ -function GuacamoleKeyboard(element) { +// Guacamole namespace +var Guacamole = Guacamole || {}; + +Guacamole.Keyboard = function(element) { var guac_keyboard = this; diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 775b23683..16fef960a 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -17,12 +17,40 @@ * along with this program. If not, see . */ -function Layer(width, height) { +// Guacamole namespace +var Guacamole = Guacamole || {}; + +/** + * Abstract ordered drawing surface. Each Layer contains a canvas element and + * provides simple drawing instructions for drawing to that canvas element, + * however unlike the canvas element itself, drawing operations on a Layer are + * guaranteed to run in order, even if such an operation must wait for an image + * to load before completing. + * + * @constructor + * + * @param {Number} width The width of the Layer, in pixels. The canvas element + * backing this Layer will be given this width. + * + * @param {Number} height The height of the Layer, in pixels. The canvas element + * backing this Layer will be given this height. + */ +Guacamole.Layer = function(width, height) { + + var layer = this; // Off-screen buffer var display = document.createElement("canvas"); var displayContext = display.getContext("2d"); + /** + * Returns the canvas element backing this Layer. + * @returns {Element} The canvas element backing this Layer. + */ + this.getCanvas = function() { + return display; + }; + function resize(newWidth, newHeight) { display.style.position = "absolute"; display.style.left = "0px"; @@ -35,7 +63,15 @@ function Layer(width, height) { height = newHeight; } - display.resize = function(newWidth, newHeight) { + /** + * Changes the size of this Layer to the given width and height. Resizing + * is only attempted if the new size provided is actually different from + * the current size. + * + * @param {Number} newWidth The new width to assign to this Layer. + * @param {Number} newHeight The new height to assign to this Layer. + */ + this.resize = function(newWidth, newHeight) { if (newWidth != width || newHeight != height) resize(newWidth, newHeight); }; @@ -87,14 +123,14 @@ function Layer(width, height) { } - display.setAutosize = function(flag) { + this.setAutosize = function(flag) { autosize = flag; }; function reserveJob(handler) { // If no pending updates, just call (if available) and exit - if (display.isReady() && handler != null) { + if (layer.isReady() && handler != null) { handler(); return null; } @@ -118,12 +154,12 @@ function Layer(width, height) { } - display.isReady = function() { + this.isReady = function() { return updates.length == 0; }; - display.drawImage = function(x, y, image) { + this.drawImage = function(x, y, image) { reserveJob(function() { if (autosize != 0) fitRect(x, y, image.width, image.height); displayContext.drawImage(image, x, y); @@ -131,7 +167,7 @@ function Layer(width, height) { }; - display.draw = function(x, y, url) { + this.draw = function(x, y, url) { var update = reserveJob(null); var image = new Image(); @@ -151,14 +187,18 @@ function Layer(width, height) { }; - // Run arbitrary function as soon as currently pending operations complete. - // Future operations will not block this function from being called (unlike - // the ready handler, which requires no pending updates) - display.sync = function(handler) { + /** + * Run an arbitrary function as soon as currently pending operations + * are complete. + * + * @param {function} handler The function to call once all currently + * pending operations are complete. + */ + this.sync = function(handler) { reserveJob(handler); }; - display.copyRect = function(srcLayer, srcx, srcy, w, h, x, y) { + this.copyRect = function(srcLayer, srcx, srcy, w, h, x, y) { function doCopyRect() { if (autosize != 0) fitRect(x, y, w, h); @@ -167,7 +207,7 @@ function Layer(width, height) { // If we ARE the source layer, no need to sync. // Syncing would result in deadlock. - if (display === srcLayer) + if (layer === srcLayer) reserveJob(doCopyRect); // Otherwise synchronize copy operation with source layer @@ -186,14 +226,14 @@ function Layer(width, height) { }; - display.clearRect = function(x, y, w, h) { + this.clearRect = function(x, y, w, h) { reserveJob(function() { if (autosize != 0) fitRect(x, y, w, h); displayContext.clearRect(x, y, w, h); }); }; - display.filter = function(filter) { + this.filter = function(filter) { reserveJob(function() { var imageData = displayContext.getImageData(0, 0, width, height); filter(imageData.data, width, height); @@ -220,13 +260,11 @@ function Layer(width, height) { 0xF: "lighter" }; - display.setChannelMask = function(mask) { + this.setChannelMask = function(mask) { reserveJob(function() { displayContext.globalCompositeOperation = compositeOperation[mask]; }); }; - return display; - } diff --git a/guacamole-common-js/src/main/resources/mouse.js b/guacamole-common-js/src/main/resources/mouse.js index 31d5185d0..ec4c7b1a2 100644 --- a/guacamole-common-js/src/main/resources/mouse.js +++ b/guacamole-common-js/src/main/resources/mouse.js @@ -17,8 +17,10 @@ * along with this program. If not, see . */ +// Guacamole namespace +var Guacamole = Guacamole || {}; -function GuacamoleMouse(element) { +Guacamole.Mouse = function(element) { var guac_mouse = this; diff --git a/guacamole-common-js/src/main/resources/oskeyboard.js b/guacamole-common-js/src/main/resources/oskeyboard.js index 872bac223..0e6a9ccef 100644 --- a/guacamole-common-js/src/main/resources/oskeyboard.js +++ b/guacamole-common-js/src/main/resources/oskeyboard.js @@ -17,10 +17,11 @@ * along with this program. If not, see . */ +// Guacamole namespace +var Guacamole = Guacamole || {}; -function GuacamoleOnScreenKeyboard(url) { +Guacamole.OnScreenKeyboard = function(url) { - var tabIndex = 1; var allKeys = new Array(); var modifierState = new function() {}; diff --git a/guacamole-common-js/src/main/resources/tunnel.js b/guacamole-common-js/src/main/resources/tunnel.js index bb89b241f..e092088cb 100644 --- a/guacamole-common-js/src/main/resources/tunnel.js +++ b/guacamole-common-js/src/main/resources/tunnel.js @@ -16,7 +16,10 @@ * You should have received a copy of the GNU Affero General Public License */ -function GuacamoleHTTPTunnel(tunnelURL) { +// Guacamole namespace +var Guacamole = Guacamole || {}; + +Guacamole.HTTPTunnel = function(tunnelURL) { var tunnel = this; From c7b880d1bc311b0b6b3f0c6c7922d35bb9d26f7d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 5 Jul 2011 23:08:34 -0700 Subject: [PATCH 05/19] More JSDoc. --- .../src/main/resources/guacamole.js | 2 +- .../src/main/resources/layer.js | 108 ++++++++++++++++-- 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index 371ba73e6..f4cf49604 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -154,7 +154,7 @@ Guacamole.Client = function(display, tunnel) { // Create buffer if necessary if (buffer == null) { buffer = new Guacamole.Layer(0, 0); - buffer.setAutosize(1); + buffer.autosize = 1; buffers[index] = buffer; } diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 16fef960a..01acafbe0 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -37,9 +37,11 @@ var Guacamole = Guacamole || {}; */ Guacamole.Layer = function(width, height) { + // Reference to this Layer var layer = this; - // Off-screen buffer + // Off-screen buffer (canvas element) and corresponding + // context. var display = document.createElement("canvas"); var displayContext = display.getContext("2d"); @@ -51,6 +53,14 @@ Guacamole.Layer = function(width, height) { return display; }; + /** + * Resizes the canvas element backing this Layer without testing the + * new size. This function should only be used internally. + * + * @private + * @param {Number} newWidth The new width to assign to this Layer. + * @param {Number} newHeight The new height to assign to this Layer. + */ function resize(newWidth, newHeight) { display.style.position = "absolute"; display.style.left = "0px"; @@ -76,6 +86,22 @@ Guacamole.Layer = function(width, height) { resize(newWidth, newHeight); }; + /** + * Given the X and Y coordinates of the upper-left corner of a rectangle + * and the rectangle's width and height, resize the backing canvas element + * as necessary to ensure that the rectangle fits within the canvas + * element's coordinate space. This function will only make the canvas + * larger. If the rectangle already fits within the canvas element's + * coordinate space, the canvas is left unchanged. + * + * @private + * @param {Number} x The X coordinate of the upper-left corner of the + * rectangle to fit. + * @param {Number} y The Y coordinate of the upper-left corner of the + * rectangle to fit. + * @param {Number} w The width of the the rectangle to fit. + * @param {Number} h The height of the the rectangle to fit. + */ function fitRect(x, y, w, h) { // Calculate bounds @@ -102,10 +128,31 @@ Guacamole.Layer = function(width, height) { } + // Initialize canvas dimensions resize(width, height); + /** + * Set to true if this Layer should resize itself to accomodate the + * dimensions of any drawing operation, and false (the default) otherwise. + * + * Note that setting this property takes effect immediately, and thus may + * take effect on operations that were started in the past but have not + * yet completed. If you wish the setting of this flag to only modify + * future operations, you will need to make the setting of this flag an + * operation with sync(). + * + * @example + * // Set autosize to true for all future operations + * layer.sync(function() { + * layer.autosize = true; + * }); + * + * @type Boolean + * @default false + */ + this.autosize = false; + var updates = new Array(); - var autosize = 0; function Update(updateHandler) { @@ -123,10 +170,6 @@ Guacamole.Layer = function(width, height) { } - this.setAutosize = function(flag) { - autosize = flag; - }; - function reserveJob(handler) { // If no pending updates, just call (if available) and exit @@ -154,11 +197,25 @@ Guacamole.Layer = function(width, height) { } + /** + * Returns whether this Layer is ready. A Layer is ready if it has no + * pending operations and no operations in-progress. + * + * @returns {Boolean} true if this Layer is ready, false otherwise. + */ this.isReady = function() { return updates.length == 0; }; - + /** + * Draws the specified image at the given coordinates. The image specified + * must already be loaded. + * + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {Image} image The image to draw. Note that this is an Image + * object - not a URL. + */ this.drawImage = function(x, y, image) { reserveJob(function() { if (autosize != 0) fitRect(x, y, image.width, image.height); @@ -166,7 +223,15 @@ Guacamole.Layer = function(width, height) { }); }; - + /** + * Draws the image at the specified URL at the given coordinates. The image + * will be loaded automatically, and this and any future operations will + * wait for the image to finish loading. + * + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + * @param {String} url The URL of the image to draw. + */ this.draw = function(x, y, url) { var update = reserveJob(null); @@ -198,11 +263,32 @@ Guacamole.Layer = function(width, height) { reserveJob(handler); }; - this.copyRect = function(srcLayer, srcx, srcy, w, h, x, y) { + /** + * Copy a rectangle of image data from one Layer to this Layer. This + * operation will copy exactly the image data that will be drawn once all + * operations of the source Layer that were pending at the time this + * function was called are complete. This operation will not alter the + * size of the source Layer even if its autosize property is set to true. + * + * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. + * @param {Number} srcx The X coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcy The Y coordinate of the upper-left corner of the + * rectangle within the source Layer's coordinate + * space to copy data from. + * @param {Number} srcw The width of the rectangle within the source Layer's + * coordinate space to copy data from. + * @param {Number} srch The height of the rectangle within the source + * Layer's coordinate space to copy data from. + * @param {Number} x The destination X coordinate. + * @param {Number} y The destination Y coordinate. + */ + this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) { function doCopyRect() { - if (autosize != 0) fitRect(x, y, w, h); - displayContext.drawImage(srcLayer, srcx, srcy, w, h, x, y, w, h); + if (autosize != 0) fitRect(x, y, srcw, srch); + displayContext.drawImage(srcLayer, srcx, srcy, srcw, srch, x, y, srcw, srch); } // If we ARE the source layer, no need to sync. From 086192f4146c9327e5fea98e375c840ef05bee9c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 6 Jul 2011 13:14:40 -0700 Subject: [PATCH 06/19] More JSDoc, renamed Update to Task, fixed wording. --- .../src/main/resources/layer.js | 158 ++++++++++-------- 1 file changed, 87 insertions(+), 71 deletions(-) diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 01acafbe0..db470fa3f 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -37,14 +37,45 @@ var Guacamole = Guacamole || {}; */ Guacamole.Layer = function(width, height) { - // Reference to this Layer + /** + * Reference to this Layer. + * @private + */ var layer = this; - // Off-screen buffer (canvas element) and corresponding - // context. + /** + * The canvas element backing this Layer. + * @private + */ var display = document.createElement("canvas"); + + /** + * The 2D display context of the canvas element backing this Layer. + * @private + */ var displayContext = display.getContext("2d"); + var tasks = new Array(); + + var compositeOperation = { + /* 0x0 NOT IMPLEMENTED */ + 0x1: "destination-in", + 0x2: "destination-out", + /* 0x3 NOT IMPLEMENTED */ + 0x4: "source-in", + /* 0x5 NOT IMPLEMENTED */ + 0x6: "source-atop", + /* 0x7 NOT IMPLEMENTED */ + 0x8: "source-out", + 0x9: "destination-atop", + 0xA: "xor", + 0xB: "destination-over", + 0xC: "copy", + /* 0xD NOT IMPLEMENTED */ + 0xE: "source-over", + 0xF: "lighter" + }; + /** * Returns the canvas element backing this Layer. * @returns {Element} The canvas element backing this Layer. @@ -128,9 +159,6 @@ Guacamole.Layer = function(width, height) { } - // Initialize canvas dimensions - resize(width, height); - /** * Set to true if this Layer should resize itself to accomodate the * dimensions of any drawing operation, and false (the default) otherwise. @@ -152,47 +180,51 @@ Guacamole.Layer = function(width, height) { */ this.autosize = false; - var updates = new Array(); - - function Update(updateHandler) { - - this.setHandler = function(handler) { - updateHandler = handler; - }; - - this.hasHandler = function() { - return updateHandler != null; - }; - - this.handle = function() { - updateHandler(); - }; - + /** + * A container for an task handler. Each operation which must be ordered + * is associated with a Task that goes into a task queue. Tasks in this + * queue are executed in order once their handlers are set, while Tasks + * without handlers block themselves and any following Tasks from running. + * + * @constructor + * @private + * @param {function} taskHandler The function to call when this task + * runs, if any. + */ + function Task(taskHandler) { + + /** + * The handler this Task is associated with, if any. + * + * @type function + */ + this.handler = taskHandler; + } - function reserveJob(handler) { + function scheduleTask(handler) { - // If no pending updates, just call (if available) and exit + // If no pending tasks, just call (if available) and exit if (layer.isReady() && handler != null) { handler(); return null; } - // If updates are pending/executing, schedule a pending update + // If tasks are pending/executing, schedule a pending task // and return a reference to it. - var update = new Update(handler); - updates.push(update); - return update; + var task = new Task(handler); + tasks.push(task); + return task; } - function handlePendingUpdates() { + function handlePendingTasks() { - // Draw all pending updates. - var update; - while ((update = updates[0]) != null && update.hasHandler()) { - update.handle(); - updates.shift(); + // Draw all pending tasks. + var task; + while ((task = tasks[0]) != null && task.handler) { + task.handler(); + tasks.shift(); } } @@ -204,7 +236,7 @@ Guacamole.Layer = function(width, height) { * @returns {Boolean} true if this Layer is ready, false otherwise. */ this.isReady = function() { - return updates.length == 0; + return tasks.length == 0; }; /** @@ -217,7 +249,7 @@ Guacamole.Layer = function(width, height) { * object - not a URL. */ this.drawImage = function(x, y, image) { - reserveJob(function() { + scheduleTask(function() { if (autosize != 0) fitRect(x, y, image.width, image.height); displayContext.drawImage(image, x, y); }); @@ -233,19 +265,19 @@ Guacamole.Layer = function(width, height) { * @param {String} url The URL of the image to draw. */ this.draw = function(x, y, url) { - var update = reserveJob(null); + var task = scheduleTask(null); var image = new Image(); image.onload = function() { - update.setHandler(function() { + task.handler = function() { if (autosize != 0) fitRect(x, y, image.width, image.height); displayContext.drawImage(image, x, y); - }); + }; - // As this update originally had no handler and may have blocked - // other updates, handle any blocked updates. - handlePendingUpdates(); + // As this task originally had no handler and may have blocked + // other tasks, handle any blocked tasks. + handlePendingTasks(); }; image.src = url; @@ -260,7 +292,7 @@ Guacamole.Layer = function(width, height) { * pending operations are complete. */ this.sync = function(handler) { - reserveJob(handler); + scheduleTask(handler); }; /** @@ -294,18 +326,18 @@ Guacamole.Layer = function(width, height) { // If we ARE the source layer, no need to sync. // Syncing would result in deadlock. if (layer === srcLayer) - reserveJob(doCopyRect); + scheduleTask(doCopyRect); // Otherwise synchronize copy operation with source layer else { - var update = reserveJob(null); + var task = scheduleTask(null); srcLayer.sync(function() { - update.setHandler(doCopyRect); + task.handler = doCopyRect; - // As this update originally had no handler and may have blocked - // other updates, handle any blocked updates. - handlePendingUpdates(); + // As this task originally had no handler and may have blocked + // other tasks, handle any blocked tasks. + handlePendingTasks(); }); } @@ -313,44 +345,28 @@ Guacamole.Layer = function(width, height) { }; this.clearRect = function(x, y, w, h) { - reserveJob(function() { + scheduleTask(function() { if (autosize != 0) fitRect(x, y, w, h); displayContext.clearRect(x, y, w, h); }); }; this.filter = function(filter) { - reserveJob(function() { + scheduleTask(function() { var imageData = displayContext.getImageData(0, 0, width, height); filter(imageData.data, width, height); displayContext.putImageData(imageData, 0, 0); }); }; - var compositeOperation = { - /* 0x0 NOT IMPLEMENTED */ - 0x1: "destination-in", - 0x2: "destination-out", - /* 0x3 NOT IMPLEMENTED */ - 0x4: "source-in", - /* 0x5 NOT IMPLEMENTED */ - 0x6: "source-atop", - /* 0x7 NOT IMPLEMENTED */ - 0x8: "source-out", - 0x9: "destination-atop", - 0xA: "xor", - 0xB: "destination-over", - 0xC: "copy", - /* 0xD NOT IMPLEMENTED */ - 0xE: "source-over", - 0xF: "lighter" - }; - this.setChannelMask = function(mask) { - reserveJob(function() { + scheduleTask(function() { displayContext.globalCompositeOperation = compositeOperation[mask]; }); }; + // Initialize canvas dimensions + resize(width, height); + } From 649d873373898e2d5857e9c1705ef89e17462ffc Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 6 Jul 2011 14:39:50 -0700 Subject: [PATCH 07/19] Finished JSDoc of Layer, more cleanup. --- .../src/main/resources/layer.js | 153 ++++++++++++------ 1 file changed, 107 insertions(+), 46 deletions(-) diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index db470fa3f..4033abde8 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -48,6 +48,9 @@ Guacamole.Layer = function(width, height) { * @private */ var display = document.createElement("canvas"); + display.style.position = "absolute"; + display.style.left = "0px"; + display.style.top = "0px"; /** * The 2D display context of the canvas element backing this Layer. @@ -55,8 +58,19 @@ Guacamole.Layer = function(width, height) { */ var displayContext = display.getContext("2d"); + /** + * The queue of all pending Tasks. Tasks will be run in order, with new + * tasks added at the end of the queue and old tasks removed from the + * front of the queue (FIFO). + * @private + */ var tasks = new Array(); + /** + * Map of all Guacamole channel masks to HTML5 canvas composite operation + * names. Not all channel mask combinations are currently implemented. + * @private + */ var compositeOperation = { /* 0x0 NOT IMPLEMENTED */ 0x1: "destination-in", @@ -76,14 +90,6 @@ Guacamole.Layer = function(width, height) { 0xF: "lighter" }; - /** - * Returns the canvas element backing this Layer. - * @returns {Element} The canvas element backing this Layer. - */ - this.getCanvas = function() { - return display; - }; - /** * Resizes the canvas element backing this Layer without testing the * new size. This function should only be used internally. @@ -93,10 +99,6 @@ Guacamole.Layer = function(width, height) { * @param {Number} newHeight The new height to assign to this Layer. */ function resize(newWidth, newHeight) { - display.style.position = "absolute"; - display.style.left = "0px"; - display.style.top = "0px"; - display.width = newWidth; display.height = newHeight; @@ -104,19 +106,6 @@ Guacamole.Layer = function(width, height) { height = newHeight; } - /** - * Changes the size of this Layer to the given width and height. Resizing - * is only attempted if the new size provided is actually different from - * the current size. - * - * @param {Number} newWidth The new width to assign to this Layer. - * @param {Number} newHeight The new height to assign to this Layer. - */ - this.resize = function(newWidth, newHeight) { - if (newWidth != width || newHeight != height) - resize(newWidth, newHeight); - }; - /** * Given the X and Y coordinates of the upper-left corner of a rectangle * and the rectangle's width and height, resize the backing canvas element @@ -159,27 +148,6 @@ Guacamole.Layer = function(width, height) { } - /** - * Set to true if this Layer should resize itself to accomodate the - * dimensions of any drawing operation, and false (the default) otherwise. - * - * Note that setting this property takes effect immediately, and thus may - * take effect on operations that were started in the past but have not - * yet completed. If you wish the setting of this flag to only modify - * future operations, you will need to make the setting of this flag an - * operation with sync(). - * - * @example - * // Set autosize to true for all future operations - * layer.sync(function() { - * layer.autosize = true; - * }); - * - * @type Boolean - * @default false - */ - this.autosize = false; - /** * A container for an task handler. Each operation which must be ordered * is associated with a Task that goes into a task queue. Tasks in this @@ -202,6 +170,17 @@ Guacamole.Layer = function(width, height) { } + /** + * If no tasks are pending or running, run the provided handler immediately, + * if any. Otherwise, schedule a task to run immediately after all currently + * running or pending tasks are complete. + * + * @private + * @param {function} handler The function to call when possible, if any. + * @returns {Task} The Task created and added to the queue for future + * running, if any, or null if the handler was run + * immediately and no Task needed to be created. + */ function scheduleTask(handler) { // If no pending tasks, just call (if available) and exit @@ -218,6 +197,11 @@ Guacamole.Layer = function(width, height) { } + /** + * Run any Tasks which were pending but are now ready to run and are not + * blocked by other Tasks. + * @private + */ function handlePendingTasks() { // Draw all pending tasks. @@ -229,6 +213,35 @@ Guacamole.Layer = function(width, height) { } + /** + * Set to true if this Layer should resize itself to accomodate the + * dimensions of any drawing operation, and false (the default) otherwise. + * + * Note that setting this property takes effect immediately, and thus may + * take effect on operations that were started in the past but have not + * yet completed. If you wish the setting of this flag to only modify + * future operations, you will need to make the setting of this flag an + * operation with sync(). + * + * @example + * // Set autosize to true for all future operations + * layer.sync(function() { + * layer.autosize = true; + * }); + * + * @type Boolean + * @default false + */ + this.autosize = false; + + /** + * Returns the canvas element backing this Layer. + * @returns {Element} The canvas element backing this Layer. + */ + this.getCanvas = function() { + return display; + }; + /** * Returns whether this Layer is ready. A Layer is ready if it has no * pending operations and no operations in-progress. @@ -239,6 +252,21 @@ Guacamole.Layer = function(width, height) { return tasks.length == 0; }; + /** + * Changes the size of this Layer to the given width and height. Resizing + * is only attempted if the new size provided is actually different from + * the current size. + * + * @param {Number} newWidth The new width to assign to this Layer. + * @param {Number} newHeight The new height to assign to this Layer. + */ + this.resize = function(newWidth, newHeight) { + scheduleTask(function() { + if (newWidth != width || newHeight != height) + resize(newWidth, newHeight); + }); + }; + /** * Draws the specified image at the given coordinates. The image specified * must already be loaded. @@ -344,6 +372,16 @@ Guacamole.Layer = function(width, height) { }; + /** + * Clear the specified rectangle of image data. + * + * @param {Number} x The X coordinate of the upper-left corner of the + * rectangle to clear. + * @param {Number} y The Y coordinate of the upper-left corner of the + * rectangle to clear. + * @param {Number} w The width of the rectangle to clear. + * @param {Number} h The height of the rectangle to clear. + */ this.clearRect = function(x, y, w, h) { scheduleTask(function() { if (autosize != 0) fitRect(x, y, w, h); @@ -351,6 +389,18 @@ Guacamole.Layer = function(width, height) { }); }; + /** + * Provides the given filtering function with a writable snapshot of + * image data and the current width and height of the Layer. + * + * @param {function} filter A function which accepts an array of image + * data (as returned by the canvas element's + * display context's getImageData() function), + * the width of the Layer, and the height of the + * Layer as parameters, in that order. This + * function must accomplish its filtering by + * modifying the given image data array directly. + */ this.filter = function(filter) { scheduleTask(function() { var imageData = displayContext.getImageData(0, 0, width, height); @@ -359,6 +409,17 @@ Guacamole.Layer = function(width, height) { }); }; + /** + * Sets the channel mask for future operations on this Layer. The channel + * mask is a Guacamole-specific compositing operation identifier with a + * single bit representing each of four channels (in order): source image + * where destination transparent, source where destination opaque, + * destination where source transparent, and destination where source + * opaque. + * + * @param {Number} mask The channel mask for future operations on this + * Layer. + */ this.setChannelMask = function(mask) { scheduleTask(function() { displayContext.globalCompositeOperation = compositeOperation[mask]; From 867e64637fa8df8bcd09a9a9f19bffbe9a6a920c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 6 Jul 2011 15:33:11 -0700 Subject: [PATCH 08/19] Fixed autosize -> layer.autosize. --- guacamole-common-js/src/main/resources/layer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 4033abde8..02cd28e9d 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -278,7 +278,7 @@ Guacamole.Layer = function(width, height) { */ this.drawImage = function(x, y, image) { scheduleTask(function() { - if (autosize != 0) fitRect(x, y, image.width, image.height); + if (layer.autosize != 0) fitRect(x, y, image.width, image.height); displayContext.drawImage(image, x, y); }); }; @@ -299,7 +299,7 @@ Guacamole.Layer = function(width, height) { image.onload = function() { task.handler = function() { - if (autosize != 0) fitRect(x, y, image.width, image.height); + if (layer.autosize != 0) fitRect(x, y, image.width, image.height); displayContext.drawImage(image, x, y); }; @@ -347,7 +347,7 @@ Guacamole.Layer = function(width, height) { this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) { function doCopyRect() { - if (autosize != 0) fitRect(x, y, srcw, srch); + if (layer.autosize != 0) fitRect(x, y, srcw, srch); displayContext.drawImage(srcLayer, srcx, srcy, srcw, srch, x, y, srcw, srch); } @@ -384,7 +384,7 @@ Guacamole.Layer = function(width, height) { */ this.clearRect = function(x, y, w, h) { scheduleTask(function() { - if (autosize != 0) fitRect(x, y, w, h); + if (layer.autosize != 0) fitRect(x, y, w, h); displayContext.clearRect(x, y, w, h); }); }; From aea01a5cdb1ed8937f3625191798383c94515d68 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 7 Jul 2011 23:19:19 -0700 Subject: [PATCH 09/19] Better mouse state object, more JSDoc, cleanup. --- .../src/main/resources/guacamole.js | 31 ++- .../src/main/resources/layer.js | 6 +- .../src/main/resources/mouse.js | 193 ++++++++++-------- 3 files changed, 131 insertions(+), 99 deletions(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index f4cf49604..7009e7a47 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -99,21 +99,21 @@ Guacamole.Client = function(display, tunnel) { // Draw client-side cursor if (cursorImage != null) { redrawCursor( - mouseState.getX(), - mouseState.getY() + mouseState.x, + mouseState.y ); } // 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; + if (mouseState.left) buttonMask |= 1; + if (mouseState.middle) buttonMask |= 2; + if (mouseState.right) buttonMask |= 4; + if (mouseState.up) buttonMask |= 8; + if (mouseState.down) buttonMask |= 16; // Send message - tunnel.sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";"); + tunnel.sendMessage("mouse:" + mouseState.x + "," + mouseState.y + "," + buttonMask + ";"); }; guac_client.setClipboard = function(data) { @@ -169,6 +169,13 @@ Guacamole.Client = function(display, tunnel) { // Add new layer layer = new Guacamole.Layer(displayWidth, displayHeight); + + // Set layer position + var canvas = layer.getCanvas(); + canvas.style.position = "absolute"; + canvas.style.left = "0px"; + canvas.style.top = "0px"; + layers[index] = layer; // (Re)-add existing layers in order @@ -288,7 +295,13 @@ Guacamole.Client = function(display, tunnel) { if (cursor == null) { cursor = new Guacamole.Layer(displayWidth, displayHeight); - display.appendChild(cursor.getCanvas()); + + var canvas = cursor.getCanvas(); + canvas.style.position = "absolute"; + canvas.style.left = "0px"; + canvas.style.top = "0px"; + + display.appendChild(canvas); } // Start cursor image load diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 02cd28e9d..467b4cc66 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -48,9 +48,6 @@ Guacamole.Layer = function(width, height) { * @private */ var display = document.createElement("canvas"); - display.style.position = "absolute"; - display.style.left = "0px"; - display.style.top = "0px"; /** * The 2D display context of the canvas element backing this Layer. @@ -429,5 +426,4 @@ Guacamole.Layer = function(width, height) { // Initialize canvas dimensions resize(width, height); -} - +}; diff --git a/guacamole-common-js/src/main/resources/mouse.js b/guacamole-common-js/src/main/resources/mouse.js index ec4c7b1a2..a8f3a8b97 100644 --- a/guacamole-common-js/src/main/resources/mouse.js +++ b/guacamole-common-js/src/main/resources/mouse.js @@ -20,54 +20,93 @@ // Guacamole namespace var Guacamole = Guacamole || {}; +/** + * Provides cross-browser mouse events for a given element. The events of + * the given element are automatically populated with handlers that translate + * mouse events into a non-browser-specific event provided by the + * Guacamole.Mouse instance. + * + * Touch event support is planned, but currently only in testing (translate + * touch events into mouse events). + * + * @constructor + * @param {Element} element The Element to use to provide mouse events. + */ Guacamole.Mouse = function(element) { + /** + * Reference to this Guacamole.Mouse. + * @private + */ var guac_mouse = this; - var mouseLeftButton = 0; - var mouseMiddleButton = 0; - var mouseRightButton = 0; + /** + * The current mouse state. The properties of this state are updated when + * mouse events fire. This state object is also passed in as a parameter to + * the handler of any mouse events. + * + * @type Guacamole.Mouse.State + */ + this.currentState = new Guacamole.Mouse.State( + 0, 0, + false, false, false, false, false + ); - var mouseX = 0; - var mouseY = 0; + /** + * Fired whenever the user presses a mouse button down over the element + * associated with this Guacamole.Mouse. + * + * @event + * @param {Guacamole.Mouse.State} state The current mouse state. + */ + this.onmousedown = null; - var absoluteMouseX = 0; - var absoluteMouseY = 0; - - - function getMouseState(up, down) { - var mouseState = new MouseEvent(mouseX, mouseY, - mouseLeftButton, mouseMiddleButton, mouseRightButton, up, down); - - return mouseState; - } + /** + * Fired whenever the user releases a mouse button down over the element + * associated with this Guacamole.Mouse. + * + * @event + * @param {Guacamole.Mouse.State} state The current mouse state. + */ + this.onmouseup = null; + /** + * Fired whenever the user moves the mouse over the element associated with + * this Guacamole.Mouse. + * + * @event + * @param {Guacamole.Mouse.State} state The current mouse state. + */ + this.onmousemove = null; // Block context menu so right-click gets sent properly - element.oncontextmenu = function(e) {return false;}; + element.oncontextmenu = function(e) { + return false; + }; element.onmousemove = function(e) { e.stopPropagation(); - absoluteMouseX = e.pageX; - absoluteMouseY = e.pageY; + var absoluteMouseX = e.pageX; + var absoluteMouseY = e.pageY; - mouseX = absoluteMouseX - element.offsetLeft; - mouseY = absoluteMouseY - element.offsetTop; + guac_mouse.currentState.x = absoluteMouseX - element.offsetLeft; + guac_mouse.currentState.y = 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; + guac_mouse.currentState.x -= parent.offsetLeft; + guac_mouse.currentState.y -= parent.offsetTop; } parent = parent.offsetParent; } if (guac_mouse.onmousemove) - guac_mouse.onmousemove(getMouseState(0, 0)); + guac_mouse.onmousemove(guac_mouse.currentState); + }; @@ -77,18 +116,19 @@ Guacamole.Mouse = function(element) { switch (e.button) { case 0: - mouseLeftButton = 1; + guac_mouse.currentState.left = true; break; case 1: - mouseMiddleButton = 1; + guac_mouse.currentState.middle = true; break; case 2: - mouseRightButton = 1; + guac_mouse.currentState.right = true; break; } if (guac_mouse.onmousedown) - guac_mouse.onmousedown(getMouseState(0, 0)); + guac_mouse.onmousedown(guac_mouse.currentState); + }; @@ -98,18 +138,19 @@ Guacamole.Mouse = function(element) { switch (e.button) { case 0: - mouseLeftButton = 0; + guac_mouse.currentState.left = false; break; case 1: - mouseMiddleButton = 0; + guac_mouse.currentState.middle = false; break; case 2: - mouseRightButton = 0; + guac_mouse.currentState.right = false; break; } if (guac_mouse.onmouseup) - guac_mouse.onmouseup(getMouseState(0, 0)); + guac_mouse.onmouseup(guac_mouse.currentState); + }; element.onmouseout = function(e) { @@ -117,13 +158,16 @@ Guacamole.Mouse = function(element) { e.stopPropagation(); // Release all buttons - if (mouseLeftButton || mouseMiddleButton || mouseRightButton) { - mouseLeftButton = 0; - mouseMiddleButton = 0; - mouseRightButton = 0; + if (guac_mouse.currentState.left + || guac_mouse.currentState.middle + || guac_mouse.currentState.right) { + + guac_mouse.currentState.left = false; + guac_mouse.currentState.middle = false; + guac_mouse.currentState.right = false; if (guac_mouse.onmouseup) - guac_mouse.onmouseup(getMouseState(0, 0)); + guac_mouse.onmouseup(guac_mouse.currentState); } }; @@ -144,20 +188,28 @@ Guacamole.Mouse = function(element) { // Up if (delta < 0) { - if (guac_mouse.onmousedown) - guac_mouse.onmousedown(getMouseState(1, 0)); + if (guac_mouse.onmousedown) { + guac_mouse.currentState.up = true; + guac_mouse.onmousedown(guac_mouse.currentState); + } - if (guac_mouse.onmouseup) - guac_mouse.onmouseup(getMouseState(0, 0)); + if (guac_mouse.onmouseup) { + guac_mouse.currentState.up = false; + guac_mouse.onmouseup(guac_mouse.currentState); + } } // Down if (delta > 0) { - if (guac_mouse.onmousedown) - guac_mouse.onmousedown(getMouseState(0, 1)); - - if (guac_mouse.onmouseup) - guac_mouse.onmouseup(getMouseState(0, 0)); + if (guac_mouse.onmousedown) { + guac_mouse.currentState.down = true; + guac_mouse.onmousedown(guac_mouse.currentState); + } + + if (guac_mouse.onmouseup) { + guac_mouse.currentState.down = false; + guac_mouse.onmouseup(guac_mouse.currentState); + } } if (e.preventDefault) @@ -170,48 +222,19 @@ Guacamole.Mouse = function(element) { element.onmousewheel = function(e) { handleScroll(e); - } - - guac_mouse.onmousedown = null; - guac_mouse.onmouseup = null; - guac_mouse.onmousemove = null; - - guac_mouse.getX = function() {return mouseX;}; - guac_mouse.getY = function() {return mouseY;}; - guac_mouse.getLeftButton = function() {return mouseLeftButton;}; - guac_mouse.getMiddleButton = function() {return mouseMiddleButton;}; - guac_mouse.getRightButton = function() {return mouseRightButton;}; - -} - -function MouseEvent(x, y, left, middle, right, up, down) { - - this.getX = function() { - return x; }; - this.getY = function() { - return y; - }; +}; - this.getLeft = function() { - return left; - }; +Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) { - this.getMiddle = function() { - return middle; - }; + this.x = x; + this.y = y; + this.left = left; + this.middle = middle + this.right = right; + this.up = up; + this.down = down; + +}; - this.getRight = function() { - return right; - }; - - this.getUp = function() { - return up; - }; - - this.getDown = function() { - return down; - }; - -} From 3684bc5522996204b57be26b99c3ce4578026b3b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 7 Jul 2011 23:28:06 -0700 Subject: [PATCH 10/19] Fixed type error in copy rect of Layer. --- guacamole-common-js/src/main/resources/layer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 467b4cc66..7c8c82235 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -345,7 +345,7 @@ Guacamole.Layer = function(width, height) { function doCopyRect() { if (layer.autosize != 0) fitRect(x, y, srcw, srch); - displayContext.drawImage(srcLayer, srcx, srcy, srcw, srch, x, y, srcw, srch); + displayContext.drawImage(srcLayer.getCanvas(), srcx, srcy, srcw, srch, x, y, srcw, srch); } // If we ARE the source layer, no need to sync. From 5ea2be369c73d890bb7d75d109301b5197a64eff Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 7 Jul 2011 23:48:32 -0700 Subject: [PATCH 11/19] Some JSDoc for keyboard, cleaned up keymap. --- .../src/main/resources/keyboard.js | 109 +++++++++++------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/guacamole-common-js/src/main/resources/keyboard.js b/guacamole-common-js/src/main/resources/keyboard.js index e798f59f7..cc1f72695 100644 --- a/guacamole-common-js/src/main/resources/keyboard.js +++ b/guacamole-common-js/src/main/resources/keyboard.js @@ -20,54 +20,76 @@ // Guacamole namespace var Guacamole = Guacamole || {}; +/** + * Provides cross-browser and cross-keyboard keyboard for a specific element. + * Browser and keyboard layout variation is abstracted away, providing events + * which represent keys as their corresponding X11 keysym. + * + * @constructor + * @param {Element} element The Element to use to provide keyboard events. + */ Guacamole.Keyboard = function(element) { + /** + * Reference to this Guacamole.Keyboard. + * @private + */ var guac_keyboard = this; - // Keymap + /** + * Map of known JavaScript keycodes which do not map to typable characters + * to their unshifted X11 keysym equivalents. + * @private + */ + var unshiftedKeySym = { + 8: 0xFF08, // backspace + 9: 0xFF09, // tab + 13: 0xFF0D, // enter + 16: 0xFFE1, // shift + 17: 0xFFE3, // ctrl + 18: 0xFFE9, // alt + 19: 0xFF13, // pause/break + 20: 0xFFE5, // caps lock + 27: 0xFF1B, // escape + 33: 0xFF55, // page up + 34: 0xFF56, // page down + 35: 0xFF57, // end + 36: 0xFF50, // home + 37: 0xFF51, // left arrow + 38: 0xFF52, // up arrow + 39: 0xFF53, // right arrow + 40: 0xFF54, // down arrow + 45: 0xFF63, // insert + 46: 0xFFFF, // delete + 91: 0xFFEB, // left window key (super_l) + 92: 0xFF67, // right window key (menu key?) + 93: null, // select key + 112: 0xFFBE, // f1 + 113: 0xFFBF, // f2 + 114: 0xFFC0, // f3 + 115: 0xFFC1, // f4 + 116: 0xFFC2, // f5 + 117: 0xFFC3, // f6 + 118: 0xFFC4, // f7 + 119: 0xFFC5, // f8 + 120: 0xFFC6, // f9 + 121: 0xFFC7, // f10 + 122: 0xFFC8, // f11 + 123: 0xFFC9, // f12 + 144: 0xFF7F, // num lock + 145: 0xFF14 // scroll lock + }; - 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 + /** + * Map of known JavaScript keycodes which do not map to typable characters + * to their shifted X11 keysym equivalents. Keycodes must only be listed + * here if their shifted X11 keysym equivalents differ from their unshifted + * equivalents. + * @private + */ + var shiftedKeySym = { + 18: 0xFFE7 // alt + }; // Single key state/modifier buffer var modShift = 0; @@ -76,7 +98,6 @@ Guacamole.Keyboard = function(element) { var keydownChar = new Array(); - // ID of routine repeating keystrokes. -1 = not repeating. var repeatKeyTimeoutId = -1; var repeatKeyIntervalId = -1; From a4d810969d7d9bc22833401eec8864d2503ca0f5 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 8 Jul 2011 09:16:52 -0700 Subject: [PATCH 12/19] Cleanup, JSDoc for keyboard events. --- .../src/main/resources/keyboard.js | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/guacamole-common-js/src/main/resources/keyboard.js b/guacamole-common-js/src/main/resources/keyboard.js index cc1f72695..3dfc31a16 100644 --- a/guacamole-common-js/src/main/resources/keyboard.js +++ b/guacamole-common-js/src/main/resources/keyboard.js @@ -36,6 +36,24 @@ Guacamole.Keyboard = function(element) { */ var guac_keyboard = this; + /** + * Fired whenever the user presses a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being pressed. + */ + this.onkeydown = null; + + /** + * Fired whenever the user releases a key with the element associated + * with this Guacamole.Keyboard in focus. + * + * @event + * @param {Number} keysym The keysym of the key being released. + */ + this.onkeyup = null; + /** * Map of known JavaScript keycodes which do not map to typable characters * to their unshifted X11 keysym equivalents. @@ -92,9 +110,9 @@ Guacamole.Keyboard = function(element) { }; // Single key state/modifier buffer - var modShift = 0; - var modCtrl = 0; - var modAlt = 0; + var modShift = false; + var modCtrl = false; + var modAlt = false; var keydownChar = new Array(); @@ -158,7 +176,7 @@ Guacamole.Keyboard = function(element) { function getKeySymFromKeyCode(keyCode) { var keysym = null; - if (modShift == 0) keysym = unshiftedKeySym[keyCode]; + if (!modShift) keysym = unshiftedKeySym[keyCode]; else { keysym = shiftedKeySym[keyCode]; if (keysym == null) keysym = unshiftedKeySym[keyCode]; @@ -200,11 +218,11 @@ Guacamole.Keyboard = function(element) { // Ctrl/Alt/Shift if (keynum == 16) - modShift = 1; + modShift = true; else if (keynum == 17) - modCtrl = 1; + modCtrl = true; else if (keynum == 18) - modAlt = 1; + modAlt = true; var keysym = getKeySymFromKeyCode(keynum); if (keysym) { @@ -213,7 +231,7 @@ Guacamole.Keyboard = function(element) { } // If modifier keys are held down, and we have keyIdentifier - else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) { + else if ((modCtrl || modAlt) && e.keyIdentifier) { // Get keysym from keyIdentifier keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier); @@ -301,11 +319,11 @@ Guacamole.Keyboard = function(element) { // Ctrl/Alt/Shift if (keynum == 16) - modShift = 0; + modShift = false; else if (keynum == 17) - modCtrl = 0; + modCtrl = false; else if (keynum == 18) - modAlt = 0; + modAlt = false; else stopRepeat(); @@ -322,15 +340,10 @@ Guacamole.Keyboard = function(element) { }; // When focus is lost, clear modifiers. - var docOnblur = element.onblur; element.onblur = function() { - modAlt = 0; - modCtrl = 0; - modShift = 0; - if (docOnblur != null) docOnblur(); + modAlt = false; + modCtrl = false; + modShift = false; }; - guac_keyboard.onkeydown = null; - guac_keyboard.onkeyup = null; - -} +}; From 4b4f21e9ab641825abd882a75b296daea405efca Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sat, 9 Jul 2011 09:12:06 -0700 Subject: [PATCH 13/19] More JSDoc and cleanup. --- .../src/main/resources/guacamole.js | 10 ++++ .../src/main/resources/mouse.js | 52 +++++++++++++++++++ .../src/main/resources/oskeyboard.js | 8 +++ .../src/main/resources/tunnel.js | 37 +++++++++++-- 4 files changed, 102 insertions(+), 5 deletions(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index 7009e7a47..3bff5d0fe 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -19,6 +19,16 @@ // Guacamole namespace var Guacamole = Guacamole || {}; +/** + * Guacamole protocol client. Given a display element and {@link Guacamole.Tunnel}, + * automatically handles incoming and outgoing Guacamole instructions via the + * provided tunnel, updating the display using one or more canvas elements. + * + * @constructor + * @param {Element} display The display element to add canvas elements to. + * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive + * Guacamole instructions. + */ Guacamole.Client = function(display, tunnel) { var guac_client = this; diff --git a/guacamole-common-js/src/main/resources/mouse.js b/guacamole-common-js/src/main/resources/mouse.js index a8f3a8b97..cb2af79cd 100644 --- a/guacamole-common-js/src/main/resources/mouse.js +++ b/guacamole-common-js/src/main/resources/mouse.js @@ -226,14 +226,66 @@ Guacamole.Mouse = function(element) { }; +/** + * Simple container for properties describing the state of a mouse. + * + * @constructor + * @param {Number} x The X position of the mouse pointer in pixels. + * @param {Number} y The Y position of the mouse pointer in pixels. + * @param {Boolean} left Whether the left mouse button is pressed. + * @param {Boolean} middle Whether the middle mouse button is pressed. + * @param {Boolean} right Whether the right mouse button is pressed. + * @param {Boolean} up Whether the up mouse button is pressed (the fourth + * button, usually part of a scroll wheel). + * @param {Boolean} down Whether the down mouse button is pressed (the fifth + * button, usually part of a scroll wheel). + */ Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) { + /** + * The current X position of the mouse pointer. + * @type Number + */ this.x = x; + + /** + * The current Y position of the mouse pointer. + * @type Number + */ this.y = y; + + /** + * Whether the left mouse button is currently pressed. + * @type Boolean + */ this.left = left; + + /** + * Whether the middle mouse button is currently pressed. + * @type Boolean + */ this.middle = middle + + /** + * Whether the right mouse button is currently pressed. + * @type Boolean + */ this.right = right; + + /** + * Whether the up mouse button is currently pressed. This is the fourth + * mouse button, associated with upward scrolling of the mouse scroll + * wheel. + * @type Boolean + */ this.up = up; + + /** + * Whether the down mouse button is currently pressed. This is the fifth + * mouse button, associated with downward scrolling of the mouse scroll + * wheel. + * @type Boolean + */ this.down = down; }; diff --git a/guacamole-common-js/src/main/resources/oskeyboard.js b/guacamole-common-js/src/main/resources/oskeyboard.js index 0e6a9ccef..34c27810c 100644 --- a/guacamole-common-js/src/main/resources/oskeyboard.js +++ b/guacamole-common-js/src/main/resources/oskeyboard.js @@ -20,6 +20,14 @@ // Guacamole namespace var Guacamole = Guacamole || {}; +/** + * Dynamic on-screen keyboard. Given the URL to an XML keyboard layout file, + * this object will download and use the XML to construct a clickable on-screen + * keyboard with its own key events. + * + * @constructor + * @param {String} url The URL of an XML keyboard layout file. + */ Guacamole.OnScreenKeyboard = function(url) { var allKeys = new Array(); diff --git a/guacamole-common-js/src/main/resources/tunnel.js b/guacamole-common-js/src/main/resources/tunnel.js index e092088cb..d28758909 100644 --- a/guacamole-common-js/src/main/resources/tunnel.js +++ b/guacamole-common-js/src/main/resources/tunnel.js @@ -19,6 +19,35 @@ // Guacamole namespace var Guacamole = Guacamole || {}; +/** + * Core object providing abstract communication for Guacamole. This object + * is a null implementation whose functions do nothing. Guacamole applications + * should use {@link Guacamole.HTTPTunnel} instead, or implement their own tunnel based + * on this one. + * + * @constructor + * @see Guacamole.HTTPTunnel + */ +Guacamole.Tunnel = function() { + + this.connect = function(data) {}; + + this.disconnect = function() {}; + + this.sendMessage = function(message) {}; + + this.onerror = null; + this.oninstruction = null; + +}; + +/** + * Guacamole Tunnel implemented over HTTP via XMLHttpRequest. + * + * @constructor + * @augments Guacamole.Tunnel + * @param {String} tunnelURL The URL of the HTTP tunneling service. + */ Guacamole.HTTPTunnel = function(tunnelURL) { var tunnel = this; @@ -44,10 +73,6 @@ Guacamole.HTTPTunnel = function(tunnelURL) { var sendingMessages = 0; var outputMessageBuffer = ""; - // Handlers - tunnel.onerror = null; - tunnel.oninstruction = null; - function sendMessage(message) { // Do not attempt to send messages if not connected @@ -267,4 +292,6 @@ Guacamole.HTTPTunnel = function(tunnelURL) { tunnel.disconnect = disconnect; tunnel.sendMessage = sendMessage; -} +}; + +Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel(); From 0452f36f04f6a8e787ce52d877a347c95d49b5f1 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 10 Jul 2011 20:49:38 -0700 Subject: [PATCH 14/19] Tunnel JSDoc. --- .../src/main/resources/tunnel.js | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/guacamole-common-js/src/main/resources/tunnel.js b/guacamole-common-js/src/main/resources/tunnel.js index d28758909..0f33c3e26 100644 --- a/guacamole-common-js/src/main/resources/tunnel.js +++ b/guacamole-common-js/src/main/resources/tunnel.js @@ -29,14 +29,47 @@ var Guacamole = Guacamole || {}; * @see Guacamole.HTTPTunnel */ Guacamole.Tunnel = function() { - + + /** + * Connect to the tunnel with the given optional data. This data is + * typically used for authentication. The format of data accepted is + * up to the tunnel implementation. + * + * @param {String} data The data to send to the tunnel when connecting. + */ this.connect = function(data) {}; + /** + * Disconnect from the tunnel. + */ this.disconnect = function() {}; + /** + * Send the given message through the tunnel to the service on the other + * side. All messages are guaranteed to be received in the order sent. + * + * @param {String} message The message to send to the service on the other + * side of the tunnel. + */ this.sendMessage = function(message) {}; + /** + * Fired whenever an error is encountered by the tunnel. + * + * @event + * @param {String} message A human-readable description of the error that + * occurred. + */ this.onerror = null; + + /** + * Fired once for every complete Guacamole instruction received, in order. + * + * @event + * @param {String} opcode The Guacamole instruction opcode. + * @param {Array} parameters The parameters provided for the instruction, + * if any. + */ this.oninstruction = null; }; @@ -50,6 +83,9 @@ Guacamole.Tunnel = function() { */ Guacamole.HTTPTunnel = function(tunnelURL) { + /** + * Reference to this HTTP tunnel. + */ var tunnel = this; var tunnel_uuid; @@ -70,10 +106,10 @@ Guacamole.HTTPTunnel = function(tunnelURL) { // Default to polling - will be turned off automatically if not needed var pollingMode = POLLING_ENABLED; - var sendingMessages = 0; + var sendingMessages = false; var outputMessageBuffer = ""; - function sendMessage(message) { + this.sendMessage = function(message) { // Do not attempt to send messages if not connected if (currentState != STATE_CONNECTED) @@ -81,16 +117,16 @@ Guacamole.HTTPTunnel = function(tunnelURL) { // Add event to queue, restart send loop if finished. outputMessageBuffer += message; - if (sendingMessages == 0) + if (!sendingMessages) sendPendingMessages(); - } + }; function sendPendingMessages() { if (outputMessageBuffer.length > 0) { - sendingMessages = 1; + sendingMessages = true; var message_xmlhttprequest = new XMLHttpRequest(); message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid); @@ -107,7 +143,7 @@ Guacamole.HTTPTunnel = function(tunnelURL) { } else - sendingMessages = 0; + sendingMessages = false; } @@ -161,7 +197,7 @@ Guacamole.HTTPTunnel = function(tunnelURL) { if (tunnel.onerror) tunnel.onerror(message); // Finish - disconnect(); + tunnel.disconnect(); return; } @@ -255,7 +291,7 @@ Guacamole.HTTPTunnel = function(tunnelURL) { } - function connect(data) { + this.connect = function(data) { // Start tunnel and connect synchronously var connect_xmlhttprequest = new XMLHttpRequest(); @@ -281,16 +317,11 @@ Guacamole.HTTPTunnel = function(tunnelURL) { currentState = STATE_CONNECTED; handleResponse(makeRequest()); - } + }; - function disconnect() { + this.disconnect = function() { currentState = STATE_DISCONNECTED; - } - - // External API - tunnel.connect = connect; - tunnel.disconnect = disconnect; - tunnel.sendMessage = sendMessage; + }; }; From b56759139574aec9dfaf23110d03811a3eb2ea49 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 13 Jul 2011 00:17:34 -0700 Subject: [PATCH 15/19] Client now handles tunnel errors. --- guacamole-common-js/src/main/resources/guacamole.js | 5 +++++ guacamole-common-js/src/main/resources/tunnel.js | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index 3bff5d0fe..e32c62dbe 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -44,6 +44,11 @@ Guacamole.Client = function(display, tunnel) { tunnel.oninstruction = doInstruction; + tunnel.onerror = function(message) { + if (guac_client.onerror) + guac_client.onerror(message); + }; + // Display must be relatively positioned for mouse to be handled properly display.style.position = "relative"; diff --git a/guacamole-common-js/src/main/resources/tunnel.js b/guacamole-common-js/src/main/resources/tunnel.js index 0f33c3e26..278766e4a 100644 --- a/guacamole-common-js/src/main/resources/tunnel.js +++ b/guacamole-common-js/src/main/resources/tunnel.js @@ -169,8 +169,8 @@ Guacamole.HTTPTunnel = function(tunnelURL) { return; } - // Start next request as soon as possible - if (xmlhttprequest.readyState >= 2 && nextRequest == null) + // Start next request as soon as possible IF request was successful + if (xmlhttprequest.readyState >= 2 && nextRequest == null && xmlhttprequest.status == 200) nextRequest = makeRequest(); // Parse stream when data is received and when complete. @@ -185,8 +185,14 @@ Guacamole.HTTPTunnel = function(tunnelURL) { clearInterval(interval); } + // If canceled, stop transfer + if (xmlhttprequest.status == 0) { + tunnel.disconnect(); + return; + } + // Halt on error during request - if (xmlhttprequest.status == 0 || xmlhttprequest.status != 200) { + else if (xmlhttprequest.status != 200) { // Get error message (if any) var message = xmlhttprequest.getResponseHeader("X-Guacamole-Error-Message"); From b5a81afe877d769877af44cd69ea4424730578bf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 13 Jul 2011 11:09:14 -0700 Subject: [PATCH 16/19] Fixed message generation logic. --- guacamole-common-js/src/main/resources/tunnel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/resources/tunnel.js b/guacamole-common-js/src/main/resources/tunnel.js index 278766e4a..9d6671f4f 100644 --- a/guacamole-common-js/src/main/resources/tunnel.js +++ b/guacamole-common-js/src/main/resources/tunnel.js @@ -196,7 +196,7 @@ Guacamole.HTTPTunnel = function(tunnelURL) { // Get error message (if any) var message = xmlhttprequest.getResponseHeader("X-Guacamole-Error-Message"); - if (message) + if (!message) message = "Internal server error"; // Call error handler From d0155cdf526a4d16c0ee6b8076a7ce60714d17eb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 21 Jul 2011 15:16:19 -0700 Subject: [PATCH 17/19] Rect and clip instructions. --- .../src/main/resources/guacamole.js | 34 ++++++++++++ .../src/main/resources/layer.js | 53 ++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index e32c62dbe..db7ad9b6b 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -302,6 +302,40 @@ Guacamole.Client = function(display, tunnel) { }, + "rect": function(parameters) { + + var channelMask = parseInt(parameters[0]); + var layer = getLayer(parseInt(parameters[1])); + var x = parseInt(parameters[2]); + var y = parseInt(parameters[3]); + var w = parseInt(parameters[4]); + var h = parseInt(parameters[5]); + var r = parseInt(parameters[6]); + var g = parseInt(parameters[7]); + var b = parseInt(parameters[8]); + var a = parseInt(parameters[9]); + + layer.setChannelMask(channelMask); + + layer.drawRect( + x, y, w, h, + r, g, b, a + ); + + }, + + "clip": function(parameters) { + + var layer = getLayer(parseInt(parameters[0])); + var x = parseInt(parameters[1]); + var y = parseInt(parameters[2]); + var w = parseInt(parameters[3]); + var h = parseInt(parameters[4]); + + layer.clipRect(x, y, w, h); + + }, + "cursor": function(parameters) { var x = parseInt(parameters[0]); diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 7c8c82235..64ccd0329 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -54,6 +54,7 @@ Guacamole.Layer = function(width, height) { * @private */ var displayContext = display.getContext("2d"); + displayContext.save(); /** * The queue of all pending Tasks. Tasks will be run in order, with new @@ -204,8 +205,8 @@ Guacamole.Layer = function(width, height) { // Draw all pending tasks. var task; while ((task = tasks[0]) != null && task.handler) { - task.handler(); tasks.shift(); + task.handler(); } } @@ -386,6 +387,56 @@ Guacamole.Layer = function(width, height) { }); }; + /** + * Fill the specified rectangle of image data with the specified color. + * + * @param {Number} x The X coordinate of the upper-left corner of the + * rectangle to draw. + * @param {Number} y The Y coordinate of the upper-left corner of the + * rectangle to draw. + * @param {Number} w The width of the rectangle to draw. + * @param {Number} h The height of the rectangle to draw. + * @param {Number} r The red component of the color of the rectangle. + * @param {Number} g The green component of the color of the rectangle. + * @param {Number} b The blue component of the color of the rectangle. + * @param {Number} a The alpha component of the color of the rectangle. + */ + this.drawRect = function(x, y, w, h, r, g, b, a) { + scheduleTask(function() { + if (layer.autosize != 0) fitRect(x, y, w, h); + displayContext.fillStyle = "rgba(" + + r + "," + g + "," + b + "," + a + ")"; + displayContext.fillRect(x, y, w, h); + }); + }; + + /** + * Clip all future drawing operations by the specified rectangle. + * + * @param {Number} x The X coordinate of the upper-left corner of the + * rectangle to use for the clipping region. + * @param {Number} y The Y coordinate of the upper-left corner of the + * rectangle to use for the clipping region. + * @param {Number} w The width of the rectangle to use for the clipping region. + * @param {Number} h The height of the rectangle to use for the clipping region. + */ + this.clipRect = function(x, y, w, h) { + scheduleTask(function() { + + // Clear any current clipping region + displayContext.restore(); + displayContext.save(); + + if (layer.autosize != 0) fitRect(x, y, w, h); + + // Set new clipping region + displayContext.beginPath(); + displayContext.rect(x, y, w, h); + displayContext.clip(); + + }); + }; + /** * Provides the given filtering function with a writable snapshot of * image data and the current width and height of the Layer. From 6c41eb5aadc0c75c4faa572e79cd942d6d8d209c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 9 Aug 2011 16:08:18 -0700 Subject: [PATCH 18/19] Alpha for rgba() is from 0.0 to 1.0, not 0 to 255. --- guacamole-common-js/src/main/resources/layer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index 64ccd0329..eb0ba7e95 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -405,7 +405,7 @@ Guacamole.Layer = function(width, height) { scheduleTask(function() { if (layer.autosize != 0) fitRect(x, y, w, h); displayContext.fillStyle = "rgba(" - + r + "," + g + "," + b + "," + a + ")"; + + r + "," + g + "," + b + "," + a / 255 + ")"; displayContext.fillRect(x, y, w, h); }); }; From b7a5cc701379f5b683bc96e7ae75548bb760a60a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 18 Aug 2011 18:00:45 -0700 Subject: [PATCH 19/19] Fixed layer resize(), avoid multiple handlePengingTasks() calls --- .../src/main/resources/layer.js | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/resources/layer.js b/guacamole-common-js/src/main/resources/layer.js index eb0ba7e95..3ef8aeba4 100644 --- a/guacamole-common-js/src/main/resources/layer.js +++ b/guacamole-common-js/src/main/resources/layer.js @@ -97,9 +97,36 @@ Guacamole.Layer = function(width, height) { * @param {Number} newHeight The new height to assign to this Layer. */ function resize(newWidth, newHeight) { + + + // Only preserve old data if width/height are both non-zero + var oldData = null; + if (width != 0 && height != 0) { + + // Create canvas and context for holding old data + oldData = document.createElement("canvas"); + oldData.width = width; + oldData.height = height; + + var oldDataContext = oldData.getContext("2d"); + + // Copy image data from current + oldDataContext.drawImage(display, + 0, 0, width, height, + 0, 0, width, height); + + } + + // Resize canvas display.width = newWidth; display.height = newHeight; + // Redraw old data, if any + if (oldData) + displayContext.drawImage(oldData, + 0, 0, width, height, + 0, 0, width, height); + width = newWidth; height = newHeight; } @@ -195,6 +222,8 @@ Guacamole.Layer = function(width, height) { } + var tasksInProgress = false; + /** * Run any Tasks which were pending but are now ready to run and are not * blocked by other Tasks. @@ -202,6 +231,11 @@ Guacamole.Layer = function(width, height) { */ function handlePendingTasks() { + if (tasksInProgress) + return; + + tasksInProgress = true; + // Draw all pending tasks. var task; while ((task = tasks[0]) != null && task.handler) { @@ -209,6 +243,8 @@ Guacamole.Layer = function(width, height) { task.handler(); } + tasksInProgress = false; + } /** @@ -475,6 +511,7 @@ Guacamole.Layer = function(width, height) { }; // Initialize canvas dimensions - resize(width, height); + display.width = width; + display.height = height; };