| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <title>JSDoc: Source: main/webapp/modules/Client.js</title> |
| |
| <script src="scripts/prettify/prettify.js"> </script> |
| <script src="scripts/prettify/lang-css.js"> </script> |
| <!--[if lt IE 9]> |
| <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> |
| <![endif]--> |
| <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> |
| <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> |
| </head> |
| |
| <body> |
| |
| <div id="main"> |
| |
| <h1 class="page-title">Source: main/webapp/modules/Client.js</h1> |
| |
| |
| |
| |
| |
| |
| <section> |
| <article> |
| <pre class="prettyprint source linenums"><code>/* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var Guacamole = Guacamole || {}; |
| |
| /** |
| * Guacamole protocol client. Given a {@link Guacamole.Tunnel}, |
| * automatically handles incoming and outgoing Guacamole instructions via the |
| * provided tunnel, updating its display using one or more canvas elements. |
| * |
| * @constructor |
| * @param {!Guacamole.Tunnel} tunnel |
| * The tunnel to use to send and receive Guacamole instructions. |
| */ |
| Guacamole.Client = function(tunnel) { |
| |
| var guac_client = this; |
| |
| var currentState = Guacamole.Client.State.IDLE; |
| |
| var currentTimestamp = 0; |
| |
| /** |
| * The rough number of milliseconds to wait between sending keep-alive |
| * pings. This may vary depending on how frequently the browser allows |
| * timers to run, as well as how frequently the client receives messages |
| * from the server. |
| * |
| * @private |
| * @constant |
| * @type {!number} |
| */ |
| var KEEP_ALIVE_FREQUENCY = 5000; |
| |
| /** |
| * The current keep-alive ping timeout ID, if any. This will only be set |
| * upon connecting. |
| * |
| * @private |
| * @type {number} |
| */ |
| var keepAliveTimeout = null; |
| |
| /** |
| * The timestamp of the point in time that the last keep-live ping was |
| * sent, in milliseconds elapsed since midnight of January 1, 1970 UTC. |
| * |
| * @private |
| * @type {!number} |
| */ |
| var lastSentKeepAlive = 0; |
| |
| /** |
| * Translation from Guacamole protocol line caps to Layer line caps. |
| * |
| * @private |
| * @type {!Object.<number, string>} |
| */ |
| var lineCap = { |
| 0: "butt", |
| 1: "round", |
| 2: "square" |
| }; |
| |
| /** |
| * Translation from Guacamole protocol line caps to Layer line caps. |
| * |
| * @private |
| * @type {!Object.<number, string>} |
| */ |
| var lineJoin = { |
| 0: "bevel", |
| 1: "miter", |
| 2: "round" |
| }; |
| |
| /** |
| * The underlying Guacamole display. |
| * |
| * @private |
| * @type {!Guacamole.Display} |
| */ |
| var display = new Guacamole.Display(); |
| |
| /** |
| * All available layers and buffers |
| * |
| * @private |
| * @type {!Object.<number, (Guacamole.Display.VisibleLayer|Guacamole.Layer)>} |
| */ |
| var layers = {}; |
| |
| /** |
| * All audio players currently in use by the client. Initially, this will |
| * be empty, but audio players may be allocated by the server upon request. |
| * |
| * @private |
| * @type {!Object.<number, Guacamole.AudioPlayer>} |
| */ |
| var audioPlayers = {}; |
| |
| /** |
| * All video players currently in use by the client. Initially, this will |
| * be empty, but video players may be allocated by the server upon request. |
| * |
| * @private |
| * @type {!Object.<number, Guacamole.VideoPlayer>} |
| */ |
| var videoPlayers = {}; |
| |
| // No initial parsers |
| var parsers = []; |
| |
| // No initial streams |
| var streams = []; |
| |
| /** |
| * All current objects. The index of each object is dictated by the |
| * Guacamole server. |
| * |
| * @private |
| * @type {!Guacamole.Object[]} |
| */ |
| var objects = []; |
| |
| // Pool of available stream indices |
| var stream_indices = new Guacamole.IntegerPool(); |
| |
| // Array of allocated output streams by index |
| var output_streams = []; |
| |
| function setState(state) { |
| if (state != currentState) { |
| currentState = state; |
| if (guac_client.onstatechange) |
| guac_client.onstatechange(currentState); |
| } |
| } |
| |
| function isConnected() { |
| return currentState == Guacamole.Client.State.CONNECTED |
| || currentState == Guacamole.Client.State.WAITING; |
| } |
| |
| /** |
| * Produces an opaque representation of Guacamole.Client state which can be |
| * later imported through a call to importState(). This object is |
| * effectively an independent, compressed snapshot of protocol and display |
| * state. Invoking this function implicitly flushes the display. |
| * |
| * @param {!function} callback |
| * Callback which should be invoked once the state object is ready. The |
| * state object will be passed to the callback as the sole parameter. |
| * This callback may be invoked immediately, or later as the display |
| * finishes rendering and becomes ready. |
| */ |
| this.exportState = function exportState(callback) { |
| |
| // Start with empty state |
| var state = { |
| 'currentState' : currentState, |
| 'currentTimestamp' : currentTimestamp, |
| 'layers' : {} |
| }; |
| |
| var layersSnapshot = {}; |
| |
| // Make a copy of all current layers (protocol state) |
| for (var key in layers) { |
| layersSnapshot[key] = layers[key]; |
| } |
| |
| // Populate layers once data is available (display state, requires flush) |
| display.flush(function populateLayers() { |
| |
| // Export each defined layer/buffer |
| for (var key in layersSnapshot) { |
| |
| var index = parseInt(key); |
| var layer = layersSnapshot[key]; |
| var canvas = layer.toCanvas(); |
| |
| // Store layer/buffer dimensions |
| var exportLayer = { |
| 'width' : layer.width, |
| 'height' : layer.height |
| }; |
| |
| // Store layer/buffer image data, if it can be generated |
| if (layer.width && layer.height) |
| exportLayer.url = canvas.toDataURL('image/png'); |
| |
| // Add layer properties if not a buffer nor the default layer |
| if (index > 0) { |
| exportLayer.x = layer.x; |
| exportLayer.y = layer.y; |
| exportLayer.z = layer.z; |
| exportLayer.alpha = layer.alpha; |
| exportLayer.matrix = layer.matrix; |
| exportLayer.parent = getLayerIndex(layer.parent); |
| } |
| |
| // Store exported layer |
| state.layers[key] = exportLayer; |
| |
| } |
| |
| // Invoke callback now that the state is ready |
| callback(state); |
| |
| }); |
| |
| }; |
| |
| /** |
| * Restores Guacamole.Client protocol and display state based on an opaque |
| * object from a prior call to exportState(). The Guacamole.Client instance |
| * used to export that state need not be the same as this instance. |
| * |
| * @param {!object} state |
| * An opaque representation of Guacamole.Client state from a prior call |
| * to exportState(). |
| * |
| * @param {function} [callback] |
| * The function to invoke when state has finished being imported. This |
| * may happen immediately, or later as images within the provided state |
| * object are loaded. |
| */ |
| this.importState = function importState(state, callback) { |
| |
| var key; |
| var index; |
| |
| currentState = state.currentState; |
| currentTimestamp = state.currentTimestamp; |
| |
| // Cancel any pending display operations/frames |
| display.cancel(); |
| |
| // Dispose of all layers |
| for (key in layers) { |
| index = parseInt(key); |
| if (index > 0) |
| layers[key].dispose(); |
| } |
| |
| layers = {}; |
| |
| // Import state of each layer/buffer |
| for (key in state.layers) { |
| |
| index = parseInt(key); |
| |
| var importLayer = state.layers[key]; |
| var layer = getLayer(index); |
| |
| // Reset layer size |
| display.resize(layer, importLayer.width, importLayer.height); |
| |
| // Initialize new layer if it has associated data |
| if (importLayer.url) { |
| display.setChannelMask(layer, Guacamole.Layer.SRC); |
| display.draw(layer, 0, 0, importLayer.url); |
| } |
| |
| // Set layer-specific properties if not a buffer nor the default layer |
| if (index > 0 && importLayer.parent >= 0) { |
| |
| // Apply layer position and set parent |
| var parent = getLayer(importLayer.parent); |
| display.move(layer, parent, importLayer.x, importLayer.y, importLayer.z); |
| |
| // Set layer transparency |
| display.shade(layer, importLayer.alpha); |
| |
| // Apply matrix transform |
| var matrix = importLayer.matrix; |
| display.distort(layer, |
| matrix[0], matrix[1], matrix[2], |
| matrix[3], matrix[4], matrix[5]); |
| |
| } |
| |
| } |
| |
| // Flush changes to display |
| display.flush(callback); |
| |
| }; |
| |
| /** |
| * Returns the underlying display of this Guacamole.Client. The display |
| * contains an Element which can be added to the DOM, causing the |
| * display to become visible. |
| * |
| * @return {!Guacamole.Display} |
| * The underlying display of this Guacamole.Client. |
| */ |
| this.getDisplay = function() { |
| return display; |
| }; |
| |
| /** |
| * Sends the current size of the screen. |
| * |
| * @param {!number} width |
| * The width of the screen. |
| * |
| * @param {!number} height |
| * The height of the screen. |
| */ |
| this.sendSize = function(width, height) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| tunnel.sendMessage("size", width, height); |
| |
| }; |
| |
| /** |
| * Sends a key event having the given properties as if the user |
| * pressed or released a key. |
| * |
| * @param {!boolean} pressed |
| * Whether the key is pressed (true) or released (false). |
| * |
| * @param {!number} keysym |
| * The keysym of the key being pressed or released. |
| */ |
| this.sendKeyEvent = function(pressed, keysym) { |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| tunnel.sendMessage("key", keysym, pressed); |
| }; |
| |
| /** |
| * Sends a mouse event having the properties provided by the given mouse |
| * state. |
| * |
| * @param {!Guacamole.Mouse.State} mouseState |
| * The state of the mouse to send in the mouse event. |
| * |
| * @param {boolean} [applyDisplayScale=false] |
| * Whether the provided mouse state uses local display units, rather |
| * than remote display units, and should be scaled to match the |
| * {@link Guacamole.Display}. |
| */ |
| this.sendMouseState = function sendMouseState(mouseState, applyDisplayScale) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| var x = mouseState.x; |
| var y = mouseState.y; |
| |
| // Translate for display units if requested |
| if (applyDisplayScale) { |
| x /= display.getScale(); |
| y /= display.getScale(); |
| } |
| |
| // Update client-side cursor |
| display.moveCursor( |
| Math.floor(x), |
| Math.floor(y) |
| ); |
| |
| // Build mask |
| var buttonMask = 0; |
| 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", Math.floor(x), Math.floor(y), buttonMask); |
| }; |
| |
| /** |
| * Sends a touch event having the properties provided by the given touch |
| * state. |
| * |
| * @param {!Guacamole.Touch.State} touchState |
| * The state of the touch contact to send in the touch event. |
| * |
| * @param {boolean} [applyDisplayScale=false] |
| * Whether the provided touch state uses local display units, rather |
| * than remote display units, and should be scaled to match the |
| * {@link Guacamole.Display}. |
| */ |
| this.sendTouchState = function sendTouchState(touchState, applyDisplayScale) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| var x = touchState.x; |
| var y = touchState.y; |
| |
| // Translate for display units if requested |
| if (applyDisplayScale) { |
| x /= display.getScale(); |
| y /= display.getScale(); |
| } |
| |
| tunnel.sendMessage('touch', touchState.id, Math.floor(x), Math.floor(y), |
| Math.floor(touchState.radiusX), Math.floor(touchState.radiusY), |
| touchState.angle, touchState.force); |
| |
| }; |
| |
| /** |
| * Allocates an available stream index and creates a new |
| * Guacamole.OutputStream using that index, associating the resulting |
| * stream with this Guacamole.Client. Note that this stream will not yet |
| * exist as far as the other end of the Guacamole connection is concerned. |
| * Streams exist within the Guacamole protocol only when referenced by an |
| * instruction which creates the stream, such as a "clipboard", "file", or |
| * "pipe" instruction. |
| * |
| * @returns {!Guacamole.OutputStream} |
| * A new Guacamole.OutputStream with a newly-allocated index and |
| * associated with this Guacamole.Client. |
| */ |
| this.createOutputStream = function createOutputStream() { |
| |
| // Allocate index |
| var index = stream_indices.next(); |
| |
| // Return new stream |
| var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index); |
| return stream; |
| |
| }; |
| |
| /** |
| * Opens a new audio stream for writing, where audio data having the give |
| * mimetype will be sent along the returned stream. The instruction |
| * necessary to create this stream will automatically be sent. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the audio data that will be sent along the returned |
| * stream. |
| * |
| * @return {!Guacamole.OutputStream} |
| * The created audio stream. |
| */ |
| this.createAudioStream = function(mimetype) { |
| |
| // Allocate and associate stream with audio metadata |
| var stream = guac_client.createOutputStream(); |
| tunnel.sendMessage("audio", stream.index, mimetype); |
| return stream; |
| |
| }; |
| |
| /** |
| * Opens a new file for writing, having the given index, mimetype and |
| * filename. The instruction necessary to create this stream will |
| * automatically be sent. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the file being sent. |
| * |
| * @param {!string} filename |
| * The filename of the file being sent. |
| * |
| * @return {!Guacamole.OutputStream} |
| * The created file stream. |
| */ |
| this.createFileStream = function(mimetype, filename) { |
| |
| // Allocate and associate stream with file metadata |
| var stream = guac_client.createOutputStream(); |
| tunnel.sendMessage("file", stream.index, mimetype, filename); |
| return stream; |
| |
| }; |
| |
| /** |
| * Opens a new pipe for writing, having the given name and mimetype. The |
| * instruction necessary to create this stream will automatically be sent. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data being sent. |
| * |
| * @param {!string} name |
| * The name of the pipe. |
| * |
| * @return {!Guacamole.OutputStream} |
| * The created file stream. |
| */ |
| this.createPipeStream = function(mimetype, name) { |
| |
| // Allocate and associate stream with pipe metadata |
| var stream = guac_client.createOutputStream(); |
| tunnel.sendMessage("pipe", stream.index, mimetype, name); |
| return stream; |
| |
| }; |
| |
| /** |
| * Opens a new clipboard object for writing, having the given mimetype. The |
| * instruction necessary to create this stream will automatically be sent. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data being sent. |
| * |
| * @param {!string} name |
| * The name of the pipe. |
| * |
| * @return {!Guacamole.OutputStream} |
| * The created file stream. |
| */ |
| this.createClipboardStream = function(mimetype) { |
| |
| // Allocate and associate stream with clipboard metadata |
| var stream = guac_client.createOutputStream(); |
| tunnel.sendMessage("clipboard", stream.index, mimetype); |
| return stream; |
| |
| }; |
| |
| /** |
| * Opens a new argument value stream for writing, having the given |
| * parameter name and mimetype, requesting that the connection parameter |
| * with the given name be updated to the value described by the contents |
| * of the following stream. The instruction necessary to create this stream |
| * will automatically be sent. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data being sent. |
| * |
| * @param {!string} name |
| * The name of the connection parameter to attempt to update. |
| * |
| * @return {!Guacamole.OutputStream} |
| * The created argument value stream. |
| */ |
| this.createArgumentValueStream = function createArgumentValueStream(mimetype, name) { |
| |
| // Allocate and associate stream with argument value metadata |
| var stream = guac_client.createOutputStream(); |
| tunnel.sendMessage("argv", stream.index, mimetype, name); |
| return stream; |
| |
| }; |
| |
| /** |
| * Creates a new output stream associated with the given object and having |
| * the given mimetype and name. The legality of a mimetype and name is |
| * dictated by the object itself. The instruction necessary to create this |
| * stream will automatically be sent. |
| * |
| * @param {!number} index |
| * The index of the object for which the output stream is being |
| * created. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data which will be sent to the output stream. |
| * |
| * @param {!string} name |
| * The defined name of an output stream within the given object. |
| * |
| * @returns {!Guacamole.OutputStream} |
| * An output stream which will write blobs to the named output stream |
| * of the given object. |
| */ |
| this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) { |
| |
| // Allocate and associate stream with object metadata |
| var stream = guac_client.createOutputStream(); |
| tunnel.sendMessage("put", index, stream.index, mimetype, name); |
| return stream; |
| |
| }; |
| |
| /** |
| * Requests read access to the input stream having the given name. If |
| * successful, a new input stream will be created. |
| * |
| * @param {!number} index |
| * The index of the object from which the input stream is being |
| * requested. |
| * |
| * @param {!string} name |
| * The name of the input stream to request. |
| */ |
| this.requestObjectInputStream = function requestObjectInputStream(index, name) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| tunnel.sendMessage("get", index, name); |
| }; |
| |
| /** |
| * Acknowledge receipt of a blob on the stream with the given index. |
| * |
| * @param {!number} index |
| * The index of the stream associated with the received blob. |
| * |
| * @param {!string} message |
| * A human-readable message describing the error or status. |
| * |
| * @param {!number} code |
| * The error code, if any, or 0 for success. |
| */ |
| this.sendAck = function(index, message, code) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| tunnel.sendMessage("ack", index, message, code); |
| }; |
| |
| /** |
| * Given the index of a file, writes a blob of data to that file. |
| * |
| * @param {!number} index |
| * The index of the file to write to. |
| * |
| * @param {!string} data |
| * Base64-encoded data to write to the file. |
| */ |
| this.sendBlob = function(index, data) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| tunnel.sendMessage("blob", index, data); |
| }; |
| |
| /** |
| * Marks a currently-open stream as complete. The other end of the |
| * Guacamole connection will be notified via an "end" instruction that the |
| * stream is closed, and the index will be made available for reuse in |
| * future streams. |
| * |
| * @param {!number} index |
| * The index of the stream to end. |
| */ |
| this.endStream = function(index) { |
| |
| // Do not send requests if not connected |
| if (!isConnected()) |
| return; |
| |
| // Explicitly close stream by sending "end" instruction |
| tunnel.sendMessage("end", index); |
| |
| // Free associated index and stream if they exist |
| if (output_streams[index]) { |
| stream_indices.free(index); |
| delete output_streams[index]; |
| } |
| |
| }; |
| |
| /** |
| * Fired whenever the state of this Guacamole.Client changes. |
| * |
| * @event |
| * @param {!number} state |
| * The new state of the client. |
| */ |
| this.onstatechange = null; |
| |
| /** |
| * Fired when the remote client sends a name update. |
| * |
| * @event |
| * @param {!string} name |
| * The new name of this client. |
| */ |
| this.onname = null; |
| |
| /** |
| * Fired when an error is reported by the remote client, and the connection |
| * is being closed. |
| * |
| * @event |
| * @param {!Guacamole.Status} status |
| * A status object which describes the error. |
| */ |
| this.onerror = null; |
| |
| /** |
| * Fired when an arbitrary message is received from the tunnel that should |
| * be processed by the client. By default, additional message-specific |
| * events such as "onjoin" and "onleave" will fire for the received message |
| * after this event has been processed. An event handler for "onmsg" need |
| * not be supplied if "onjoin" and/or "onleave" will be used. |
| * |
| * @event |
| * @param {!number} msgcode |
| * A status code sent by the remote server that indicates the nature of |
| * the message that is being sent to the client. |
| * |
| * @param {string[]} args |
| * An array of arguments to be processed with the message sent to the |
| * client. |
| * |
| * @return {boolean} |
| * true if message-specific events such as "onjoin" and |
| * "onleave" should be fired for this message, false otherwise. If |
| * no value is returned, message-specific events will be allowed to |
| * fire. |
| */ |
| this.onmsg = null; |
| |
| /** |
| * Fired when a user joins a shared connection. |
| * |
| * @event |
| * @param {!string} userID |
| * A unique value representing this specific user's connection to the |
| * shared connection. This value is generated by the server and is |
| * guaranteed to be unique relative to other users of the connection. |
| * |
| * @param {!string} name |
| * A human-readable name representing the user that joined, such as |
| * their username. This value is provided by the web application during |
| * the connection handshake and is not necessarily unique relative to |
| * other users of the connection. |
| */ |
| this.onjoin = null; |
| |
| /** |
| * Fired when a user leaves a shared connection. |
| * |
| * @event |
| * @param {!string} userID |
| * A unique value representing this specific user's connection to the |
| * shared connection. This value is generated by the server and is |
| * guaranteed to be unique relative to other users of the connection. |
| * |
| * @param {!string} name |
| * A human-readable name representing the user that left, such as their |
| * username. This value is provided by the web application during the |
| * connection handshake and is not necessarily unique relative to other |
| * users of the connection. |
| */ |
| this.onleave = null; |
| |
| /** |
| * Fired when a audio stream is created. The stream provided to this event |
| * handler will contain its own event handlers for received data. |
| * |
| * @event |
| * @param {!Guacamole.InputStream} stream |
| * The stream that will receive audio data from the server. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the audio data which will be received. |
| * |
| * @return {Guacamole.AudioPlayer} |
| * An object which implements the Guacamole.AudioPlayer interface and |
| * has been initialized to play the data in the provided stream, or null |
| * if the built-in audio players of the Guacamole client should be |
| * used. |
| */ |
| this.onaudio = null; |
| |
| /** |
| * Fired when a video stream is created. The stream provided to this event |
| * handler will contain its own event handlers for received data. |
| * |
| * @event |
| * @param {!Guacamole.InputStream} stream |
| * The stream that will receive video data from the server. |
| * |
| * @param {!Guacamole.Display.VisibleLayer} layer |
| * The destination layer on which the received video data should be |
| * played. It is the responsibility of the Guacamole.VideoPlayer |
| * implementation to play the received data within this layer. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the video data which will be received. |
| * |
| * @return {Guacamole.VideoPlayer} |
| * An object which implements the Guacamole.VideoPlayer interface and |
| * has been initialized to play the data in the provided stream, or null |
| * if the built-in video players of the Guacamole client should be |
| * used. |
| */ |
| this.onvideo = null; |
| |
| /** |
| * Fired when the remote client is explicitly declaring the level of |
| * multi-touch support provided by a particular display layer. |
| * |
| * @event |
| * @param {!Guacamole.Display.VisibleLayer} layer |
| * The layer whose multi-touch support level is being declared. |
| * |
| * @param {!number} touches |
| * The maximum number of simultaneous touches supported by the given |
| * layer, where 0 indicates that touch events are not supported at all. |
| */ |
| this.onmultitouch = null; |
| |
| /** |
| * Fired when the current value of a connection parameter is being exposed |
| * by the server. |
| * |
| * @event |
| * @param {!Guacamole.InputStream} stream |
| * The stream that will receive connection parameter data from the |
| * server. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data which will be received. |
| * |
| * @param {!string} name |
| * The name of the connection parameter whose value is being exposed. |
| */ |
| this.onargv = null; |
| |
| /** |
| * Fired when the clipboard of the remote client is changing. |
| * |
| * @event |
| * @param {!Guacamole.InputStream} stream |
| * The stream that will receive clipboard data from the server. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data which will be received. |
| */ |
| this.onclipboard = null; |
| |
| /** |
| * Fired when a file stream is created. The stream provided to this event |
| * handler will contain its own event handlers for received data. |
| * |
| * @event |
| * @param {!Guacamole.InputStream} stream |
| * The stream that will receive data from the server. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the file received. |
| * |
| * @param {!string} filename |
| * The name of the file received. |
| */ |
| this.onfile = null; |
| |
| /** |
| * Fired when a filesystem object is created. The object provided to this |
| * event handler will contain its own event handlers and functions for |
| * requesting and handling data. |
| * |
| * @event |
| * @param {!Guacamole.Object} object |
| * The created filesystem object. |
| * |
| * @param {!string} name |
| * The name of the filesystem. |
| */ |
| this.onfilesystem = null; |
| |
| /** |
| * Fired when a pipe stream is created. The stream provided to this event |
| * handler will contain its own event handlers for received data; |
| * |
| * @event |
| * @param {!Guacamole.InputStream} stream |
| * The stream that will receive data from the server. |
| * |
| * @param {!string} mimetype |
| * The mimetype of the data which will be received. |
| * |
| * @param {!string} name |
| * The name of the pipe. |
| */ |
| this.onpipe = null; |
| |
| /** |
| * Fired when a "required" instruction is received. A required instruction |
| * indicates that additional parameters are required for the connection to |
| * continue, such as user credentials. |
| * |
| * @event |
| * @param {!string[]} parameters |
| * The names of the connection parameters that are required to be |
| * provided for the connection to continue. |
| */ |
| this.onrequired = null; |
| |
| /** |
| * Fired whenever a sync instruction is received from the server, indicating |
| * that the server is finished processing any input from the client and |
| * has sent any results. |
| * |
| * @event |
| * @param {!number} timestamp |
| * The timestamp associated with the sync instruction. |
| * |
| * @param {!number} frames |
| * The number of frames that were considered or combined to produce the |
| * frame associated with this sync instruction, or zero if this value |
| * is not known or the remote desktop server provides no concept of |
| * frames. |
| */ |
| this.onsync = null; |
| |
| /** |
| * Returns the layer with the given index, creating it if necessary. |
| * Positive indices refer to visible layers, an index of zero refers to |
| * the default layer, and negative indices refer to buffers. |
| * |
| * @private |
| * @param {!number} index |
| * The index of the layer to retrieve. |
| * |
| * @return {!(Guacamole.Display.VisibleLayer|Guacamole.Layer)} |
| * The layer having the given index. |
| */ |
| var getLayer = function getLayer(index) { |
| |
| // Get layer, create if necessary |
| var layer = layers[index]; |
| if (!layer) { |
| |
| // Create layer based on index |
| if (index === 0) |
| layer = display.getDefaultLayer(); |
| else if (index > 0) |
| layer = display.createLayer(); |
| else |
| layer = display.createBuffer(); |
| |
| // Add new layer |
| layers[index] = layer; |
| |
| } |
| |
| return layer; |
| |
| }; |
| |
| /** |
| * Returns the index passed to getLayer() when the given layer was created. |
| * Positive indices refer to visible layers, an index of zero refers to the |
| * default layer, and negative indices refer to buffers. |
| * |
| * @param {!(Guacamole.Display.VisibleLayer|Guacamole.Layer)} layer |
| * The layer whose index should be determined. |
| * |
| * @returns {number} |
| * The index of the given layer, or null if no such layer is associated |
| * with this client. |
| */ |
| var getLayerIndex = function getLayerIndex(layer) { |
| |
| // Avoid searching if there clearly is no such layer |
| if (!layer) |
| return null; |
| |
| // Search through each layer, returning the index of the given layer |
| // once found |
| for (var key in layers) { |
| if (layer === layers[key]) |
| return parseInt(key); |
| } |
| |
| // Otherwise, no such index |
| return null; |
| |
| }; |
| |
| function getParser(index) { |
| |
| var parser = parsers[index]; |
| |
| // If parser not yet created, create it, and tie to the |
| // oninstruction handler of the tunnel. |
| if (parser == null) { |
| parser = parsers[index] = new Guacamole.Parser(); |
| parser.oninstruction = tunnel.oninstruction; |
| } |
| |
| return parser; |
| |
| } |
| |
| /** |
| * Handlers for all defined layer properties. |
| * |
| * @private |
| * @type {!Object.<string, function>} |
| */ |
| var layerPropertyHandlers = { |
| |
| "miter-limit": function(layer, value) { |
| display.setMiterLimit(layer, parseFloat(value)); |
| }, |
| |
| "multi-touch" : function layerSupportsMultiTouch(layer, value) { |
| |
| // Process "multi-touch" property only for true visible layers (not off-screen buffers) |
| if (guac_client.onmultitouch && layer instanceof Guacamole.Display.VisibleLayer) |
| guac_client.onmultitouch(layer, parseInt(value)); |
| |
| } |
| |
| }; |
| |
| /** |
| * Handlers for all instruction opcodes receivable by a Guacamole protocol |
| * client. |
| * |
| * @private |
| * @type {!Object.<string, function>} |
| */ |
| var instructionHandlers = { |
| |
| "ack": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var reason = parameters[1]; |
| var code = parseInt(parameters[2]); |
| |
| // Get stream |
| var stream = output_streams[stream_index]; |
| if (stream) { |
| |
| // Signal ack if handler defined |
| if (stream.onack) |
| stream.onack(new Guacamole.Status(code, reason)); |
| |
| // If code is an error, invalidate stream if not already |
| // invalidated by onack handler |
| if (code >= 0x0100 && output_streams[stream_index] === stream) { |
| stream_indices.free(stream_index); |
| delete output_streams[stream_index]; |
| } |
| |
| } |
| |
| }, |
| |
| "arc": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| var x = parseInt(parameters[1]); |
| var y = parseInt(parameters[2]); |
| var radius = parseInt(parameters[3]); |
| var startAngle = parseFloat(parameters[4]); |
| var endAngle = parseFloat(parameters[5]); |
| var negative = parseInt(parameters[6]); |
| |
| display.arc(layer, x, y, radius, startAngle, endAngle, negative != 0); |
| |
| }, |
| |
| "argv": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var mimetype = parameters[1]; |
| var name = parameters[2]; |
| |
| // Create stream |
| if (guac_client.onargv) { |
| var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
| guac_client.onargv(stream, mimetype, name); |
| } |
| |
| // Otherwise, unsupported |
| else |
| guac_client.sendAck(stream_index, "Receiving argument values unsupported", 0x0100); |
| |
| }, |
| |
| "audio": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var mimetype = parameters[1]; |
| |
| // Create stream |
| var stream = streams[stream_index] = |
| new Guacamole.InputStream(guac_client, stream_index); |
| |
| // Get player instance via callback |
| var audioPlayer = null; |
| if (guac_client.onaudio) |
| audioPlayer = guac_client.onaudio(stream, mimetype); |
| |
| // If unsuccessful, try to use a default implementation |
| if (!audioPlayer) |
| audioPlayer = Guacamole.AudioPlayer.getInstance(stream, mimetype); |
| |
| // If we have successfully retrieved an audio player, send success response |
| if (audioPlayer) { |
| audioPlayers[stream_index] = audioPlayer; |
| guac_client.sendAck(stream_index, "OK", 0x0000); |
| } |
| |
| // Otherwise, mimetype must be unsupported |
| else |
| guac_client.sendAck(stream_index, "BAD TYPE", 0x030F); |
| |
| }, |
| |
| "blob": function(parameters) { |
| |
| // Get stream |
| var stream_index = parseInt(parameters[0]); |
| var data = parameters[1]; |
| var stream = streams[stream_index]; |
| |
| // Write data |
| if (stream && stream.onblob) |
| stream.onblob(data); |
| |
| }, |
| |
| "body" : function handleBody(parameters) { |
| |
| // Get object |
| var objectIndex = parseInt(parameters[0]); |
| var object = objects[objectIndex]; |
| |
| var streamIndex = parseInt(parameters[1]); |
| var mimetype = parameters[2]; |
| var name = parameters[3]; |
| |
| // Create stream if handler defined |
| if (object && object.onbody) { |
| var stream = streams[streamIndex] = new Guacamole.InputStream(guac_client, streamIndex); |
| object.onbody(stream, mimetype, name); |
| } |
| |
| // Otherwise, unsupported |
| else |
| guac_client.sendAck(streamIndex, "Receipt of body unsupported", 0x0100); |
| |
| }, |
| |
| "cfill": function(parameters) { |
| |
| var channelMask = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var r = parseInt(parameters[2]); |
| var g = parseInt(parameters[3]); |
| var b = parseInt(parameters[4]); |
| var a = parseInt(parameters[5]); |
| |
| display.setChannelMask(layer, channelMask); |
| display.fillColor(layer, r, g, b, a); |
| |
| }, |
| |
| "clip": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| |
| display.clip(layer); |
| |
| }, |
| |
| "clipboard": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var mimetype = parameters[1]; |
| |
| // Create stream |
| if (guac_client.onclipboard) { |
| var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
| guac_client.onclipboard(stream, mimetype); |
| } |
| |
| // Otherwise, unsupported |
| else |
| guac_client.sendAck(stream_index, "Clipboard unsupported", 0x0100); |
| |
| }, |
| |
| "close": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| |
| display.close(layer); |
| |
| }, |
| |
| "copy": function(parameters) { |
| |
| var srcL = getLayer(parseInt(parameters[0])); |
| var srcX = parseInt(parameters[1]); |
| var srcY = parseInt(parameters[2]); |
| var srcWidth = parseInt(parameters[3]); |
| var srcHeight = parseInt(parameters[4]); |
| var channelMask = parseInt(parameters[5]); |
| var dstL = getLayer(parseInt(parameters[6])); |
| var dstX = parseInt(parameters[7]); |
| var dstY = parseInt(parameters[8]); |
| |
| display.setChannelMask(dstL, channelMask); |
| display.copy(srcL, srcX, srcY, srcWidth, srcHeight, |
| dstL, dstX, dstY); |
| |
| }, |
| |
| "cstroke": function(parameters) { |
| |
| var channelMask = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var cap = lineCap[parseInt(parameters[2])]; |
| var join = lineJoin[parseInt(parameters[3])]; |
| var thickness = parseInt(parameters[4]); |
| var r = parseInt(parameters[5]); |
| var g = parseInt(parameters[6]); |
| var b = parseInt(parameters[7]); |
| var a = parseInt(parameters[8]); |
| |
| display.setChannelMask(layer, channelMask); |
| display.strokeColor(layer, cap, join, thickness, r, g, b, a); |
| |
| }, |
| |
| "cursor": function(parameters) { |
| |
| var cursorHotspotX = parseInt(parameters[0]); |
| var cursorHotspotY = parseInt(parameters[1]); |
| var srcL = getLayer(parseInt(parameters[2])); |
| var srcX = parseInt(parameters[3]); |
| var srcY = parseInt(parameters[4]); |
| var srcWidth = parseInt(parameters[5]); |
| var srcHeight = parseInt(parameters[6]); |
| |
| display.setCursor(cursorHotspotX, cursorHotspotY, |
| srcL, srcX, srcY, srcWidth, srcHeight); |
| |
| }, |
| |
| "curve": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| var cp1x = parseInt(parameters[1]); |
| var cp1y = parseInt(parameters[2]); |
| var cp2x = parseInt(parameters[3]); |
| var cp2y = parseInt(parameters[4]); |
| var x = parseInt(parameters[5]); |
| var y = parseInt(parameters[6]); |
| |
| display.curveTo(layer, cp1x, cp1y, cp2x, cp2y, x, y); |
| |
| }, |
| |
| "disconnect" : function handleDisconnect(parameters) { |
| |
| // Explicitly tear down connection |
| guac_client.disconnect(); |
| |
| }, |
| |
| "dispose": function(parameters) { |
| |
| var layer_index = parseInt(parameters[0]); |
| |
| // If visible layer, remove from parent |
| if (layer_index > 0) { |
| |
| // Remove from parent |
| var layer = getLayer(layer_index); |
| display.dispose(layer); |
| |
| // Delete reference |
| delete layers[layer_index]; |
| |
| } |
| |
| // If buffer, just delete reference |
| else if (layer_index < 0) |
| delete layers[layer_index]; |
| |
| // Attempting to dispose the root layer currently has no effect. |
| |
| }, |
| |
| "distort": function(parameters) { |
| |
| var layer_index = parseInt(parameters[0]); |
| var a = parseFloat(parameters[1]); |
| var b = parseFloat(parameters[2]); |
| var c = parseFloat(parameters[3]); |
| var d = parseFloat(parameters[4]); |
| var e = parseFloat(parameters[5]); |
| var f = parseFloat(parameters[6]); |
| |
| // Only valid for visible layers (not buffers) |
| if (layer_index >= 0) { |
| var layer = getLayer(layer_index); |
| display.distort(layer, a, b, c, d, e, f); |
| } |
| |
| }, |
| |
| "error": function(parameters) { |
| |
| var reason = parameters[0]; |
| var code = parseInt(parameters[1]); |
| |
| // Call handler if defined |
| if (guac_client.onerror) |
| guac_client.onerror(new Guacamole.Status(code, reason)); |
| |
| guac_client.disconnect(); |
| |
| }, |
| |
| "end": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| |
| // Get stream |
| var stream = streams[stream_index]; |
| if (stream) { |
| |
| // Signal end of stream if handler defined |
| if (stream.onend) |
| stream.onend(); |
| |
| // Invalidate stream |
| delete streams[stream_index]; |
| |
| } |
| |
| }, |
| |
| "file": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var mimetype = parameters[1]; |
| var filename = parameters[2]; |
| |
| // Create stream |
| if (guac_client.onfile) { |
| var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
| guac_client.onfile(stream, mimetype, filename); |
| } |
| |
| // Otherwise, unsupported |
| else |
| guac_client.sendAck(stream_index, "File transfer unsupported", 0x0100); |
| |
| }, |
| |
| "filesystem" : function handleFilesystem(parameters) { |
| |
| var objectIndex = parseInt(parameters[0]); |
| var name = parameters[1]; |
| |
| // Create object, if supported |
| if (guac_client.onfilesystem) { |
| var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex); |
| guac_client.onfilesystem(object, name); |
| } |
| |
| // If unsupported, simply ignore the availability of the filesystem |
| |
| }, |
| |
| "identity": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| |
| display.setTransform(layer, 1, 0, 0, 1, 0, 0); |
| |
| }, |
| |
| "img": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var channelMask = parseInt(parameters[1]); |
| var layer = getLayer(parseInt(parameters[2])); |
| var mimetype = parameters[3]; |
| var x = parseInt(parameters[4]); |
| var y = parseInt(parameters[5]); |
| |
| // Create stream |
| var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
| |
| // Draw received contents once decoded |
| display.setChannelMask(layer, channelMask); |
| display.drawStream(layer, x, y, stream, mimetype); |
| |
| }, |
| |
| "jpeg": function(parameters) { |
| |
| var channelMask = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var x = parseInt(parameters[2]); |
| var y = parseInt(parameters[3]); |
| var data = parameters[4]; |
| |
| display.setChannelMask(layer, channelMask); |
| display.draw(layer, x, y, "data:image/jpeg;base64," + data); |
| |
| }, |
| |
| "lfill": function(parameters) { |
| |
| var channelMask = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var srcLayer = getLayer(parseInt(parameters[2])); |
| |
| display.setChannelMask(layer, channelMask); |
| display.fillLayer(layer, srcLayer); |
| |
| }, |
| |
| "line": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| var x = parseInt(parameters[1]); |
| var y = parseInt(parameters[2]); |
| |
| display.lineTo(layer, x, y); |
| |
| }, |
| |
| "lstroke": function(parameters) { |
| |
| var channelMask = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var srcLayer = getLayer(parseInt(parameters[2])); |
| |
| display.setChannelMask(layer, channelMask); |
| display.strokeLayer(layer, srcLayer); |
| |
| }, |
| |
| "mouse" : function handleMouse(parameters) { |
| |
| var x = parseInt(parameters[0]); |
| var y = parseInt(parameters[1]); |
| |
| // Display and move software cursor to received coordinates |
| display.showCursor(true); |
| display.moveCursor(x, y); |
| |
| }, |
| |
| "move": function(parameters) { |
| |
| var layer_index = parseInt(parameters[0]); |
| var parent_index = parseInt(parameters[1]); |
| var x = parseInt(parameters[2]); |
| var y = parseInt(parameters[3]); |
| var z = parseInt(parameters[4]); |
| |
| // Only valid for non-default layers |
| if (layer_index > 0 && parent_index >= 0) { |
| var layer = getLayer(layer_index); |
| var parent = getLayer(parent_index); |
| display.move(layer, parent, x, y, z); |
| } |
| |
| }, |
| |
| "msg" : function(parameters) { |
| |
| var userID; |
| var username; |
| |
| // Fire general message handling event first |
| var allowDefault = true; |
| var msgid = parseInt(parameters[0]); |
| if (guac_client.onmsg) { |
| allowDefault = guac_client.onmsg(msgid, parameters.slice(1)); |
| if (allowDefault === undefined) |
| allowDefault = true; |
| } |
| |
| // Fire message-specific convenience events if not prevented by the |
| // "onmsg" handler |
| if (allowDefault) { |
| switch (msgid) { |
| |
| case Guacamole.Client.Message.USER_JOINED: |
| userID = parameters[1]; |
| username = parameters[2]; |
| if (guac_client.onjoin) |
| guac_client.onjoin(userID, username); |
| break; |
| |
| case Guacamole.Client.Message.USER_LEFT: |
| userID = parameters[1]; |
| username = parameters[2]; |
| if (guac_client.onleave) |
| guac_client.onleave(userID, username); |
| break; |
| |
| } |
| } |
| |
| }, |
| |
| "name": function(parameters) { |
| if (guac_client.onname) guac_client.onname(parameters[0]); |
| }, |
| |
| "nest": function(parameters) { |
| var parser = getParser(parseInt(parameters[0])); |
| parser.receive(parameters[1]); |
| }, |
| |
| "pipe": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var mimetype = parameters[1]; |
| var name = parameters[2]; |
| |
| // Create stream |
| if (guac_client.onpipe) { |
| var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
| guac_client.onpipe(stream, mimetype, name); |
| } |
| |
| // Otherwise, unsupported |
| else |
| guac_client.sendAck(stream_index, "Named pipes unsupported", 0x0100); |
| |
| }, |
| |
| "png": function(parameters) { |
| |
| var channelMask = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var x = parseInt(parameters[2]); |
| var y = parseInt(parameters[3]); |
| var data = parameters[4]; |
| |
| display.setChannelMask(layer, channelMask); |
| display.draw(layer, x, y, "data:image/png;base64," + data); |
| |
| }, |
| |
| "pop": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| |
| display.pop(layer); |
| |
| }, |
| |
| "push": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| |
| display.push(layer); |
| |
| }, |
| |
| "rect": 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]); |
| |
| display.rect(layer, x, y, w, h); |
| |
| }, |
| |
| "required": function required(parameters) { |
| if (guac_client.onrequired) guac_client.onrequired(parameters); |
| }, |
| |
| "reset": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| |
| display.reset(layer); |
| |
| }, |
| |
| "set": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| var name = parameters[1]; |
| var value = parameters[2]; |
| |
| // Call property handler if defined |
| var handler = layerPropertyHandlers[name]; |
| if (handler) |
| handler(layer, value); |
| |
| }, |
| |
| "shade": function(parameters) { |
| |
| var layer_index = parseInt(parameters[0]); |
| var a = parseInt(parameters[1]); |
| |
| // Only valid for visible layers (not buffers) |
| if (layer_index >= 0) { |
| var layer = getLayer(layer_index); |
| display.shade(layer, a); |
| } |
| |
| }, |
| |
| "size": function(parameters) { |
| |
| var layer_index = parseInt(parameters[0]); |
| var layer = getLayer(layer_index); |
| var width = parseInt(parameters[1]); |
| var height = parseInt(parameters[2]); |
| |
| display.resize(layer, width, height); |
| |
| }, |
| |
| "start": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| var x = parseInt(parameters[1]); |
| var y = parseInt(parameters[2]); |
| |
| display.moveTo(layer, x, y); |
| |
| }, |
| |
| "sync": function(parameters) { |
| |
| var timestamp = parseInt(parameters[0]); |
| var frames = parameters[1] ? parseInt(parameters[1]) : 0; |
| |
| // Flush display, send sync when done |
| display.flush(function displaySyncComplete() { |
| |
| // Synchronize all audio players |
| for (var index in audioPlayers) { |
| var audioPlayer = audioPlayers[index]; |
| if (audioPlayer) |
| audioPlayer.sync(); |
| } |
| |
| // Send sync response to server |
| if (timestamp !== currentTimestamp) { |
| tunnel.sendMessage("sync", timestamp); |
| currentTimestamp = timestamp; |
| } |
| |
| }, timestamp, frames); |
| |
| // If received first update, no longer waiting. |
| if (currentState === Guacamole.Client.State.WAITING) |
| setState(Guacamole.Client.State.CONNECTED); |
| |
| // Call sync handler if defined |
| if (guac_client.onsync) |
| guac_client.onsync(timestamp, frames); |
| |
| }, |
| |
| "transfer": function(parameters) { |
| |
| var srcL = getLayer(parseInt(parameters[0])); |
| var srcX = parseInt(parameters[1]); |
| var srcY = parseInt(parameters[2]); |
| var srcWidth = parseInt(parameters[3]); |
| var srcHeight = parseInt(parameters[4]); |
| var function_index = parseInt(parameters[5]); |
| var dstL = getLayer(parseInt(parameters[6])); |
| var dstX = parseInt(parameters[7]); |
| var dstY = parseInt(parameters[8]); |
| |
| /* SRC */ |
| if (function_index === 0x3) |
| display.put(srcL, srcX, srcY, srcWidth, srcHeight, |
| dstL, dstX, dstY); |
| |
| /* Anything else that isn't a NO-OP */ |
| else if (function_index !== 0x5) |
| display.transfer(srcL, srcX, srcY, srcWidth, srcHeight, |
| dstL, dstX, dstY, Guacamole.Client.DefaultTransferFunction[function_index]); |
| |
| }, |
| |
| "transform": function(parameters) { |
| |
| var layer = getLayer(parseInt(parameters[0])); |
| var a = parseFloat(parameters[1]); |
| var b = parseFloat(parameters[2]); |
| var c = parseFloat(parameters[3]); |
| var d = parseFloat(parameters[4]); |
| var e = parseFloat(parameters[5]); |
| var f = parseFloat(parameters[6]); |
| |
| display.transform(layer, a, b, c, d, e, f); |
| |
| }, |
| |
| "undefine" : function handleUndefine(parameters) { |
| |
| // Get object |
| var objectIndex = parseInt(parameters[0]); |
| var object = objects[objectIndex]; |
| |
| // Signal end of object definition |
| if (object && object.onundefine) |
| object.onundefine(); |
| |
| }, |
| |
| "video": function(parameters) { |
| |
| var stream_index = parseInt(parameters[0]); |
| var layer = getLayer(parseInt(parameters[1])); |
| var mimetype = parameters[2]; |
| |
| // Create stream |
| var stream = streams[stream_index] = |
| new Guacamole.InputStream(guac_client, stream_index); |
| |
| // Get player instance via callback |
| var videoPlayer = null; |
| if (guac_client.onvideo) |
| videoPlayer = guac_client.onvideo(stream, layer, mimetype); |
| |
| // If unsuccessful, try to use a default implementation |
| if (!videoPlayer) |
| videoPlayer = Guacamole.VideoPlayer.getInstance(stream, layer, mimetype); |
| |
| // If we have successfully retrieved an video player, send success response |
| if (videoPlayer) { |
| videoPlayers[stream_index] = videoPlayer; |
| guac_client.sendAck(stream_index, "OK", 0x0000); |
| } |
| |
| // Otherwise, mimetype must be unsupported |
| else |
| guac_client.sendAck(stream_index, "BAD TYPE", 0x030F); |
| |
| } |
| |
| }; |
| |
| /** |
| * Sends a keep-alive ping to the Guacamole server, advising the server |
| * that the client is still connected and responding. The lastSentKeepAlive |
| * timestamp is automatically updated as a result of calling this function. |
| * |
| * @private |
| */ |
| var sendKeepAlive = function sendKeepAlive() { |
| tunnel.sendMessage('nop'); |
| lastSentKeepAlive = new Date().getTime(); |
| }; |
| |
| /** |
| * Schedules the next keep-alive ping based on the KEEP_ALIVE_FREQUENCY and |
| * the time that the last ping was sent, if ever. If enough time has |
| * elapsed that a ping should have already been sent, calling this function |
| * will send that ping immediately. |
| * |
| * @private |
| */ |
| var scheduleKeepAlive = function scheduleKeepAlive() { |
| |
| window.clearTimeout(keepAliveTimeout); |
| |
| var currentTime = new Date().getTime(); |
| var keepAliveDelay = Math.max(lastSentKeepAlive + KEEP_ALIVE_FREQUENCY - currentTime, 0); |
| |
| // Ping server regularly to keep connection alive, but send the ping |
| // immediately if enough time has elapsed that it should have already |
| // been sent |
| if (keepAliveDelay > 0) |
| keepAliveTimeout = window.setTimeout(sendKeepAlive, keepAliveDelay); |
| else |
| sendKeepAlive(); |
| |
| }; |
| |
| /** |
| * Stops sending any further keep-alive pings. If a keep-alive ping was |
| * scheduled to be sent, that ping is cancelled. |
| * |
| * @private |
| */ |
| var stopKeepAlive = function stopKeepAlive() { |
| window.clearTimeout(keepAliveTimeout); |
| }; |
| |
| tunnel.oninstruction = function(opcode, parameters) { |
| |
| var handler = instructionHandlers[opcode]; |
| if (handler) |
| handler(parameters); |
| |
| // Leverage network activity to ensure the next keep-alive ping is |
| // sent, even if the browser is currently throttling timers |
| scheduleKeepAlive(); |
| |
| }; |
| |
| /** |
| * Sends a disconnect instruction to the server and closes the tunnel. |
| */ |
| this.disconnect = function() { |
| |
| // Only attempt disconnection not disconnected. |
| if (currentState != Guacamole.Client.State.DISCONNECTED |
| && currentState != Guacamole.Client.State.DISCONNECTING) { |
| |
| setState(Guacamole.Client.State.DISCONNECTING); |
| |
| // Stop sending keep-alive messages |
| stopKeepAlive(); |
| |
| // Send disconnect message and disconnect |
| tunnel.sendMessage("disconnect"); |
| tunnel.disconnect(); |
| setState(Guacamole.Client.State.DISCONNECTED); |
| |
| } |
| |
| }; |
| |
| /** |
| * Connects the underlying tunnel of this Guacamole.Client, passing the |
| * given arbitrary data to the tunnel during the connection process. |
| * |
| * @param {string} data |
| * Arbitrary connection data to be sent to the underlying tunnel during |
| * the connection process. |
| * |
| * @throws {!Guacamole.Status} |
| * If an error occurs during connection. |
| */ |
| this.connect = function(data) { |
| |
| setState(Guacamole.Client.State.CONNECTING); |
| |
| try { |
| tunnel.connect(data); |
| } |
| catch (status) { |
| setState(Guacamole.Client.State.IDLE); |
| throw status; |
| } |
| |
| // Regularly send keep-alive ping to ensure the server knows we're |
| // still here, even if not active |
| scheduleKeepAlive(); |
| |
| setState(Guacamole.Client.State.WAITING); |
| }; |
| |
| }; |
| |
| /** |
| * All possible Guacamole Client states. |
| * |
| * @type {!Object.<string, number>} |
| */ |
| Guacamole.Client.State = { |
| |
| /** |
| * The client is idle, with no active connection. |
| * |
| * @type number |
| */ |
| "IDLE" : 0, |
| |
| /** |
| * The client is in the process of establishing a connection. |
| * |
| * @type {!number} |
| */ |
| "CONNECTING" : 1, |
| |
| /** |
| * The client is waiting on further information or a remote server to |
| * establish the connection. |
| * |
| * @type {!number} |
| */ |
| "WAITING" : 2, |
| |
| /** |
| * The client is actively connected to a remote server. |
| * |
| * @type {!number} |
| */ |
| "CONNECTED" : 3, |
| |
| /** |
| * The client is in the process of disconnecting from the remote server. |
| * |
| * @type {!number} |
| */ |
| "DISCONNECTING" : 4, |
| |
| /** |
| * The client has completed the connection and is no longer connected. |
| * |
| * @type {!number} |
| */ |
| "DISCONNECTED" : 5 |
| |
| }; |
| |
| /** |
| * Map of all Guacamole binary raster operations to transfer functions. |
| * |
| * @private |
| * @type {!Object.<number, function>} |
| */ |
| Guacamole.Client.DefaultTransferFunction = { |
| |
| /* BLACK */ |
| 0x0: function (src, dst) { |
| dst.red = dst.green = dst.blue = 0x00; |
| }, |
| |
| /* WHITE */ |
| 0xF: function (src, dst) { |
| dst.red = dst.green = dst.blue = 0xFF; |
| }, |
| |
| /* SRC */ |
| 0x3: function (src, dst) { |
| dst.red = src.red; |
| dst.green = src.green; |
| dst.blue = src.blue; |
| dst.alpha = src.alpha; |
| }, |
| |
| /* DEST (no-op) */ |
| 0x5: function (src, dst) { |
| // Do nothing |
| }, |
| |
| /* Invert SRC */ |
| 0xC: function (src, dst) { |
| dst.red = 0xFF & ~src.red; |
| dst.green = 0xFF & ~src.green; |
| dst.blue = 0xFF & ~src.blue; |
| dst.alpha = src.alpha; |
| }, |
| |
| /* Invert DEST */ |
| 0xA: function (src, dst) { |
| dst.red = 0xFF & ~dst.red; |
| dst.green = 0xFF & ~dst.green; |
| dst.blue = 0xFF & ~dst.blue; |
| }, |
| |
| /* AND */ |
| 0x1: function (src, dst) { |
| dst.red = ( src.red & dst.red); |
| dst.green = ( src.green & dst.green); |
| dst.blue = ( src.blue & dst.blue); |
| }, |
| |
| /* NAND */ |
| 0xE: function (src, dst) { |
| dst.red = 0xFF & ~( src.red & dst.red); |
| dst.green = 0xFF & ~( src.green & dst.green); |
| dst.blue = 0xFF & ~( src.blue & dst.blue); |
| }, |
| |
| /* OR */ |
| 0x7: function (src, dst) { |
| dst.red = ( src.red | dst.red); |
| dst.green = ( src.green | dst.green); |
| dst.blue = ( src.blue | dst.blue); |
| }, |
| |
| /* NOR */ |
| 0x8: function (src, dst) { |
| dst.red = 0xFF & ~( src.red | dst.red); |
| dst.green = 0xFF & ~( src.green | dst.green); |
| dst.blue = 0xFF & ~( src.blue | dst.blue); |
| }, |
| |
| /* XOR */ |
| 0x6: function (src, dst) { |
| dst.red = ( src.red ^ dst.red); |
| dst.green = ( src.green ^ dst.green); |
| dst.blue = ( src.blue ^ dst.blue); |
| }, |
| |
| /* XNOR */ |
| 0x9: function (src, dst) { |
| dst.red = 0xFF & ~( src.red ^ dst.red); |
| dst.green = 0xFF & ~( src.green ^ dst.green); |
| dst.blue = 0xFF & ~( src.blue ^ dst.blue); |
| }, |
| |
| /* AND inverted source */ |
| 0x4: function (src, dst) { |
| dst.red = 0xFF & (~src.red & dst.red); |
| dst.green = 0xFF & (~src.green & dst.green); |
| dst.blue = 0xFF & (~src.blue & dst.blue); |
| }, |
| |
| /* OR inverted source */ |
| 0xD: function (src, dst) { |
| dst.red = 0xFF & (~src.red | dst.red); |
| dst.green = 0xFF & (~src.green | dst.green); |
| dst.blue = 0xFF & (~src.blue | dst.blue); |
| }, |
| |
| /* AND inverted destination */ |
| 0x2: function (src, dst) { |
| dst.red = 0xFF & ( src.red & ~dst.red); |
| dst.green = 0xFF & ( src.green & ~dst.green); |
| dst.blue = 0xFF & ( src.blue & ~dst.blue); |
| }, |
| |
| /* OR inverted destination */ |
| 0xB: function (src, dst) { |
| dst.red = 0xFF & ( src.red | ~dst.red); |
| dst.green = 0xFF & ( src.green | ~dst.green); |
| dst.blue = 0xFF & ( src.blue | ~dst.blue); |
| } |
| |
| }; |
| |
| /** |
| * A list of possible messages that can be sent by the server for processing |
| * by the client. |
| * |
| * @type {!Object.<string, number>} |
| */ |
| Guacamole.Client.Message = { |
| |
| /** |
| * A client message that indicates that a user has joined an existing |
| * connection. This message expects a single additional argument - the |
| * name of the user who has joined the connection. |
| * |
| * @type {!number} |
| */ |
| "USER_JOINED": 0x0001, |
| |
| /** |
| * A client message that indicates that a user has left an existing |
| * connection. This message expects a single additional argument - the |
| * name of the user who has left the connection. |
| * |
| * @type {!number} |
| */ |
| "USER_LEFT": 0x0002 |
| |
| }; |
| </code></pre> |
| </article> |
| </section> |
| |
| |
| |
| |
| </div> |
| |
| <nav> |
| <h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">Guacamole.AudioContextFactory</a></li></ul><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">Guacamole.ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">Guacamole.ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">Guacamole.AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">Guacamole.AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">Guacamole.BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">Guacamole.BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">Guacamole.ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Guacamole.Client</a></li><li><a href="Guacamole.DataURIReader.html">Guacamole.DataURIReader</a></li><li><a href="Guacamole.Display.html">Guacamole.Display</a></li><li><a href="Guacamole.Display.Statistics.html">Guacamole.Display.Statistics</a></li><li><a href="Guacamole.Display.VisibleLayer.html">Guacamole.Display.VisibleLayer</a></li><li><a href="Guacamole.Event.html">Guacamole.Event</a></li><li><a href="Guacamole.Event.DOMEvent.html">Guacamole.Event.DOMEvent</a></li><li><a href="Guacamole.Event.Target.html">Guacamole.Event.Target</a></li><li><a href="Guacamole.HTTPTunnel.html">Guacamole.HTTPTunnel</a></li><li><a href="Guacamole.InputSink.html">Guacamole.InputSink</a></li><li><a href="Guacamole.InputStream.html">Guacamole.InputStream</a></li><li><a href="Guacamole.IntegerPool.html">Guacamole.IntegerPool</a></li><li><a href="Guacamole.JSONReader.html">Guacamole.JSONReader</a></li><li><a href="Guacamole.KeyEventInterpreter.html">Guacamole.KeyEventInterpreter</a></li><li><a href="Guacamole.KeyEventInterpreter.KeyDefinition.html">Guacamole.KeyEventInterpreter.KeyDefinition</a></li><li><a href="Guacamole.KeyEventInterpreter.KeyEvent.html">Guacamole.KeyEventInterpreter.KeyEvent</a></li><li><a href="Guacamole.Keyboard.html">Guacamole.Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">Guacamole.Keyboard.ModifierState</a></li><li><a href="Guacamole.Layer.html">Guacamole.Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Guacamole.Layer.Pixel</a></li><li><a href="Guacamole.Mouse.html">Guacamole.Mouse</a></li><li><a href="Guacamole.Mouse.Event.html">Guacamole.Mouse.Event</a></li><li><a href="Guacamole.Mouse.Event.Target.html">Guacamole.Mouse.Event.Target</a></li><li><a href="Guacamole.Mouse.State.html">Guacamole.Mouse.State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Guacamole.Mouse.Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Guacamole.Mouse.Touchscreen</a></li><li><a href="Guacamole.Object.html">Guacamole.Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">Guacamole.OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Guacamole.OnScreenKeyboard.Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Guacamole.OnScreenKeyboard.Layout</a></li><li><a href="Guacamole.OutputStream.html">Guacamole.OutputStream</a></li><li><a href="Guacamole.Parser.html">Guacamole.Parser</a></li><li><a href="Guacamole.Position.html">Guacamole.Position</a></li><li><a href="Guacamole.RawAudioFormat.html">Guacamole.RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html">Guacamole.RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioRecorder.html">Guacamole.RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">Guacamole.SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">Guacamole.StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Guacamole.Status</a></li><li><a href="Guacamole.StringReader.html">Guacamole.StringReader</a></li><li><a href="Guacamole.StringWriter.html">Guacamole.StringWriter</a></li><li><a href="Guacamole.Touch.html">Guacamole.Touch</a></li><li><a href="Guacamole.Touch.Event.html">Guacamole.Touch.Event</a></li><li><a href="Guacamole.Touch.State.html">Guacamole.Touch.State</a></li><li><a href="Guacamole.Tunnel.html">Guacamole.Tunnel</a></li><li><a href="Guacamole.UTF8Parser.html">Guacamole.UTF8Parser</a></li><li><a href="Guacamole.VideoPlayer.html">Guacamole.VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">Guacamole.WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">Guacamole.ArrayBufferReader#ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">Guacamole.ArrayBufferReader#onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">Guacamole.ArrayBufferWriter#onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">Guacamole.AudioRecorder#onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">Guacamole.AudioRecorder#onerror</a></li><li><a href="Guacamole.BlobReader.html#event:onend">Guacamole.BlobReader#onend</a></li><li><a href="Guacamole.BlobReader.html#event:onprogress">Guacamole.BlobReader#onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">Guacamole.BlobWriter#onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">Guacamole.BlobWriter#oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">Guacamole.BlobWriter#onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">Guacamole.BlobWriter#onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">Guacamole.ChainedTunnel#onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">Guacamole.ChainedTunnel#oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">Guacamole.ChainedTunnel#onstatechange</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onuuid">Guacamole.ChainedTunnel#onuuid</a></li><li><a href="Guacamole.Client.html#event:onargv">Guacamole.Client#onargv</a></li><li><a href="Guacamole.Client.html#event:onaudio">Guacamole.Client#onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">Guacamole.Client#onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">Guacamole.Client#onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">Guacamole.Client#onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">Guacamole.Client#onfilesystem</a></li><li><a href="Guacamole.Client.html#event:onjoin">Guacamole.Client#onjoin</a></li><li><a href="Guacamole.Client.html#event:onleave">Guacamole.Client#onleave</a></li><li><a href="Guacamole.Client.html#event:onmsg">Guacamole.Client#onmsg</a></li><li><a href="Guacamole.Client.html#event:onmultitouch">Guacamole.Client#onmultitouch</a></li><li><a href="Guacamole.Client.html#event:onname">Guacamole.Client#onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">Guacamole.Client#onpipe</a></li><li><a href="Guacamole.Client.html#event:onrequired">Guacamole.Client#onrequired</a></li><li><a href="Guacamole.Client.html#event:onstatechange">Guacamole.Client#onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">Guacamole.Client#onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">Guacamole.Client#onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">Guacamole.DataURIReader#onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">Guacamole.Display#oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">Guacamole.Display#onresize</a></li><li><a href="Guacamole.Display.html#event:onstatistics">Guacamole.Display#onstatistics</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">Guacamole.HTTPTunnel#onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">Guacamole.HTTPTunnel#oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">Guacamole.HTTPTunnel#onstatechange</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onuuid">Guacamole.HTTPTunnel#onuuid</a></li><li><a href="Guacamole.InputStream.html#event:onblob">Guacamole.InputStream#onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">Guacamole.InputStream#onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">Guacamole.JSONReader#onend</a></li><li><a href="Guacamole.JSONReader.html#event:onprogress">Guacamole.JSONReader#onprogress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">Guacamole.Keyboard#onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">Guacamole.Keyboard#onkeyup</a></li><li><a href="Guacamole.Mouse.html#event:mousedown">Guacamole.Mouse#mousedown</a></li><li><a href="Guacamole.Mouse.html#event:mousemove">Guacamole.Mouse#mousemove</a></li><li><a href="Guacamole.Mouse.html#event:mouseout">Guacamole.Mouse#mouseout</a></li><li><a href="Guacamole.Mouse.html#event:mouseup">Guacamole.Mouse#mouseup</a></li><li><a href="Guacamole.Mouse.Event.Target.html#event:mousedown">Guacamole.Mouse.Event.Target#mousedown</a></li><li><a href="Guacamole.Mouse.Event.Target.html#event:mousemove">Guacamole.Mouse.Event.Target#mousemove</a></li><li><a href="Guacamole.Mouse.Event.Target.html#event:mouseout">Guacamole.Mouse.Event.Target#mouseout</a></li><li><a href="Guacamole.Mouse.Event.Target.html#event:mouseup">Guacamole.Mouse.Event.Target#mouseup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:mousedown">Guacamole.Mouse.Touchpad#mousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:mousemove">Guacamole.Mouse.Touchpad#mousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:mouseup">Guacamole.Mouse.Touchpad#mouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:mousedown">Guacamole.Mouse.Touchscreen#mousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:mousemove">Guacamole.Mouse.Touchscreen#mousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:mouseup">Guacamole.Mouse.Touchscreen#mouseup</a></li><li><a href="Guacamole.Object.html#event:onbody">Guacamole.Object#onbody</a></li><li><a href="Guacamole.Object.html#event:onundefine">Guacamole.Object#onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">Guacamole.OnScreenKeyboard#onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">Guacamole.OnScreenKeyboard#onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">Guacamole.OutputStream#onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">Guacamole.Parser#oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">Guacamole.RawAudioRecorder#onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">Guacamole.RawAudioRecorder#onerror</a></li><li><a href="Guacamole.SessionRecording.html#event:onabort">Guacamole.SessionRecording#onabort</a></li><li><a href="Guacamole.SessionRecording.html#event:onerror">Guacamole.SessionRecording#onerror</a></li><li><a href="Guacamole.SessionRecording.html#event:onkeyevents">Guacamole.SessionRecording#onkeyevents</a></li><li><a href="Guacamole.SessionRecording.html#event:onload">Guacamole.SessionRecording#onload</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">Guacamole.SessionRecording#onpause</a></li><li><a href="Guacamole.SessionRecording.html#event:onplay">Guacamole.SessionRecording#onplay</a></li><li><a href="Guacamole.SessionRecording.html#event:onprogress">Guacamole.SessionRecording#onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">Guacamole.SessionRecording#onseek</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">Guacamole.SessionRecording._PlaybackTunnel#onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">Guacamole.SessionRecording._PlaybackTunnel#oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">Guacamole.SessionRecording._PlaybackTunnel#onstatechange</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onuuid">Guacamole.SessionRecording._PlaybackTunnel#onuuid</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">Guacamole.StaticHTTPTunnel#onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">Guacamole.StaticHTTPTunnel#oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">Guacamole.StaticHTTPTunnel#onstatechange</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onuuid">Guacamole.StaticHTTPTunnel#onuuid</a></li><li><a href="Guacamole.StringReader.html#event:onend">Guacamole.StringReader#onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">Guacamole.StringReader#ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">Guacamole.StringWriter#onack</a></li><li><a href="Guacamole.Touch.html#event:touchend">Guacamole.Touch#touchend</a></li><li><a href="Guacamole.Touch.html#event:touchmove">Guacamole.Touch#touchmove</a></li><li><a href="Guacamole.Touch.html#event:touchstart">Guacamole.Touch#touchstart</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">Guacamole.Tunnel#onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">Guacamole.Tunnel#oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">Guacamole.Tunnel#onstatechange</a></li><li><a href="Guacamole.Tunnel.html#event:onuuid">Guacamole.Tunnel#onuuid</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">Guacamole.WebSocketTunnel#onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:oninstruction">Guacamole.WebSocketTunnel#oninstruction</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onstatechange">Guacamole.WebSocketTunnel#onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onuuid">Guacamole.WebSocketTunnel#onuuid</a></li></ul> |
| </nav> |
| |
| <br class="clear"> |
| |
| <footer> |
| Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a> on Mon Jun 16 2025 15:53:22 GMT-0700 (Pacific Daylight Time) |
| </footer> |
| |
| <script> prettyPrint(); </script> |
| <script src="scripts/linenumber.js"> </script> |
| </body> |
| </html> |