Merge branch 'unstable' into touch-support

Conflicts:
	src/main/resources/mouse.js
This commit is contained in:
Michael Jumper
2011-08-26 14:00:14 -07:00
7 changed files with 901 additions and 408 deletions

View File

@@ -16,7 +16,22 @@
* You should have received a copy of the GNU Affero General Public License
*/
function GuacamoleClient(display, tunnel) {
// 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;
var STATE_IDLE = 0;
var STATE_CONNECTING = 1;
@@ -26,9 +41,13 @@ function GuacamoleClient(display, tunnel) {
var STATE_DISCONNECTED = 5;
var currentState = STATE_IDLE;
var stateChangeHandler = null;
tunnel.setInstructionHandler(doInstruction);
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";
@@ -36,15 +55,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;
@@ -54,7 +69,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;
@@ -83,7 +97,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;
@@ -91,7 +105,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())
@@ -100,24 +114,24 @@ function GuacamoleClient(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 + ";");
};
this.setClipboard = function(data) {
guac_client.setClipboard = function(data) {
// Do not send requests if not connected
if (!isConnected())
@@ -127,21 +141,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;
@@ -151,7 +154,7 @@ function GuacamoleClient(display, tunnel) {
var buffers = new Array();
var cursor = null;
this.getLayers = function() {
guac_client.getLayers = function() {
return layers;
};
@@ -165,8 +168,8 @@ function GuacamoleClient(display, tunnel) {
// Create buffer if necessary
if (buffer == null) {
buffer = new Layer(0, 0);
buffer.setAutosize(1);
buffer = new Guacamole.Layer(0, 0);
buffer.autosize = 1;
buffers[index] = buffer;
}
@@ -180,7 +183,14 @@ function GuacamoleClient(display, tunnel) {
if (layer == null) {
// Add new layer
layer = new Layer(displayWidth, displayHeight);
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
@@ -189,18 +199,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());
}
}
@@ -217,16 +227,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) {
@@ -292,6 +302,40 @@ function GuacamoleClient(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]);
@@ -299,8 +343,14 @@ 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);
var canvas = cursor.getCanvas();
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
display.appendChild(canvas);
}
// Start cursor image load
@@ -354,7 +404,7 @@ function GuacamoleClient(display, tunnel) {
if (layersToSync == 0)
tunnel.sendMessage("sync:" + timestamp + ";");
},
}
};
@@ -433,8 +483,8 @@ function GuacamoleClient(display, tunnel) {
}
this.disconnect = disconnect;
this.connect = function(data) {
guac_client.disconnect = disconnect;
guac_client.connect = function(data) {
setState(STATE_CONNECTING);
@@ -449,7 +499,7 @@ function GuacamoleClient(display, tunnel) {
setState(STATE_WAITING);
};
this.escapeGuacamoleString = escapeGuacamoleString;
this.unescapeGuacamoleString = unescapeGuacamoleString;
guac_client.escapeGuacamoleString = escapeGuacamoleString;
guac_client.unescapeGuacamoleString = unescapeGuacamoleString;
}

View File

@@ -17,20 +17,105 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function GuacamoleKeyboard(element) {
// Guacamole namespace
var Guacamole = Guacamole || {};
/*****************************************/
/*** Keyboard Handler ***/
/*****************************************/
/**
* 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;
/**
* 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.
* @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
};
/**
* 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;
var modCtrl = 0;
var modAlt = 0;
var modShift = false;
var modCtrl = false;
var modAlt = false;
var keydownChar = new Array();
// ID of routine repeating keystrokes. -1 = not repeating.
var repeatKeyTimeoutId = -1;
var repeatKeyIntervalId = -1;
@@ -52,27 +137,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;
}
@@ -91,7 +176,7 @@ function GuacamoleKeyboard(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];
@@ -104,14 +189,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);
}
@@ -125,7 +210,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;
@@ -133,11 +218,11 @@ function GuacamoleKeyboard(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) {
@@ -146,7 +231,7 @@ function GuacamoleKeyboard(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);
@@ -183,13 +268,15 @@ function GuacamoleKeyboard(element) {
return false;
}
return true;
};
// When key pressed
element.onkeypress = function(e) {
// Only intercept if handler set
if (!keyPressedHandler) return true;
if (!guac_keyboard.onkeydown) return true;
if (keySymSource != KEYPRESS) return false;
@@ -224,7 +311,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;
@@ -232,11 +319,11 @@ function GuacamoleKeyboard(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();
@@ -253,18 +340,10 @@ function GuacamoleKeyboard(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;
};
var keyPressedHandler = null;
var keyReleasedHandler = null;
this.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
this.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
}
};

View File

@@ -1,72 +0,0 @@
/*
* Guacamole - Clientless Remote Desktop
* Copyright (C) 2010 Michael Jumper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Keymap
var unshiftedKeySym = new Array();
unshiftedKeySym[8] = 0xFF08; // backspace
unshiftedKeySym[9] = 0xFF09; // tab
unshiftedKeySym[13] = 0xFF0D; // enter
unshiftedKeySym[16] = 0xFFE1; // shift
unshiftedKeySym[17] = 0xFFE3; // ctrl
unshiftedKeySym[18] = 0xFFE9; // alt
unshiftedKeySym[19] = 0xFF13; // pause/break
unshiftedKeySym[20] = 0xFFE5; // caps lock
unshiftedKeySym[27] = 0xFF1B; // escape
unshiftedKeySym[33] = 0xFF55; // page up
unshiftedKeySym[34] = 0xFF56; // page down
unshiftedKeySym[35] = 0xFF57; // end
unshiftedKeySym[36] = 0xFF50; // home
unshiftedKeySym[37] = 0xFF51; // left arrow
unshiftedKeySym[38] = 0xFF52; // up arrow
unshiftedKeySym[39] = 0xFF53; // right arrow
unshiftedKeySym[40] = 0xFF54; // down arrow
unshiftedKeySym[45] = 0xFF63; // insert
unshiftedKeySym[46] = 0xFFFF; // delete
unshiftedKeySym[91] = 0xFFEB; // left window key (super_l)
unshiftedKeySym[92] = 0xFF67; // right window key (menu key?)
unshiftedKeySym[93] = null; // select key
unshiftedKeySym[112] = 0xFFBE; // f1
unshiftedKeySym[113] = 0xFFBF; // f2
unshiftedKeySym[114] = 0xFFC0; // f3
unshiftedKeySym[115] = 0xFFC1; // f4
unshiftedKeySym[116] = 0xFFC2; // f5
unshiftedKeySym[117] = 0xFFC3; // f6
unshiftedKeySym[118] = 0xFFC4; // f7
unshiftedKeySym[119] = 0xFFC5; // f8
unshiftedKeySym[120] = 0xFFC6; // f9
unshiftedKeySym[121] = 0xFFC7; // f10
unshiftedKeySym[122] = 0xFFC8; // f11
unshiftedKeySym[123] = 0xFFC9; // f12
unshiftedKeySym[144] = 0xFF7F; // num lock
unshiftedKeySym[145] = 0xFF14; // scroll lock
// Shifted versions, IF DIFFERENT FROM UNSHIFTED!
// If any of these are null, the unshifted one will be used.
var shiftedKeySym = new Array();
shiftedKeySym[18] = 0xFFE7; // alt
// Constants for keysyms for special keys
var KEYSYM_CTRL = 65507;
var KEYSYM_ALT = 65513;
var KEYSYM_DELETE = 65535;
var KEYSYM_SHIFT = 65505;

View File

@@ -17,29 +17,136 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function Layer(width, height) {
// Guacamole namespace
var Guacamole = Guacamole || {};
// Off-screen buffer
/**
* 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) {
/**
* Reference to this Layer.
* @private
*/
var layer = this;
/**
* 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");
displayContext.save();
/**
* 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",
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"
};
/**
* 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";
display.style.top = "0px";
// 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;
}
display.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
* 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
@@ -66,176 +173,345 @@ function Layer(width, height) {
}
resize(width, height);
var readyHandler = null;
var updates = new Array();
var autosize = 0;
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;
}
display.setAutosize = function(flag) {
autosize = flag;
};
function reserveJob(handler) {
/**
* 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 updates, just call (if available) and exit
if (display.isReady() && handler != null) {
// 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() {
var tasksInProgress = false;
// Draw all pending updates.
var update;
while ((update = updates[0]) != null && update.hasHandler()) {
update.handle();
updates.shift();
/**
* Run any Tasks which were pending but are now ready to run and are not
* blocked by other Tasks.
* @private
*/
function handlePendingTasks() {
if (tasksInProgress)
return;
tasksInProgress = true;
// Draw all pending tasks.
var task;
while ((task = tasks[0]) != null && task.handler) {
tasks.shift();
task.handler();
}
// If done with updates, call ready handler
if (display.isReady() && readyHandler != null)
readyHandler();
tasksInProgress = false;
}
display.isReady = function() {
return updates.length == 0;
/**
* 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;
};
display.setReadyHandler = function(handler) {
readyHandler = handler;
/**
* 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 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);
});
};
display.drawImage = function(x, y, image) {
reserveJob(function() {
if (autosize != 0) fitRect(x, y, image.width, image.height);
/**
* 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) {
scheduleTask(function() {
if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
displayContext.drawImage(image, x, y);
});
};
display.draw = function(x, y, url) {
var update = reserveJob(null);
/**
* 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 task = scheduleTask(null);
var image = new Image();
image.onload = function() {
update.setHandler(function() {
if (autosize != 0) fitRect(x, y, image.width, image.height);
task.handler = function() {
if (layer.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;
};
// 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) {
reserveJob(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) {
scheduleTask(handler);
};
display.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 (layer.autosize != 0) fitRect(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.
// Syncing would result in deadlock.
if (display === srcLayer)
reserveJob(doCopyRect);
if (layer === srcLayer)
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();
});
}
};
display.clearRect = function(x, y, w, h) {
reserveJob(function() {
if (autosize != 0) fitRect(x, y, w, h);
/**
* 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 (layer.autosize != 0) fitRect(x, y, w, h);
displayContext.clearRect(x, y, w, h);
});
};
display.filter = function(filter) {
reserveJob(function() {
/**
* 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 / 255 + ")";
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.
*
* @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);
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",
};
display.setChannelMask = function(mask) {
reserveJob(function() {
/**
* 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];
});
};
return display;
}
// Initialize canvas dimensions
display.width = width;
display.height = height;
};

View File

@@ -17,60 +17,93 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Guacamole namespace
var Guacamole = Guacamole || {};
function GuacamoleMouse(element) {
/**
* 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) {
/*****************************************/
/*** Mouse Handler ***/
/*****************************************/
/**
* Reference to this Guacamole.Mouse.
* @private
*/
var guac_mouse = this;
/**
* 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 mouseIndex = 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 mouseLeftButton = 0;
var mouseMiddleButton = 0;
var mouseRightButton = 0;
var mouseX = 0;
var mouseY = 0;
var absoluteMouseX = 0;
var absoluteMouseY = 0;
function getMouseState(up, down) {
var mouseState = new MouseEvent(mouseX, mouseY,
mouseLeftButton, mouseMiddleButton, mouseRightButton, up, down);
return mouseState;
}
/**
* 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;
function moveMouse(pageX, pageY) {
absoluteMouseX = pageX;
absoluteMouseY = pageY;
mouseX = absoluteMouseX - element.offsetLeft;
mouseY = absoluteMouseY - element.offsetTop;
guac_mouse.currentState.x = pageX - element.offsetLeft;
guac_mouse.currentState.y = pageY - 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;
}
movementHandler(getMouseState(0, 0));
if (guac_mouse.onmousemove)
guac_mouse.onmousemove(guac_mouse.currentState);
}
// 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) {
@@ -136,17 +169,19 @@ function GuacamoleMouse(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;
}
buttonPressedHandler(getMouseState(0, 0));
if (guac_mouse.onmousedown)
guac_mouse.onmousedown(guac_mouse.currentState);
};
@@ -156,17 +191,19 @@ function GuacamoleMouse(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;
}
buttonReleasedHandler(getMouseState(0, 0));
if (guac_mouse.onmouseup)
guac_mouse.onmouseup(guac_mouse.currentState);
};
element.onmouseout = function(e) {
@@ -174,12 +211,16 @@ function GuacamoleMouse(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) {
buttonReleasedHandler(getMouseState(0, 0));
guac_mouse.currentState.left = false;
guac_mouse.currentState.middle = false;
guac_mouse.currentState.right = false;
if (guac_mouse.onmouseup)
guac_mouse.onmouseup(guac_mouse.currentState);
}
};
@@ -200,14 +241,28 @@ function GuacamoleMouse(element) {
// Up
if (delta < 0) {
buttonPressedHandler(getMouseState(1, 0));
buttonReleasedHandler(getMouseState(0, 0));
if (guac_mouse.onmousedown) {
guac_mouse.currentState.up = true;
guac_mouse.onmousedown(guac_mouse.currentState);
}
if (guac_mouse.onmouseup) {
guac_mouse.currentState.up = false;
guac_mouse.onmouseup(guac_mouse.currentState);
}
}
// Down
if (delta > 0) {
buttonPressedHandler(getMouseState(0, 1));
buttonReleasedHandler(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)
@@ -220,53 +275,71 @@ function GuacamoleMouse(element) {
element.onmousewheel = function(e) {
handleScroll(e);
}
var buttonPressedHandler = null;
var buttonReleasedHandler = null;
var movementHandler = null;
this.setButtonPressedHandler = function(mh) {buttonPressedHandler = mh;};
this.setButtonReleasedHandler = function(mh) {buttonReleasedHandler = mh;};
this.setMovementHandler = function(mh) {movementHandler = mh;};
this.getX = function() {return mouseX;};
this.getY = function() {return mouseY;};
this.getLeftButton = function() {return mouseLeftButton;};
this.getMiddleButton = function() {return mouseMiddleButton;};
this.getRightButton = function() {return mouseRightButton;};
}
function MouseEvent(x, y, left, middle, right, up, down) {
this.getX = function() {
return x;
};
this.getY = function() {
return y;
};
};
this.getLeft = function() {
return left;
};
/**
* 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) {
this.getMiddle = function() {
return middle;
};
/**
* The current X position of the mouse pointer.
* @type Number
*/
this.x = x;
this.getRight = function() {
return right;
};
/**
* The current Y position of the mouse pointer.
* @type Number
*/
this.y = y;
this.getUp = function() {
return up;
};
/**
* Whether the left mouse button is currently pressed.
* @type Boolean
*/
this.left = left;
this.getDown = function() {
return down;
};
/**
* 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;
};
}

View File

@@ -17,10 +17,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Guacamole namespace
var Guacamole = Guacamole || {};
function GuacamoleOnScreenKeyboard(url) {
/**
* 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 tabIndex = 1;
var allKeys = new Array();
var modifierState = new function() {};

View File

@@ -16,7 +16,77 @@
* You should have received a copy of the GNU Affero General Public License
*/
function GuacamoleHTTPTunnel(tunnelURL) {
// 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() {
/**
* 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;
};
/**
* 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) {
/**
* Reference to this HTTP tunnel.
*/
var tunnel = this;
var tunnel_uuid;
@@ -36,12 +106,10 @@ 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 sendingMessages = false;
var outputMessageBuffer = "";
function sendMessage(message) {
this.sendMessage = function(message) {
// Do not attempt to send messages if not connected
if (currentState != STATE_CONNECTED)
@@ -49,16 +117,16 @@ function GuacamoleHTTPTunnel(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);
@@ -75,7 +143,7 @@ function GuacamoleHTTPTunnel(tunnelURL) {
}
else
sendingMessages = 0;
sendingMessages = false;
}
@@ -101,8 +169,8 @@ function GuacamoleHTTPTunnel(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.
@@ -117,9 +185,25 @@ function GuacamoleHTTPTunnel(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) {
disconnect();
else if (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
tunnel.disconnect();
return;
}
@@ -160,8 +244,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.
@@ -213,7 +297,7 @@ function GuacamoleHTTPTunnel(tunnelURL) {
}
function connect(data) {
this.connect = function(data) {
// Start tunnel and connect synchronously
var connect_xmlhttprequest = new XMLHttpRequest();
@@ -239,18 +323,12 @@ function GuacamoleHTTPTunnel(tunnelURL) {
currentState = STATE_CONNECTED;
handleResponse(makeRequest());
}
function disconnect() {
currentState = STATE_DISCONNECTED;
}
// External API
this.connect = connect;
this.disconnect = disconnect;
this.sendMessage = sendMessage;
this.setInstructionHandler = function(handler) {
instructionHandler = handler;
};
}
this.disconnect = function() {
currentState = STATE_DISCONNECTED;
};
};
Guacamole.HTTPTunnel.prototype = new Guacamole.Tunnel();