mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 13:41:21 +00:00
Merge branch 'unstable' into touch-support
Conflicts: src/main/resources/mouse.js
This commit is contained in:
@@ -16,7 +16,22 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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_IDLE = 0;
|
||||||
var STATE_CONNECTING = 1;
|
var STATE_CONNECTING = 1;
|
||||||
@@ -26,9 +41,13 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
var STATE_DISCONNECTED = 5;
|
var STATE_DISCONNECTED = 5;
|
||||||
|
|
||||||
var currentState = STATE_IDLE;
|
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 must be relatively positioned for mouse to be handled properly
|
||||||
display.style.position = "relative";
|
display.style.position = "relative";
|
||||||
@@ -36,15 +55,11 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
function setState(state) {
|
function setState(state) {
|
||||||
if (state != currentState) {
|
if (state != currentState) {
|
||||||
currentState = state;
|
currentState = state;
|
||||||
if (stateChangeHandler)
|
if (guac_client.onstatechange)
|
||||||
stateChangeHandler(currentState);
|
guac_client.onstatechange(currentState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setOnStateChangeHandler = function(handler) {
|
|
||||||
stateChangeHandler = handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
function isConnected() {
|
function isConnected() {
|
||||||
return currentState == STATE_CONNECTED
|
return currentState == STATE_CONNECTED
|
||||||
|| currentState == STATE_WAITING;
|
|| currentState == STATE_WAITING;
|
||||||
@@ -54,7 +69,6 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
var cursorHotspotX = 0;
|
var cursorHotspotX = 0;
|
||||||
var cursorHotspotY = 0;
|
var cursorHotspotY = 0;
|
||||||
|
|
||||||
// FIXME: Make object. Clean up.
|
|
||||||
var cursorRectX = 0;
|
var cursorRectX = 0;
|
||||||
var cursorRectY = 0;
|
var cursorRectY = 0;
|
||||||
var cursorRectW = 0;
|
var cursorRectW = 0;
|
||||||
@@ -83,7 +97,7 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
|
cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendKeyEvent = function(pressed, keysym) {
|
guac_client.sendKeyEvent = function(pressed, keysym) {
|
||||||
// Do not send requests if not connected
|
// Do not send requests if not connected
|
||||||
if (!isConnected())
|
if (!isConnected())
|
||||||
return;
|
return;
|
||||||
@@ -91,7 +105,7 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
tunnel.sendMessage("key:" + keysym + "," + pressed + ";");
|
tunnel.sendMessage("key:" + keysym + "," + pressed + ";");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sendMouseState = function(mouseState) {
|
guac_client.sendMouseState = function(mouseState) {
|
||||||
|
|
||||||
// Do not send requests if not connected
|
// Do not send requests if not connected
|
||||||
if (!isConnected())
|
if (!isConnected())
|
||||||
@@ -100,24 +114,24 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
// Draw client-side cursor
|
// Draw client-side cursor
|
||||||
if (cursorImage != null) {
|
if (cursorImage != null) {
|
||||||
redrawCursor(
|
redrawCursor(
|
||||||
mouseState.getX(),
|
mouseState.x,
|
||||||
mouseState.getY()
|
mouseState.y
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build mask
|
// Build mask
|
||||||
var buttonMask = 0;
|
var buttonMask = 0;
|
||||||
if (mouseState.getLeft()) buttonMask |= 1;
|
if (mouseState.left) buttonMask |= 1;
|
||||||
if (mouseState.getMiddle()) buttonMask |= 2;
|
if (mouseState.middle) buttonMask |= 2;
|
||||||
if (mouseState.getRight()) buttonMask |= 4;
|
if (mouseState.right) buttonMask |= 4;
|
||||||
if (mouseState.getUp()) buttonMask |= 8;
|
if (mouseState.up) buttonMask |= 8;
|
||||||
if (mouseState.getDown()) buttonMask |= 16;
|
if (mouseState.down) buttonMask |= 16;
|
||||||
|
|
||||||
// Send message
|
// 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
|
// Do not send requests if not connected
|
||||||
if (!isConnected())
|
if (!isConnected())
|
||||||
@@ -127,21 +141,10 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
|
guac_client.onstatechange = null;
|
||||||
var nameHandler = null;
|
guac_client.onname = null;
|
||||||
this.setNameHandler = function(handler) {
|
guac_client.onerror = null;
|
||||||
nameHandler = handler;
|
guac_client.onclipboard = null;
|
||||||
}
|
|
||||||
|
|
||||||
var errorHandler = null;
|
|
||||||
this.setErrorHandler = function(handler) {
|
|
||||||
errorHandler = handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
var clipboardHandler = null;
|
|
||||||
this.setClipboardHandler = function(handler) {
|
|
||||||
clipboardHandler = handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
var displayWidth = 0;
|
var displayWidth = 0;
|
||||||
@@ -151,7 +154,7 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
var buffers = new Array();
|
var buffers = new Array();
|
||||||
var cursor = null;
|
var cursor = null;
|
||||||
|
|
||||||
this.getLayers = function() {
|
guac_client.getLayers = function() {
|
||||||
return layers;
|
return layers;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -165,8 +168,8 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
|
|
||||||
// Create buffer if necessary
|
// Create buffer if necessary
|
||||||
if (buffer == null) {
|
if (buffer == null) {
|
||||||
buffer = new Layer(0, 0);
|
buffer = new Guacamole.Layer(0, 0);
|
||||||
buffer.setAutosize(1);
|
buffer.autosize = 1;
|
||||||
buffers[index] = buffer;
|
buffers[index] = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +183,14 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
if (layer == null) {
|
if (layer == null) {
|
||||||
|
|
||||||
// Add new layer
|
// 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;
|
layers[index] = layer;
|
||||||
|
|
||||||
// (Re)-add existing layers in order
|
// (Re)-add existing layers in order
|
||||||
@@ -189,18 +199,18 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
|
|
||||||
// If already present, remove
|
// If already present, remove
|
||||||
if (layers[i].parentNode === display)
|
if (layers[i].parentNode === display)
|
||||||
display.removeChild(layers[i]);
|
display.removeChild(layers[i].getCanvas());
|
||||||
|
|
||||||
// Add to end
|
// Add to end
|
||||||
display.appendChild(layers[i]);
|
display.appendChild(layers[i].getCanvas());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cursor layer last
|
// Add cursor layer last
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
if (cursor.parentNode === display)
|
if (cursor.parentNode === display)
|
||||||
display.removeChild(cursor);
|
display.removeChild(cursor.getCanvas());
|
||||||
display.appendChild(cursor);
|
display.appendChild(cursor.getCanvas());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -217,16 +227,16 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
var instructionHandlers = {
|
var instructionHandlers = {
|
||||||
|
|
||||||
"error": function(parameters) {
|
"error": function(parameters) {
|
||||||
if (errorHandler) errorHandler(unescapeGuacamoleString(parameters[0]));
|
if (guac_client.onerror) guac_client.onerror(unescapeGuacamoleString(parameters[0]));
|
||||||
disconnect();
|
disconnect();
|
||||||
},
|
},
|
||||||
|
|
||||||
"name": function(parameters) {
|
"name": function(parameters) {
|
||||||
if (nameHandler) nameHandler(unescapeGuacamoleString(parameters[0]));
|
if (guac_client.onname) guac_client.onname(unescapeGuacamoleString(parameters[0]));
|
||||||
},
|
},
|
||||||
|
|
||||||
"clipboard": function(parameters) {
|
"clipboard": function(parameters) {
|
||||||
if (clipboardHandler) clipboardHandler(unescapeGuacamoleString(parameters[0]));
|
if (guac_client.onclipboard) guac_client.onclipboard(unescapeGuacamoleString(parameters[0]));
|
||||||
},
|
},
|
||||||
|
|
||||||
"size": function(parameters) {
|
"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) {
|
"cursor": function(parameters) {
|
||||||
|
|
||||||
var x = parseInt(parameters[0]);
|
var x = parseInt(parameters[0]);
|
||||||
@@ -299,8 +343,14 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
var data = parameters[2];
|
var data = parameters[2];
|
||||||
|
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
cursor = new Layer(displayWidth, displayHeight);
|
cursor = new Guacamole.Layer(displayWidth, displayHeight);
|
||||||
display.appendChild(cursor);
|
|
||||||
|
var canvas = cursor.getCanvas();
|
||||||
|
canvas.style.position = "absolute";
|
||||||
|
canvas.style.left = "0px";
|
||||||
|
canvas.style.top = "0px";
|
||||||
|
|
||||||
|
display.appendChild(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start cursor image load
|
// Start cursor image load
|
||||||
@@ -354,7 +404,7 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
if (layersToSync == 0)
|
if (layersToSync == 0)
|
||||||
tunnel.sendMessage("sync:" + timestamp + ";");
|
tunnel.sendMessage("sync:" + timestamp + ";");
|
||||||
|
|
||||||
},
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -433,8 +483,8 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disconnect = disconnect;
|
guac_client.disconnect = disconnect;
|
||||||
this.connect = function(data) {
|
guac_client.connect = function(data) {
|
||||||
|
|
||||||
setState(STATE_CONNECTING);
|
setState(STATE_CONNECTING);
|
||||||
|
|
||||||
@@ -449,7 +499,7 @@ function GuacamoleClient(display, tunnel) {
|
|||||||
setState(STATE_WAITING);
|
setState(STATE_WAITING);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.escapeGuacamoleString = escapeGuacamoleString;
|
guac_client.escapeGuacamoleString = escapeGuacamoleString;
|
||||||
this.unescapeGuacamoleString = unescapeGuacamoleString;
|
guac_client.unescapeGuacamoleString = unescapeGuacamoleString;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,20 +17,105 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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
|
// Single key state/modifier buffer
|
||||||
var modShift = 0;
|
var modShift = false;
|
||||||
var modCtrl = 0;
|
var modCtrl = false;
|
||||||
var modAlt = 0;
|
var modAlt = false;
|
||||||
|
|
||||||
var keydownChar = new Array();
|
var keydownChar = new Array();
|
||||||
|
|
||||||
|
|
||||||
// ID of routine repeating keystrokes. -1 = not repeating.
|
// ID of routine repeating keystrokes. -1 = not repeating.
|
||||||
var repeatKeyTimeoutId = -1;
|
var repeatKeyTimeoutId = -1;
|
||||||
var repeatKeyIntervalId = -1;
|
var repeatKeyIntervalId = -1;
|
||||||
@@ -91,7 +176,7 @@ function GuacamoleKeyboard(element) {
|
|||||||
function getKeySymFromKeyCode(keyCode) {
|
function getKeySymFromKeyCode(keyCode) {
|
||||||
|
|
||||||
var keysym = null;
|
var keysym = null;
|
||||||
if (modShift == 0) keysym = unshiftedKeySym[keyCode];
|
if (!modShift) keysym = unshiftedKeySym[keyCode];
|
||||||
else {
|
else {
|
||||||
keysym = shiftedKeySym[keyCode];
|
keysym = shiftedKeySym[keyCode];
|
||||||
if (keysym == null) keysym = unshiftedKeySym[keyCode];
|
if (keysym == null) keysym = unshiftedKeySym[keyCode];
|
||||||
@@ -104,14 +189,14 @@ function GuacamoleKeyboard(element) {
|
|||||||
|
|
||||||
// Sends a single keystroke over the network
|
// Sends a single keystroke over the network
|
||||||
function sendKeyPressed(keysym) {
|
function sendKeyPressed(keysym) {
|
||||||
if (keysym != null && keyPressedHandler)
|
if (keysym != null && guac_keyboard.onkeydown)
|
||||||
keyPressedHandler(keysym);
|
guac_keyboard.onkeydown(keysym);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends a single keystroke over the network
|
// Sends a single keystroke over the network
|
||||||
function sendKeyReleased(keysym) {
|
function sendKeyReleased(keysym) {
|
||||||
if (keysym != null)
|
if (keysym != null && guac_keyboard.onkeyup)
|
||||||
keyReleasedHandler(keysym);
|
guac_keyboard.onkeyup(keysym);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -125,7 +210,7 @@ function GuacamoleKeyboard(element) {
|
|||||||
element.onkeydown = function(e) {
|
element.onkeydown = function(e) {
|
||||||
|
|
||||||
// Only intercept if handler set
|
// Only intercept if handler set
|
||||||
if (!keyPressedHandler) return true;
|
if (!guac_keyboard.onkeydown) return true;
|
||||||
|
|
||||||
var keynum;
|
var keynum;
|
||||||
if (window.event) keynum = window.event.keyCode;
|
if (window.event) keynum = window.event.keyCode;
|
||||||
@@ -133,11 +218,11 @@ function GuacamoleKeyboard(element) {
|
|||||||
|
|
||||||
// Ctrl/Alt/Shift
|
// Ctrl/Alt/Shift
|
||||||
if (keynum == 16)
|
if (keynum == 16)
|
||||||
modShift = 1;
|
modShift = true;
|
||||||
else if (keynum == 17)
|
else if (keynum == 17)
|
||||||
modCtrl = 1;
|
modCtrl = true;
|
||||||
else if (keynum == 18)
|
else if (keynum == 18)
|
||||||
modAlt = 1;
|
modAlt = true;
|
||||||
|
|
||||||
var keysym = getKeySymFromKeyCode(keynum);
|
var keysym = getKeySymFromKeyCode(keynum);
|
||||||
if (keysym) {
|
if (keysym) {
|
||||||
@@ -146,7 +231,7 @@ function GuacamoleKeyboard(element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If modifier keys are held down, and we have keyIdentifier
|
// 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
|
// Get keysym from keyIdentifier
|
||||||
keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
|
keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
|
||||||
@@ -183,13 +268,15 @@ function GuacamoleKeyboard(element) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// When key pressed
|
// When key pressed
|
||||||
element.onkeypress = function(e) {
|
element.onkeypress = function(e) {
|
||||||
|
|
||||||
// Only intercept if handler set
|
// Only intercept if handler set
|
||||||
if (!keyPressedHandler) return true;
|
if (!guac_keyboard.onkeydown) return true;
|
||||||
|
|
||||||
if (keySymSource != KEYPRESS) return false;
|
if (keySymSource != KEYPRESS) return false;
|
||||||
|
|
||||||
@@ -224,7 +311,7 @@ function GuacamoleKeyboard(element) {
|
|||||||
element.onkeyup = function(e) {
|
element.onkeyup = function(e) {
|
||||||
|
|
||||||
// Only intercept if handler set
|
// Only intercept if handler set
|
||||||
if (!keyReleasedHandler) return true;
|
if (!guac_keyboard.onkeyup) return true;
|
||||||
|
|
||||||
var keynum;
|
var keynum;
|
||||||
if (window.event) keynum = window.event.keyCode;
|
if (window.event) keynum = window.event.keyCode;
|
||||||
@@ -232,11 +319,11 @@ function GuacamoleKeyboard(element) {
|
|||||||
|
|
||||||
// Ctrl/Alt/Shift
|
// Ctrl/Alt/Shift
|
||||||
if (keynum == 16)
|
if (keynum == 16)
|
||||||
modShift = 0;
|
modShift = false;
|
||||||
else if (keynum == 17)
|
else if (keynum == 17)
|
||||||
modCtrl = 0;
|
modCtrl = false;
|
||||||
else if (keynum == 18)
|
else if (keynum == 18)
|
||||||
modAlt = 0;
|
modAlt = false;
|
||||||
else
|
else
|
||||||
stopRepeat();
|
stopRepeat();
|
||||||
|
|
||||||
@@ -253,18 +340,10 @@ function GuacamoleKeyboard(element) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// When focus is lost, clear modifiers.
|
// When focus is lost, clear modifiers.
|
||||||
var docOnblur = element.onblur;
|
|
||||||
element.onblur = function() {
|
element.onblur = function() {
|
||||||
modAlt = 0;
|
modAlt = false;
|
||||||
modCtrl = 0;
|
modCtrl = false;
|
||||||
modShift = 0;
|
modShift = false;
|
||||||
if (docOnblur != null) docOnblur();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var keyPressedHandler = null;
|
};
|
||||||
var keyReleasedHandler = null;
|
|
||||||
|
|
||||||
this.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
|
|
||||||
this.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
* Guacamole - Clientless Remote Desktop
|
|
||||||
* Copyright (C) 2010 Michael Jumper
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
// Keymap
|
|
||||||
|
|
||||||
var unshiftedKeySym = new Array();
|
|
||||||
unshiftedKeySym[8] = 0xFF08; // backspace
|
|
||||||
unshiftedKeySym[9] = 0xFF09; // tab
|
|
||||||
unshiftedKeySym[13] = 0xFF0D; // enter
|
|
||||||
unshiftedKeySym[16] = 0xFFE1; // shift
|
|
||||||
unshiftedKeySym[17] = 0xFFE3; // ctrl
|
|
||||||
unshiftedKeySym[18] = 0xFFE9; // alt
|
|
||||||
unshiftedKeySym[19] = 0xFF13; // pause/break
|
|
||||||
unshiftedKeySym[20] = 0xFFE5; // caps lock
|
|
||||||
unshiftedKeySym[27] = 0xFF1B; // escape
|
|
||||||
unshiftedKeySym[33] = 0xFF55; // page up
|
|
||||||
unshiftedKeySym[34] = 0xFF56; // page down
|
|
||||||
unshiftedKeySym[35] = 0xFF57; // end
|
|
||||||
unshiftedKeySym[36] = 0xFF50; // home
|
|
||||||
unshiftedKeySym[37] = 0xFF51; // left arrow
|
|
||||||
unshiftedKeySym[38] = 0xFF52; // up arrow
|
|
||||||
unshiftedKeySym[39] = 0xFF53; // right arrow
|
|
||||||
unshiftedKeySym[40] = 0xFF54; // down arrow
|
|
||||||
unshiftedKeySym[45] = 0xFF63; // insert
|
|
||||||
unshiftedKeySym[46] = 0xFFFF; // delete
|
|
||||||
unshiftedKeySym[91] = 0xFFEB; // left window key (super_l)
|
|
||||||
unshiftedKeySym[92] = 0xFF67; // right window key (menu key?)
|
|
||||||
unshiftedKeySym[93] = null; // select key
|
|
||||||
unshiftedKeySym[112] = 0xFFBE; // f1
|
|
||||||
unshiftedKeySym[113] = 0xFFBF; // f2
|
|
||||||
unshiftedKeySym[114] = 0xFFC0; // f3
|
|
||||||
unshiftedKeySym[115] = 0xFFC1; // f4
|
|
||||||
unshiftedKeySym[116] = 0xFFC2; // f5
|
|
||||||
unshiftedKeySym[117] = 0xFFC3; // f6
|
|
||||||
unshiftedKeySym[118] = 0xFFC4; // f7
|
|
||||||
unshiftedKeySym[119] = 0xFFC5; // f8
|
|
||||||
unshiftedKeySym[120] = 0xFFC6; // f9
|
|
||||||
unshiftedKeySym[121] = 0xFFC7; // f10
|
|
||||||
unshiftedKeySym[122] = 0xFFC8; // f11
|
|
||||||
unshiftedKeySym[123] = 0xFFC9; // f12
|
|
||||||
unshiftedKeySym[144] = 0xFF7F; // num lock
|
|
||||||
unshiftedKeySym[145] = 0xFF14; // scroll lock
|
|
||||||
|
|
||||||
// Shifted versions, IF DIFFERENT FROM UNSHIFTED!
|
|
||||||
// If any of these are null, the unshifted one will be used.
|
|
||||||
var shiftedKeySym = new Array();
|
|
||||||
shiftedKeySym[18] = 0xFFE7; // alt
|
|
||||||
|
|
||||||
// Constants for keysyms for special keys
|
|
||||||
var KEYSYM_CTRL = 65507;
|
|
||||||
var KEYSYM_ALT = 65513;
|
|
||||||
var KEYSYM_DELETE = 65535;
|
|
||||||
var KEYSYM_SHIFT = 65505;
|
|
||||||
|
|
||||||
|
|
@@ -17,29 +17,136 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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");
|
var display = document.createElement("canvas");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 2D display context of the canvas element backing this Layer.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
var displayContext = display.getContext("2d");
|
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) {
|
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.width = newWidth;
|
||||||
display.height = newHeight;
|
display.height = newHeight;
|
||||||
|
|
||||||
|
// Redraw old data, if any
|
||||||
|
if (oldData)
|
||||||
|
displayContext.drawImage(oldData,
|
||||||
|
0, 0, width, height,
|
||||||
|
0, 0, width, height);
|
||||||
|
|
||||||
width = newWidth;
|
width = newWidth;
|
||||||
height = newHeight;
|
height = newHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.resize = function(newWidth, newHeight) {
|
/**
|
||||||
if (newWidth != width || newHeight != height)
|
* Given the X and Y coordinates of the upper-left corner of a rectangle
|
||||||
resize(newWidth, newHeight);
|
* 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) {
|
function fitRect(x, y, w, h) {
|
||||||
|
|
||||||
// Calculate bounds
|
// Calculate bounds
|
||||||
@@ -66,176 +173,345 @@ function Layer(width, height) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width, height);
|
/**
|
||||||
|
* 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) {
|
||||||
|
|
||||||
var readyHandler = null;
|
/**
|
||||||
var updates = new Array();
|
* The handler this Task is associated with, if any.
|
||||||
var autosize = 0;
|
*
|
||||||
|
* @type function
|
||||||
function Update(updateHandler) {
|
*/
|
||||||
|
this.handler = taskHandler;
|
||||||
this.setHandler = function(handler) {
|
|
||||||
updateHandler = handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.hasHandler = function() {
|
|
||||||
return updateHandler != null;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handle = function() {
|
|
||||||
updateHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display.setAutosize = function(flag) {
|
/**
|
||||||
autosize = flag;
|
* 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) {
|
||||||
|
|
||||||
function reserveJob(handler) {
|
// If no pending tasks, just call (if available) and exit
|
||||||
|
if (layer.isReady() && handler != null) {
|
||||||
// If no pending updates, just call (if available) and exit
|
|
||||||
if (display.isReady() && handler != null) {
|
|
||||||
handler();
|
handler();
|
||||||
return null;
|
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.
|
// and return a reference to it.
|
||||||
var update = new Update(handler);
|
var task = new Task(handler);
|
||||||
updates.push(update);
|
tasks.push(task);
|
||||||
return update;
|
return task;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePendingUpdates() {
|
var tasksInProgress = false;
|
||||||
|
|
||||||
// Draw all pending updates.
|
/**
|
||||||
var update;
|
* Run any Tasks which were pending but are now ready to run and are not
|
||||||
while ((update = updates[0]) != null && update.hasHandler()) {
|
* blocked by other Tasks.
|
||||||
update.handle();
|
* @private
|
||||||
updates.shift();
|
*/
|
||||||
|
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
|
tasksInProgress = false;
|
||||||
if (display.isReady() && readyHandler != null)
|
|
||||||
readyHandler();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
* Draws the specified image at the given coordinates. The image specified
|
||||||
if (autosize != 0) fitRect(x, y, image.width, image.height);
|
* 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);
|
displayContext.drawImage(image, x, y);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
display.draw = function(x, y, url) {
|
* Draws the image at the specified URL at the given coordinates. The image
|
||||||
var update = reserveJob(null);
|
* 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();
|
var image = new Image();
|
||||||
image.onload = function() {
|
image.onload = function() {
|
||||||
|
|
||||||
update.setHandler(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);
|
displayContext.drawImage(image, x, y);
|
||||||
});
|
};
|
||||||
|
|
||||||
// As this update originally had no handler and may have blocked
|
// As this task originally had no handler and may have blocked
|
||||||
// other updates, handle any blocked updates.
|
// other tasks, handle any blocked tasks.
|
||||||
handlePendingUpdates();
|
handlePendingTasks();
|
||||||
|
|
||||||
};
|
};
|
||||||
image.src = url;
|
image.src = url;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run arbitrary function as soon as currently pending operations complete.
|
/**
|
||||||
// Future operations will not block this function from being called (unlike
|
* Run an arbitrary function as soon as currently pending operations
|
||||||
// the ready handler, which requires no pending updates)
|
* are complete.
|
||||||
display.sync = function(handler) {
|
*
|
||||||
reserveJob(handler);
|
* @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() {
|
function doCopyRect() {
|
||||||
if (autosize != 0) fitRect(x, y, w, h);
|
if (layer.autosize != 0) fitRect(x, y, srcw, srch);
|
||||||
displayContext.drawImage(srcLayer, srcx, srcy, w, h, x, y, w, h);
|
displayContext.drawImage(srcLayer.getCanvas(), srcx, srcy, srcw, srch, x, y, srcw, srch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we ARE the source layer, no need to sync.
|
// If we ARE the source layer, no need to sync.
|
||||||
// Syncing would result in deadlock.
|
// Syncing would result in deadlock.
|
||||||
if (display === srcLayer)
|
if (layer === srcLayer)
|
||||||
reserveJob(doCopyRect);
|
scheduleTask(doCopyRect);
|
||||||
|
|
||||||
// Otherwise synchronize copy operation with source layer
|
// Otherwise synchronize copy operation with source layer
|
||||||
else {
|
else {
|
||||||
var update = reserveJob(null);
|
var task = scheduleTask(null);
|
||||||
srcLayer.sync(function() {
|
srcLayer.sync(function() {
|
||||||
|
|
||||||
update.setHandler(doCopyRect);
|
task.handler = doCopyRect;
|
||||||
|
|
||||||
// As this update originally had no handler and may have blocked
|
// As this task originally had no handler and may have blocked
|
||||||
// other updates, handle any blocked updates.
|
// other tasks, handle any blocked tasks.
|
||||||
handlePendingUpdates();
|
handlePendingTasks();
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
display.clearRect = function(x, y, w, h) {
|
/**
|
||||||
reserveJob(function() {
|
* Clear the specified rectangle of image data.
|
||||||
if (autosize != 0) fitRect(x, y, w, h);
|
*
|
||||||
|
* @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);
|
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);
|
var imageData = displayContext.getImageData(0, 0, width, height);
|
||||||
filter(imageData.data, width, height);
|
filter(imageData.data, width, height);
|
||||||
displayContext.putImageData(imageData, 0, 0);
|
displayContext.putImageData(imageData, 0, 0);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var compositeOperation = {
|
/**
|
||||||
/* 0x0 NOT IMPLEMENTED */
|
* Sets the channel mask for future operations on this Layer. The channel
|
||||||
0x1: "destination-in",
|
* mask is a Guacamole-specific compositing operation identifier with a
|
||||||
0x2: "destination-out",
|
* single bit representing each of four channels (in order): source image
|
||||||
/* 0x3 NOT IMPLEMENTED */
|
* where destination transparent, source where destination opaque,
|
||||||
0x4: "source-in",
|
* destination where source transparent, and destination where source
|
||||||
/* 0x5 NOT IMPLEMENTED */
|
* opaque.
|
||||||
0x6: "source-atop",
|
*
|
||||||
/* 0x7 NOT IMPLEMENTED */
|
* @param {Number} mask The channel mask for future operations on this
|
||||||
0x8: "source-out",
|
* Layer.
|
||||||
0x9: "destination-atop",
|
*/
|
||||||
0xA: "xor",
|
this.setChannelMask = function(mask) {
|
||||||
0xB: "destination-over",
|
scheduleTask(function() {
|
||||||
0xC: "copy",
|
|
||||||
/* 0xD NOT IMPLEMENTED */
|
|
||||||
0xE: "source-over",
|
|
||||||
0xF: "lighter",
|
|
||||||
};
|
|
||||||
|
|
||||||
display.setChannelMask = function(mask) {
|
|
||||||
reserveJob(function() {
|
|
||||||
displayContext.globalCompositeOperation = compositeOperation[mask];
|
displayContext.globalCompositeOperation = compositeOperation[mask];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return display;
|
// Initialize canvas dimensions
|
||||||
|
display.width = width;
|
||||||
}
|
display.height = height;
|
||||||
|
|
||||||
|
};
|
||||||
|
@@ -17,60 +17,93 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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;
|
* Fired whenever the user releases a mouse button down over the element
|
||||||
var mouseRightButton = 0;
|
* associated with this Guacamole.Mouse.
|
||||||
|
*
|
||||||
var mouseX = 0;
|
* @event
|
||||||
var mouseY = 0;
|
* @param {Guacamole.Mouse.State} state The current mouse state.
|
||||||
|
*/
|
||||||
var absoluteMouseX = 0;
|
this.onmouseup = null;
|
||||||
var absoluteMouseY = 0;
|
|
||||||
|
|
||||||
|
|
||||||
function getMouseState(up, down) {
|
|
||||||
var mouseState = new MouseEvent(mouseX, mouseY,
|
|
||||||
mouseLeftButton, mouseMiddleButton, mouseRightButton, up, down);
|
|
||||||
|
|
||||||
return mouseState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
function moveMouse(pageX, pageY) {
|
||||||
|
|
||||||
absoluteMouseX = pageX;
|
guac_mouse.currentState.x = pageX - element.offsetLeft;
|
||||||
absoluteMouseY = pageY;
|
guac_mouse.currentState.y = pageY - element.offsetTop;
|
||||||
|
|
||||||
mouseX = absoluteMouseX - element.offsetLeft;
|
|
||||||
mouseY = absoluteMouseY - element.offsetTop;
|
|
||||||
|
|
||||||
// This is all JUST so we can get the mouse position within the element
|
// This is all JUST so we can get the mouse position within the element
|
||||||
var parent = element.offsetParent;
|
var parent = element.offsetParent;
|
||||||
while (parent) {
|
while (parent) {
|
||||||
if (parent.offsetLeft && parent.offsetTop) {
|
if (parent.offsetLeft && parent.offsetTop) {
|
||||||
mouseX -= parent.offsetLeft;
|
guac_mouse.currentState.x -= parent.offsetLeft;
|
||||||
mouseY -= parent.offsetTop;
|
guac_mouse.currentState.y -= parent.offsetTop;
|
||||||
}
|
}
|
||||||
parent = parent.offsetParent;
|
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
|
// 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) {
|
element.onmousemove = function(e) {
|
||||||
|
|
||||||
@@ -136,17 +169,19 @@ function GuacamoleMouse(element) {
|
|||||||
|
|
||||||
switch (e.button) {
|
switch (e.button) {
|
||||||
case 0:
|
case 0:
|
||||||
mouseLeftButton = 1;
|
guac_mouse.currentState.left = true;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
mouseMiddleButton = 1;
|
guac_mouse.currentState.middle = true;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
mouseRightButton = 1;
|
guac_mouse.currentState.right = true;
|
||||||
break;
|
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) {
|
switch (e.button) {
|
||||||
case 0:
|
case 0:
|
||||||
mouseLeftButton = 0;
|
guac_mouse.currentState.left = false;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
mouseMiddleButton = 0;
|
guac_mouse.currentState.middle = false;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
mouseRightButton = 0;
|
guac_mouse.currentState.right = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonReleasedHandler(getMouseState(0, 0));
|
if (guac_mouse.onmouseup)
|
||||||
|
guac_mouse.onmouseup(guac_mouse.currentState);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
element.onmouseout = function(e) {
|
element.onmouseout = function(e) {
|
||||||
@@ -174,12 +211,16 @@ function GuacamoleMouse(element) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// Release all buttons
|
// Release all buttons
|
||||||
if (mouseLeftButton || mouseMiddleButton || mouseRightButton) {
|
if (guac_mouse.currentState.left
|
||||||
mouseLeftButton = 0;
|
|| guac_mouse.currentState.middle
|
||||||
mouseMiddleButton = 0;
|
|| guac_mouse.currentState.right) {
|
||||||
mouseRightButton = 0;
|
|
||||||
|
|
||||||
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
|
// Up
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
buttonPressedHandler(getMouseState(1, 0));
|
if (guac_mouse.onmousedown) {
|
||||||
buttonReleasedHandler(getMouseState(0, 0));
|
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
|
// Down
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
buttonPressedHandler(getMouseState(0, 1));
|
if (guac_mouse.onmousedown) {
|
||||||
buttonReleasedHandler(getMouseState(0, 0));
|
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)
|
if (e.preventDefault)
|
||||||
@@ -220,53 +275,71 @@ function GuacamoleMouse(element) {
|
|||||||
|
|
||||||
element.onmousewheel = function(e) {
|
element.onmousewheel = function(e) {
|
||||||
handleScroll(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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -17,10 +17,19 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 allKeys = new Array();
|
||||||
var modifierState = new function() {};
|
var modifierState = new function() {};
|
||||||
|
|
||||||
|
@@ -16,7 +16,77 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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;
|
var tunnel_uuid;
|
||||||
|
|
||||||
@@ -36,12 +106,10 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
// Default to polling - will be turned off automatically if not needed
|
// Default to polling - will be turned off automatically if not needed
|
||||||
var pollingMode = POLLING_ENABLED;
|
var pollingMode = POLLING_ENABLED;
|
||||||
|
|
||||||
var instructionHandler = null;
|
var sendingMessages = false;
|
||||||
|
|
||||||
var sendingMessages = 0;
|
|
||||||
var outputMessageBuffer = "";
|
var outputMessageBuffer = "";
|
||||||
|
|
||||||
function sendMessage(message) {
|
this.sendMessage = function(message) {
|
||||||
|
|
||||||
// Do not attempt to send messages if not connected
|
// Do not attempt to send messages if not connected
|
||||||
if (currentState != STATE_CONNECTED)
|
if (currentState != STATE_CONNECTED)
|
||||||
@@ -49,16 +117,16 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
|
|
||||||
// Add event to queue, restart send loop if finished.
|
// Add event to queue, restart send loop if finished.
|
||||||
outputMessageBuffer += message;
|
outputMessageBuffer += message;
|
||||||
if (sendingMessages == 0)
|
if (!sendingMessages)
|
||||||
sendPendingMessages();
|
sendPendingMessages();
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
function sendPendingMessages() {
|
function sendPendingMessages() {
|
||||||
|
|
||||||
if (outputMessageBuffer.length > 0) {
|
if (outputMessageBuffer.length > 0) {
|
||||||
|
|
||||||
sendingMessages = 1;
|
sendingMessages = true;
|
||||||
|
|
||||||
var message_xmlhttprequest = new XMLHttpRequest();
|
var message_xmlhttprequest = new XMLHttpRequest();
|
||||||
message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid);
|
message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid);
|
||||||
@@ -75,7 +143,7 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
sendingMessages = 0;
|
sendingMessages = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +169,8 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start next request as soon as possible
|
// Start next request as soon as possible IF request was successful
|
||||||
if (xmlhttprequest.readyState >= 2 && nextRequest == null)
|
if (xmlhttprequest.readyState >= 2 && nextRequest == null && xmlhttprequest.status == 200)
|
||||||
nextRequest = makeRequest();
|
nextRequest = makeRequest();
|
||||||
|
|
||||||
// Parse stream when data is received and when complete.
|
// Parse stream when data is received and when complete.
|
||||||
@@ -117,9 +185,25 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If canceled, stop transfer
|
||||||
|
if (xmlhttprequest.status == 0) {
|
||||||
|
tunnel.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Halt on error during request
|
// Halt on error during request
|
||||||
if (xmlhttprequest.status == 0 || xmlhttprequest.status != 200) {
|
else if (xmlhttprequest.status != 200) {
|
||||||
disconnect();
|
|
||||||
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +244,8 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call instruction handler.
|
// Call instruction handler.
|
||||||
if (instructionHandler != null)
|
if (tunnel.oninstruction != null)
|
||||||
instructionHandler(opcode, parameters);
|
tunnel.oninstruction(opcode, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start search at end of string.
|
// 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
|
// Start tunnel and connect synchronously
|
||||||
var connect_xmlhttprequest = new XMLHttpRequest();
|
var connect_xmlhttprequest = new XMLHttpRequest();
|
||||||
@@ -239,18 +323,12 @@ function GuacamoleHTTPTunnel(tunnelURL) {
|
|||||||
currentState = STATE_CONNECTED;
|
currentState = STATE_CONNECTED;
|
||||||
handleResponse(makeRequest());
|
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();
|
||||||
|
Reference in New Issue
Block a user