guacamole-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jmuehl...@apache.org
Subject [32/64] [partial] incubator-guacamole-website git commit: Add documentation for 0.9.13-incubating.
Date Thu, 06 Jul 2017 02:19:59 GMT
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-website/blob/fd9055f3/doc/0.9.13-incubating/guacamole-common-js/Layer.js.html
----------------------------------------------------------------------
diff --git a/doc/0.9.13-incubating/guacamole-common-js/Layer.js.html b/doc/0.9.13-incubating/guacamole-common-js/Layer.js.html
new file mode 100644
index 0000000..799cc7d
--- /dev/null
+++ b/doc/0.9.13-incubating/guacamole-common-js/Layer.js.html
@@ -0,0 +1,1029 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>JSDoc: Source: Layer.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: Layer.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 || {};
+
+/**
+ * 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 number of pixels the width or height of a layer must change before
+     * the underlying canvas is resized. The underlying canvas will be kept at
+     * dimensions which are integer multiples of this factor.
+     *
+     * @private
+     * @constant
+     * @type Number
+     */
+    var CANVAS_SIZE_FACTOR = 64;
+
+    /**
+     * The canvas element backing this Layer.
+     * @private
+     */
+    var canvas = document.createElement("canvas");
+
+    /**
+     * The 2D display context of the canvas element backing this Layer.
+     * @private
+     */
+    var context = canvas.getContext("2d");
+    context.save();
+
+    /**
+     * Whether the layer has not yet been drawn to. Once any draw operation
+     * which affects the underlying canvas is invoked, this flag will be set to
+     * false.
+     *
+     * @private
+     * @type Boolean
+     */
+    var empty = true;
+
+    /**
+     * Whether a new path should be started with the next path drawing
+     * operations.
+     * @private
+     */
+    var pathClosed = true;
+
+    /**
+     * The number of states on the state stack.
+     * 
+     * Note that there will ALWAYS be one element on the stack, but that
+     * element is not exposed. It is only used to reset the layer to its
+     * initial state.
+     * 
+     * @private
+     */
+    var stackSize = 0;
+
+    /**
+     * 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. This function should only
+     * be used internally.
+     * 
+     * @private
+     * @param {Number} [newWidth=0]
+     *     The new width to assign to this Layer.
+     *
+     * @param {Number} [newHeight=0]
+     *     The new height to assign to this Layer.
+     */
+    var resize = function resize(newWidth, newHeight) {
+
+        // Default size to zero
+        newWidth = newWidth || 0;
+        newHeight = newHeight || 0;
+
+        // Calculate new dimensions of internal canvas
+        var canvasWidth  = Math.ceil(newWidth  / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR;
+        var canvasHeight = Math.ceil(newHeight / CANVAS_SIZE_FACTOR) * CANVAS_SIZE_FACTOR;
+
+        // Resize only if canvas dimensions are actually changing
+        if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
+
+            // Copy old data only if relevant and non-empty
+            var oldData = null;
+            if (!empty &amp;&amp; canvas.width !== 0 &amp;&amp; canvas.height !== 0) {
+
+                // Create canvas and context for holding old data
+                oldData = document.createElement("canvas");
+                oldData.width = Math.min(layer.width, newWidth);
+                oldData.height = Math.min(layer.height, newHeight);
+
+                var oldDataContext = oldData.getContext("2d");
+
+                // Copy image data from current
+                oldDataContext.drawImage(canvas,
+                        0, 0, oldData.width, oldData.height,
+                        0, 0, oldData.width, oldData.height);
+
+            }
+
+            // Preserve composite operation
+            var oldCompositeOperation = context.globalCompositeOperation;
+
+            // Resize canvas
+            canvas.width = canvasWidth;
+            canvas.height = canvasHeight;
+
+            // Redraw old data, if any
+            if (oldData)
+                context.drawImage(oldData,
+                    0, 0, oldData.width, oldData.height,
+                    0, 0, oldData.width, oldData.height);
+
+            // Restore composite operation
+            context.globalCompositeOperation = oldCompositeOperation;
+
+            // Acknowledge reset of stack (happens on resize of canvas)
+            stackSize = 0;
+            context.save();
+
+        }
+
+        // If the canvas size is not changing, manually force state reset
+        else
+            layer.reset();
+
+        // Assign new layer dimensions
+        layer.width = newWidth;
+        layer.height = newHeight;
+
+    };
+
+    /**
+     * Given the X and Y coordinates of the upper-left corner of a rectangle
+     * and the rectangle's width and height, resize the backing canvas element
+     * as necessary to ensure that the rectangle fits within the canvas
+     * element's coordinate space. This function will only make the canvas
+     * larger. If the rectangle already fits within the canvas element's
+     * coordinate space, the canvas is left unchanged.
+     * 
+     * @private
+     * @param {Number} x The X coordinate of the upper-left corner of the
+     *                   rectangle to fit.
+     * @param {Number} y The Y coordinate of the upper-left corner of the
+     *                   rectangle to fit.
+     * @param {Number} w The width of the the rectangle to fit.
+     * @param {Number} h The height of the the rectangle to fit.
+     */
+    function fitRect(x, y, w, h) {
+        
+        // Calculate bounds
+        var opBoundX = w + x;
+        var opBoundY = h + y;
+        
+        // Determine max width
+        var resizeWidth;
+        if (opBoundX > layer.width)
+            resizeWidth = opBoundX;
+        else
+            resizeWidth = layer.width;
+
+        // Determine max height
+        var resizeHeight;
+        if (opBoundY > layer.height)
+            resizeHeight = opBoundY;
+        else
+            resizeHeight = layer.height;
+
+        // Resize if necessary
+        layer.resize(resizeWidth, resizeHeight);
+
+    }
+
+    /**
+     * 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;
+
+    /**
+     * The current width of this layer.
+     * @type {Number}
+     */
+    this.width = width;
+
+    /**
+     * The current height of this layer.
+     * @type {Number}
+     */
+    this.height = height;
+
+    /**
+     * Returns the canvas element backing this Layer. Note that the dimensions
+     * of the canvas may not exactly match those of the Layer, as resizing a
+     * canvas while maintaining its state is an expensive operation.
+     *
+     * @returns {HTMLCanvasElement}
+     *     The canvas element backing this Layer.
+     */
+    this.getCanvas = function getCanvas() {
+        return canvas;
+    };
+
+    /**
+     * Returns a new canvas element containing the same image as this Layer.
+     * Unlike getCanvas(), the canvas element returned is guaranteed to have
+     * the exact same dimensions as the Layer.
+     *
+     * @returns {HTMLCanvasElement}
+     *     A new canvas element containing a copy of the image content this
+     *     Layer.
+     */
+    this.toCanvas = function toCanvas() {
+
+        // Create new canvas having same dimensions
+        var canvas = document.createElement('canvas');
+        canvas.width = layer.width;
+        canvas.height = layer.height;
+
+        // Copy image contents to new canvas
+        var context = canvas.getContext('2d');
+        context.drawImage(layer.getCanvas(), 0, 0);
+
+        return canvas;
+
+    };
+
+    /**
+     * Changes the size of this Layer to the given width and height. Resizing
+     * is only attempted if the new size provided is actually different from
+     * the current size.
+     * 
+     * @param {Number} newWidth The new width to assign to this Layer.
+     * @param {Number} newHeight The new height to assign to this Layer.
+     */
+    this.resize = function(newWidth, newHeight) {
+        if (newWidth !== layer.width || newHeight !== layer.height)
+            resize(newWidth, newHeight);
+    };
+
+    /**
+     * Draws the specified image at the given coordinates. The image specified
+     * must already be loaded.
+     * 
+     * @param {Number} x The destination X coordinate.
+     * @param {Number} y The destination Y coordinate.
+     * @param {Image} image The image to draw. Note that this is an Image
+     *                      object - not a URL.
+     */
+    this.drawImage = function(x, y, image) {
+        if (layer.autosize) fitRect(x, y, image.width, image.height);
+        context.drawImage(image, x, y);
+        empty = false;
+    };
+
+    /**
+     * Transfer a rectangle of image data from one Layer to this Layer using the
+     * specified transfer function.
+     * 
+     * @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.
+     * @param {Function} transferFunction The transfer function to use to
+     *                                    transfer data from source to
+     *                                    destination.
+     */
+    this.transfer = function(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction) {
+
+        var srcCanvas = srcLayer.getCanvas();
+
+        // If entire rectangle outside source canvas, stop
+        if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
+
+        // Otherwise, clip rectangle to area
+        if (srcx + srcw > srcCanvas.width)
+            srcw = srcCanvas.width - srcx;
+
+        if (srcy + srch > srcCanvas.height)
+            srch = srcCanvas.height - srcy;
+
+        // Stop if nothing to draw.
+        if (srcw === 0 || srch === 0) return;
+
+        if (layer.autosize) fitRect(x, y, srcw, srch);
+
+        // Get image data from src and dst
+        var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
+        var dst = context.getImageData(x , y, srcw, srch);
+
+        // Apply transfer for each pixel
+        for (var i=0; i&lt;srcw*srch*4; i+=4) {
+
+            // Get source pixel environment
+            var src_pixel = new Guacamole.Layer.Pixel(
+                src.data[i],
+                src.data[i+1],
+                src.data[i+2],
+                src.data[i+3]
+            );
+                
+            // Get destination pixel environment
+            var dst_pixel = new Guacamole.Layer.Pixel(
+                dst.data[i],
+                dst.data[i+1],
+                dst.data[i+2],
+                dst.data[i+3]
+            );
+
+            // Apply transfer function
+            transferFunction(src_pixel, dst_pixel);
+
+            // Save pixel data
+            dst.data[i  ] = dst_pixel.red;
+            dst.data[i+1] = dst_pixel.green;
+            dst.data[i+2] = dst_pixel.blue;
+            dst.data[i+3] = dst_pixel.alpha;
+
+        }
+
+        // Draw image data
+        context.putImageData(dst, x, y);
+        empty = false;
+
+    };
+
+    /**
+     * Put a rectangle of image data from one Layer to this Layer directly
+     * without performing any alpha blending. Simply copy the data.
+     * 
+     * @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.put = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
+
+        var srcCanvas = srcLayer.getCanvas();
+
+        // If entire rectangle outside source canvas, stop
+        if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
+
+        // Otherwise, clip rectangle to area
+        if (srcx + srcw > srcCanvas.width)
+            srcw = srcCanvas.width - srcx;
+
+        if (srcy + srch > srcCanvas.height)
+            srch = srcCanvas.height - srcy;
+
+        // Stop if nothing to draw.
+        if (srcw === 0 || srch === 0) return;
+
+        if (layer.autosize) fitRect(x, y, srcw, srch);
+
+        // Get image data from src and dst
+        var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch);
+        context.putImageData(src, x, y);
+        empty = false;
+
+    };
+
+    /**
+     * 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.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) {
+
+        var srcCanvas = srcLayer.getCanvas();
+
+        // If entire rectangle outside source canvas, stop
+        if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return;
+
+        // Otherwise, clip rectangle to area
+        if (srcx + srcw > srcCanvas.width)
+            srcw = srcCanvas.width - srcx;
+
+        if (srcy + srch > srcCanvas.height)
+            srch = srcCanvas.height - srcy;
+
+        // Stop if nothing to draw.
+        if (srcw === 0 || srch === 0) return;
+
+        if (layer.autosize) fitRect(x, y, srcw, srch);
+        context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch);
+        empty = false;
+
+    };
+
+    /**
+     * Starts a new path at the specified point.
+     * 
+     * @param {Number} x The X coordinate of the point to draw.
+     * @param {Number} y The Y coordinate of the point to draw.
+     */
+    this.moveTo = function(x, y) {
+        
+        // Start a new path if current path is closed
+        if (pathClosed) {
+            context.beginPath();
+            pathClosed = false;
+        }
+        
+        if (layer.autosize) fitRect(x, y, 0, 0);
+        context.moveTo(x, y);
+
+    };
+
+    /**
+     * Add the specified line to the current path.
+     * 
+     * @param {Number} x The X coordinate of the endpoint of the line to draw.
+     * @param {Number} y The Y coordinate of the endpoint of the line to draw.
+     */
+    this.lineTo = function(x, y) {
+        
+        // Start a new path if current path is closed
+        if (pathClosed) {
+            context.beginPath();
+            pathClosed = false;
+        }
+        
+        if (layer.autosize) fitRect(x, y, 0, 0);
+        context.lineTo(x, y);
+        
+    };
+
+    /**
+     * Add the specified arc to the current path.
+     * 
+     * @param {Number} x The X coordinate of the center of the circle which
+     *                   will contain the arc.
+     * @param {Number} y The Y coordinate of the center of the circle which
+     *                   will contain the arc.
+     * @param {Number} radius The radius of the circle.
+     * @param {Number} startAngle The starting angle of the arc, in radians.
+     * @param {Number} endAngle The ending angle of the arc, in radians.
+     * @param {Boolean} negative Whether the arc should be drawn in order of
+     *                           decreasing angle.
+     */
+    this.arc = function(x, y, radius, startAngle, endAngle, negative) {
+        
+        // Start a new path if current path is closed
+        if (pathClosed) {
+            context.beginPath();
+            pathClosed = false;
+        }
+        
+        if (layer.autosize) fitRect(x, y, 0, 0);
+        context.arc(x, y, radius, startAngle, endAngle, negative);
+        
+    };
+
+    /**
+     * Starts a new path at the specified point.
+     * 
+     * @param {Number} cp1x The X coordinate of the first control point.
+     * @param {Number} cp1y The Y coordinate of the first control point.
+     * @param {Number} cp2x The X coordinate of the second control point.
+     * @param {Number} cp2y The Y coordinate of the second control point.
+     * @param {Number} x The X coordinate of the endpoint of the curve.
+     * @param {Number} y The Y coordinate of the endpoint of the curve.
+     */
+    this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+        
+        // Start a new path if current path is closed
+        if (pathClosed) {
+            context.beginPath();
+            pathClosed = false;
+        }
+        
+        if (layer.autosize) fitRect(x, y, 0, 0);
+        context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
+        
+    };
+
+    /**
+     * Closes the current path by connecting the end point with the start
+     * point (if any) with a straight line.
+     */
+    this.close = function() {
+        context.closePath();
+        pathClosed = true;
+    };
+
+    /**
+     * Add the specified rectangle to the current path.
+     * 
+     * @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.
+     */
+    this.rect = function(x, y, w, h) {
+            
+        // Start a new path if current path is closed
+        if (pathClosed) {
+            context.beginPath();
+            pathClosed = false;
+        }
+        
+        if (layer.autosize) fitRect(x, y, w, h);
+        context.rect(x, y, w, h);
+        
+    };
+
+    /**
+     * Clip all future drawing operations by the current path. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as fillColor()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     */
+    this.clip = function() {
+
+        // Set new clipping region
+        context.clip();
+
+        // Path now implicitly closed
+        pathClosed = true;
+
+    };
+
+    /**
+     * Stroke the current path with the specified color. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {String} cap The line cap style. Can be "round", "square",
+     *                     or "butt".
+     * @param {String} join The line join style. Can be "round", "bevel",
+     *                      or "miter".
+     * @param {Number} thickness The line thickness in pixels.
+     * @param {Number} r The red component of the color to fill.
+     * @param {Number} g The green component of the color to fill.
+     * @param {Number} b The blue component of the color to fill.
+     * @param {Number} a The alpha component of the color to fill.
+     */
+    this.strokeColor = function(cap, join, thickness, r, g, b, a) {
+
+        // Stroke with color
+        context.lineCap = cap;
+        context.lineJoin = join;
+        context.lineWidth = thickness;
+        context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
+        context.stroke();
+        empty = false;
+
+        // Path now implicitly closed
+        pathClosed = true;
+
+    };
+
+    /**
+     * Fills the current path with the specified color. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {Number} r The red component of the color to fill.
+     * @param {Number} g The green component of the color to fill.
+     * @param {Number} b The blue component of the color to fill.
+     * @param {Number} a The alpha component of the color to fill.
+     */
+    this.fillColor = function(r, g, b, a) {
+
+        // Fill with color
+        context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")";
+        context.fill();
+        empty = false;
+
+        // Path now implicitly closed
+        pathClosed = true;
+
+    };
+
+    /**
+     * Stroke the current path with the image within the specified layer. The
+     * image data will be tiled infinitely within the stroke. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {String} cap The line cap style. Can be "round", "square",
+     *                     or "butt".
+     * @param {String} join The line join style. Can be "round", "bevel",
+     *                      or "miter".
+     * @param {Number} thickness The line thickness in pixels.
+     * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
+     *                                   within the stroke.
+     */
+    this.strokeLayer = function(cap, join, thickness, srcLayer) {
+
+        // Stroke with image data
+        context.lineCap = cap;
+        context.lineJoin = join;
+        context.lineWidth = thickness;
+        context.strokeStyle = context.createPattern(
+            srcLayer.getCanvas(),
+            "repeat"
+        );
+        context.stroke();
+        empty = false;
+
+        // Path now implicitly closed
+        pathClosed = true;
+
+    };
+
+    /**
+     * Fills the current path with the image within the specified layer. The
+     * image data will be tiled infinitely within the stroke. The current path
+     * is implicitly closed. The current path can continue to be reused
+     * for other operations (such as clip()) but a new path will be started
+     * once a path drawing operation (path() or rect()) is used.
+     * 
+     * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
+     *                                   within the fill.
+     */
+    this.fillLayer = function(srcLayer) {
+
+        // Fill with image data 
+        context.fillStyle = context.createPattern(
+            srcLayer.getCanvas(),
+            "repeat"
+        );
+        context.fill();
+        empty = false;
+
+        // Path now implicitly closed
+        pathClosed = true;
+
+    };
+
+    /**
+     * Push current layer state onto stack.
+     */
+    this.push = function() {
+
+        // Save current state onto stack
+        context.save();
+        stackSize++;
+
+    };
+
+    /**
+     * Pop layer state off stack.
+     */
+    this.pop = function() {
+
+        // Restore current state from stack
+        if (stackSize > 0) {
+            context.restore();
+            stackSize--;
+        }
+
+    };
+
+    /**
+     * Reset the layer, clearing the stack, the current path, and any transform
+     * matrix.
+     */
+    this.reset = function() {
+
+        // Clear stack
+        while (stackSize > 0) {
+            context.restore();
+            stackSize--;
+        }
+
+        // Restore to initial state
+        context.restore();
+        context.save();
+
+        // Clear path
+        context.beginPath();
+        pathClosed = false;
+
+    };
+
+    /**
+     * Sets the given affine transform (defined with six values from the
+     * transform's matrix).
+     * 
+     * @param {Number} a The first value in the affine transform's matrix.
+     * @param {Number} b The second value in the affine transform's matrix.
+     * @param {Number} c The third value in the affine transform's matrix.
+     * @param {Number} d The fourth value in the affine transform's matrix.
+     * @param {Number} e The fifth value in the affine transform's matrix.
+     * @param {Number} f The sixth value in the affine transform's matrix.
+     */
+    this.setTransform = function(a, b, c, d, e, f) {
+        context.setTransform(
+            a, b, c,
+            d, e, f
+          /*0, 0, 1*/
+        );
+    };
+
+    /**
+     * Applies the given affine transform (defined with six values from the
+     * transform's matrix).
+     * 
+     * @param {Number} a The first value in the affine transform's matrix.
+     * @param {Number} b The second value in the affine transform's matrix.
+     * @param {Number} c The third value in the affine transform's matrix.
+     * @param {Number} d The fourth value in the affine transform's matrix.
+     * @param {Number} e The fifth value in the affine transform's matrix.
+     * @param {Number} f The sixth value in the affine transform's matrix.
+     */
+    this.transform = function(a, b, c, d, e, f) {
+        context.transform(
+            a, b, c,
+            d, e, f
+          /*0, 0, 1*/
+        );
+    };
+
+    /**
+     * Sets the channel mask for future operations on this Layer.
+     * 
+     * The channel mask is a Guacamole-specific compositing operation identifier
+     * with a single bit representing each of four channels (in order): source
+     * image where destination transparent, source where destination opaque,
+     * destination where source transparent, and destination where source
+     * opaque.
+     * 
+     * @param {Number} mask The channel mask for future operations on this
+     *                      Layer.
+     */
+    this.setChannelMask = function(mask) {
+        context.globalCompositeOperation = compositeOperation[mask];
+    };
+
+    /**
+     * Sets the miter limit for stroke operations using the miter join. This
+     * limit is the maximum ratio of the size of the miter join to the stroke
+     * width. If this ratio is exceeded, the miter will not be drawn for that
+     * joint of the path.
+     * 
+     * @param {Number} limit The miter limit for stroke operations using the
+     *                       miter join.
+     */
+    this.setMiterLimit = function(limit) {
+        context.miterLimit = limit;
+    };
+
+    // Initialize canvas dimensions
+    resize(width, height);
+
+    // Explicitly render canvas below other elements in the layer (such as
+    // child layers). Chrome and others may fail to render layers properly
+    // without this.
+    canvas.style.zIndex = -1;
+
+};
+
+/**
+ * Channel mask for the composite operation "rout".
+ */
+Guacamole.Layer.ROUT  = 0x2;
+
+/**
+ * Channel mask for the composite operation "atop".
+ */
+Guacamole.Layer.ATOP  = 0x6;
+
+/**
+ * Channel mask for the composite operation "xor".
+ */
+Guacamole.Layer.XOR   = 0xA;
+
+/**
+ * Channel mask for the composite operation "rover".
+ */
+Guacamole.Layer.ROVER = 0xB;
+
+/**
+ * Channel mask for the composite operation "over".
+ */
+Guacamole.Layer.OVER  = 0xE;
+
+/**
+ * Channel mask for the composite operation "plus".
+ */
+Guacamole.Layer.PLUS  = 0xF;
+
+/**
+ * Channel mask for the composite operation "rin".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.RIN   = 0x1;
+
+/**
+ * Channel mask for the composite operation "in".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.IN    = 0x4;
+
+/**
+ * Channel mask for the composite operation "out".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.OUT   = 0x8;
+
+/**
+ * Channel mask for the composite operation "ratop".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.RATOP = 0x9;
+
+/**
+ * Channel mask for the composite operation "src".
+ * Beware that WebKit-based browsers may leave the contents of the destionation
+ * layer where the source layer is transparent, despite the definition of this
+ * operation.
+ */
+Guacamole.Layer.SRC   = 0xC;
+
+/**
+ * Represents a single pixel of image data. All components have a minimum value
+ * of 0 and a maximum value of 255.
+ * 
+ * @constructor
+ * 
+ * @param {Number} r The red component of this pixel.
+ * @param {Number} g The green component of this pixel.
+ * @param {Number} b The blue component of this pixel.
+ * @param {Number} a The alpha component of this pixel.
+ */
+Guacamole.Layer.Pixel = function(r, g, b, a) {
+
+    /**
+     * The red component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.red   = r;
+
+    /**
+     * The green component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.green = g;
+
+    /**
+     * The blue component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.blue  = b;
+
+    /**
+     * The alpha component of this pixel, where 0 is the minimum value,
+     * and 255 is the maximum.
+     */
+    this.alpha = a;
+
+};
+</code></pre>
+        </article>
+    </section>
+
+
+
+
+</div>
+
+<nav>
+    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Client</a></li><li><a href="Guacamole.DataURIReader.html">DataURIReader</a></li><li><a href="Guacamole.Display.html">Display</a></li><li><a href="Guacamole.Display.VisibleLayer.html">VisibleLayer</a></li><li><a href="Guacamole.HTTPTunnel.html">HTTPTunnel</a></li><li><a href="Guacamole.InputStream.html">InputStream</a></li><li><a href="Guacamole.IntegerPool.html">IntegerPool</a></li><li><a href="Guacamole.JSONReader.html">JSONReader</a></li>
 <li><a href="Guacamole.Keyboard.html">Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">ModifierState</a></li><li><a href="Guacamole.Layer.html">Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Pixel</a></li><li><a href="Guacamole.Mouse.html">Mouse</a></li><li><a href="Guacamole.Mouse.State.html">State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Touchscreen</a></li><li><a href="Guacamole.Object.html">Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Layout</a></li><li><a href="Guacamole.OutputStream.html">OutputStream</a></li><li><a href="Guacamole.Parser.html">Parser</a></li><li><a href="Guacamole.RawAudioFormat.html">RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html">RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioR
 ecorder.html">RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Status</a></li><li><a href="Guacamole.StringReader.html">StringReader</a></li><li><a href="Guacamole.StringWriter.html">StringWriter</a></li><li><a href="Guacamole.Tunnel.html">Tunnel</a></li><li><a href="Guacamole.VideoPlayer.html">VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobReader.html#event:onend">onend</a></li><
 li><a href="Guacamole.BlobReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onaudio">onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">onfilesystem</a></li><li><a href="Guacamole.Client.html#event:onna
 me">onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">onpipe</a></li><li><a href="Guacamole.Client.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">onresize</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.InputStream.html#event:onblob">onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onprogress">onprog
 ress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.html#event:onmouseout">onmouseout</a></li><li><a href="Guacamole.Mouse.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Object.html#event:onbody">onbody</a></li><
 li><a href="Guacamole.Object.html#event:onundefine">onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">onpause</a></li><li><a href="Guacamole.SessionRecording.html#event:onplay">onplay</a></li><li><a href="Guacamole
 .SessionRecording.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">onseek</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.StringReader.html#event:onend">onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">onack</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamol
 e.WebSocketTunnel.html#event:onstatechange">onstatechange</a></li></ul><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">AudioContextFactory</a></li></ul>
+</nav>
+
+<br class="clear">
+
+<footer>
+    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Sun Jul 02 2017 12:09:53 GMT-0700 (PDT)
+</footer>
+
+<script> prettyPrint(); </script>
+<script src="scripts/linenumber.js"> </script>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-website/blob/fd9055f3/doc/0.9.13-incubating/guacamole-common-js/Mouse.js.html
----------------------------------------------------------------------
diff --git a/doc/0.9.13-incubating/guacamole-common-js/Mouse.js.html b/doc/0.9.13-incubating/guacamole-common-js/Mouse.js.html
new file mode 100644
index 0000000..6409c04
--- /dev/null
+++ b/doc/0.9.13-incubating/guacamole-common-js/Mouse.js.html
@@ -0,0 +1,1138 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>JSDoc: Source: Mouse.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: Mouse.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 || {};
+
+/**
+ * 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.
+ * 
+ * @constructor
+ * @param {Element} element The Element to use to provide mouse events.
+ */
+Guacamole.Mouse = function(element) {
+
+    /**
+     * Reference to this Guacamole.Mouse.
+     * @private
+     */
+    var guac_mouse = this;
+
+    /**
+     * The number of mousemove events to require before re-enabling mouse
+     * event handling after receiving a touch event.
+     */
+    this.touchMouseThreshold = 3;
+
+    /**
+     * The minimum amount of pixels scrolled required for a single scroll button
+     * click.
+     */
+    this.scrollThreshold = 53;
+
+    /**
+     * The number of pixels to scroll per line.
+     */
+    this.PIXELS_PER_LINE = 18;
+
+    /**
+     * The number of pixels to scroll per page.
+     */
+    this.PIXELS_PER_PAGE = this.PIXELS_PER_LINE * 16;
+
+    /**
+     * 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
+    );
+
+    /**
+     * 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;
+
+    /**
+     * Fired whenever the user releases a mouse button down over the element
+     * associated with this Guacamole.Mouse.
+     * 
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmouseup = null;
+
+    /**
+     * Fired whenever the user moves the mouse over the element associated with
+     * this Guacamole.Mouse.
+     * 
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmousemove = null;
+
+    /**
+     * Fired whenever the mouse leaves the boundaries of the element associated
+     * with this Guacamole.Mouse.
+     * 
+     * @event
+     */
+	this.onmouseout = null;
+
+    /**
+     * Counter of mouse events to ignore. This decremented by mousemove, and
+     * while non-zero, mouse events will have no effect.
+     * @private
+     */
+    var ignore_mouse = 0;
+
+    /**
+     * Cumulative scroll delta amount. This value is accumulated through scroll
+     * events and results in scroll button clicks if it exceeds a certain
+     * threshold.
+     *
+     * @private
+     */
+    var scroll_delta = 0;
+
+    function cancelEvent(e) {
+        e.stopPropagation();
+        if (e.preventDefault) e.preventDefault();
+        e.returnValue = false;
+    }
+
+    // Block context menu so right-click gets sent properly
+    element.addEventListener("contextmenu", function(e) {
+        cancelEvent(e);
+    }, false);
+
+    element.addEventListener("mousemove", function(e) {
+
+        cancelEvent(e);
+
+        // If ignoring events, decrement counter
+        if (ignore_mouse) {
+            ignore_mouse--;
+            return;
+        }
+
+        guac_mouse.currentState.fromClientPosition(element, e.clientX, e.clientY);
+
+        if (guac_mouse.onmousemove)
+            guac_mouse.onmousemove(guac_mouse.currentState);
+
+    }, false);
+
+    element.addEventListener("mousedown", function(e) {
+
+        cancelEvent(e);
+
+        // Do not handle if ignoring events
+        if (ignore_mouse)
+            return;
+
+        switch (e.button) {
+            case 0:
+                guac_mouse.currentState.left = true;
+                break;
+            case 1:
+                guac_mouse.currentState.middle = true;
+                break;
+            case 2:
+                guac_mouse.currentState.right = true;
+                break;
+        }
+
+        if (guac_mouse.onmousedown)
+            guac_mouse.onmousedown(guac_mouse.currentState);
+
+    }, false);
+
+    element.addEventListener("mouseup", function(e) {
+
+        cancelEvent(e);
+
+        // Do not handle if ignoring events
+        if (ignore_mouse)
+            return;
+
+        switch (e.button) {
+            case 0:
+                guac_mouse.currentState.left = false;
+                break;
+            case 1:
+                guac_mouse.currentState.middle = false;
+                break;
+            case 2:
+                guac_mouse.currentState.right = false;
+                break;
+        }
+
+        if (guac_mouse.onmouseup)
+            guac_mouse.onmouseup(guac_mouse.currentState);
+
+    }, false);
+
+    element.addEventListener("mouseout", function(e) {
+
+        // Get parent of the element the mouse pointer is leaving
+       	if (!e) e = window.event;
+
+        // Check that mouseout is due to actually LEAVING the element
+        var target = e.relatedTarget || e.toElement;
+        while (target) {
+            if (target === element)
+                return;
+            target = target.parentNode;
+        }
+
+        cancelEvent(e);
+
+        // Release all buttons
+        if (guac_mouse.currentState.left
+            || guac_mouse.currentState.middle
+            || guac_mouse.currentState.right) {
+
+            guac_mouse.currentState.left = false;
+            guac_mouse.currentState.middle = false;
+            guac_mouse.currentState.right = false;
+
+            if (guac_mouse.onmouseup)
+                guac_mouse.onmouseup(guac_mouse.currentState);
+        }
+
+        // Fire onmouseout event
+        if (guac_mouse.onmouseout)
+            guac_mouse.onmouseout();
+
+    }, false);
+
+    // Override selection on mouse event element.
+    element.addEventListener("selectstart", function(e) {
+        cancelEvent(e);
+    }, false);
+
+    // Ignore all pending mouse events when touch events are the apparent source
+    function ignorePendingMouseEvents() { ignore_mouse = guac_mouse.touchMouseThreshold; }
+
+    element.addEventListener("touchmove",  ignorePendingMouseEvents, false);
+    element.addEventListener("touchstart", ignorePendingMouseEvents, false);
+    element.addEventListener("touchend",   ignorePendingMouseEvents, false);
+
+    // Scroll wheel support
+    function mousewheel_handler(e) {
+
+        // Determine approximate scroll amount (in pixels)
+        var delta = e.deltaY || -e.wheelDeltaY || -e.wheelDelta;
+
+        // If successfully retrieved scroll amount, convert to pixels if not
+        // already in pixels
+        if (delta) {
+
+            // Convert to pixels if delta was lines
+            if (e.deltaMode === 1)
+                delta = e.deltaY * guac_mouse.PIXELS_PER_LINE;
+
+            // Convert to pixels if delta was pages
+            else if (e.deltaMode === 2)
+                delta = e.deltaY * guac_mouse.PIXELS_PER_PAGE;
+
+        }
+
+        // Otherwise, assume legacy mousewheel event and line scrolling
+        else
+            delta = e.detail * guac_mouse.PIXELS_PER_LINE;
+        
+        // Update overall delta
+        scroll_delta += delta;
+
+        // Up
+        if (scroll_delta &lt;= -guac_mouse.scrollThreshold) {
+
+            // Repeatedly click the up button until insufficient delta remains
+            do {
+
+                if (guac_mouse.onmousedown) {
+                    guac_mouse.currentState.up = true;
+                    guac_mouse.onmousedown(guac_mouse.currentState);
+                }
+
+                if (guac_mouse.onmouseup) {
+                    guac_mouse.currentState.up = false;
+                    guac_mouse.onmouseup(guac_mouse.currentState);
+                }
+
+                scroll_delta += guac_mouse.scrollThreshold;
+
+            } while (scroll_delta &lt;= -guac_mouse.scrollThreshold);
+
+            // Reset delta
+            scroll_delta = 0;
+
+        }
+
+        // Down
+        if (scroll_delta >= guac_mouse.scrollThreshold) {
+
+            // Repeatedly click the down button until insufficient delta remains
+            do {
+
+                if (guac_mouse.onmousedown) {
+                    guac_mouse.currentState.down = true;
+                    guac_mouse.onmousedown(guac_mouse.currentState);
+                }
+
+                if (guac_mouse.onmouseup) {
+                    guac_mouse.currentState.down = false;
+                    guac_mouse.onmouseup(guac_mouse.currentState);
+                }
+
+                scroll_delta -= guac_mouse.scrollThreshold;
+
+            } while (scroll_delta >= guac_mouse.scrollThreshold);
+
+            // Reset delta
+            scroll_delta = 0;
+
+        }
+
+        cancelEvent(e);
+
+    }
+
+    element.addEventListener('DOMMouseScroll', mousewheel_handler, false);
+    element.addEventListener('mousewheel',     mousewheel_handler, false);
+    element.addEventListener('wheel',          mousewheel_handler, false);
+
+    /**
+     * Whether the browser supports CSS3 cursor styling, including hotspot
+     * coordinates.
+     *
+     * @private
+     * @type {Boolean}
+     */
+    var CSS3_CURSOR_SUPPORTED = (function() {
+
+        var div = document.createElement("div");
+
+        // If no cursor property at all, then no support
+        if (!("cursor" in div.style))
+            return false;
+
+        try {
+            // Apply simple 1x1 PNG
+            div.style.cursor = "url(data:image/png;base64,"
+                             + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB"
+                             + "AQMAAAAl21bKAAAAA1BMVEX///+nxBvI"
+                             + "AAAACklEQVQI12NgAAAAAgAB4iG8MwAA"
+                             + "AABJRU5ErkJggg==) 0 0, auto";
+        }
+        catch (e) {
+            return false;
+        }
+
+        // Verify cursor property is set to URL with hotspot
+        return /\burl\([^()]*\)\s+0\s+0\b/.test(div.style.cursor || "");
+
+    })();
+
+    /**
+     * Changes the local mouse cursor to the given canvas, having the given
+     * hotspot coordinates. This affects styling of the element backing this
+     * Guacamole.Mouse only, and may fail depending on browser support for
+     * setting the mouse cursor.
+     * 
+     * If setting the local cursor is desired, it is up to the implementation
+     * to do something else, such as use the software cursor built into
+     * Guacamole.Display, if the local cursor cannot be set.
+     *
+     * @param {HTMLCanvasElement} canvas The cursor image.
+     * @param {Number} x The X-coordinate of the cursor hotspot.
+     * @param {Number} y The Y-coordinate of the cursor hotspot.
+     * @return {Boolean} true if the cursor was successfully set, false if the
+     *                   cursor could not be set for any reason.
+     */
+    this.setCursor = function(canvas, x, y) {
+
+        // Attempt to set via CSS3 cursor styling
+        if (CSS3_CURSOR_SUPPORTED) {
+            var dataURL = canvas.toDataURL('image/png');
+            element.style.cursor = "url(" + dataURL + ") " + x + " " + y + ", auto";
+            return true;
+        }
+
+        // Otherwise, setting cursor failed
+        return false;
+
+    };
+
+};
+
+/**
+ * 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) {
+
+    /**
+     * Reference to this Guacamole.Mouse.State.
+     * @private
+     */
+    var guac_state = this;
+
+    /**
+     * The current X position of the mouse pointer.
+     * @type {Number}
+     */
+    this.x = x;
+
+    /**
+     * The current Y position of the mouse pointer.
+     * @type {Number}
+     */
+    this.y = y;
+
+    /**
+     * Whether the left mouse button is currently pressed.
+     * @type {Boolean}
+     */
+    this.left = left;
+
+    /**
+     * Whether the middle mouse button is currently pressed.
+     * @type {Boolean}
+     */
+    this.middle = middle;
+
+    /**
+     * Whether the right mouse button is currently pressed.
+     * @type {Boolean}
+     */
+    this.right = right;
+
+    /**
+     * Whether the up mouse button is currently pressed. This is the fourth
+     * mouse button, associated with upward scrolling of the mouse scroll
+     * wheel.
+     * @type {Boolean}
+     */
+    this.up = up;
+
+    /**
+     * Whether the down mouse button is currently pressed. This is the fifth 
+     * mouse button, associated with downward scrolling of the mouse scroll
+     * wheel.
+     * @type {Boolean}
+     */
+    this.down = down;
+
+    /**
+     * Updates the position represented within this state object by the given
+     * element and clientX/clientY coordinates (commonly available within event
+     * objects). Position is translated from clientX/clientY (relative to
+     * viewport) to element-relative coordinates.
+     * 
+     * @param {Element} element The element the coordinates should be relative
+     *                          to.
+     * @param {Number} clientX The X coordinate to translate, viewport-relative.
+     * @param {Number} clientY The Y coordinate to translate, viewport-relative.
+     */
+    this.fromClientPosition = function(element, clientX, clientY) {
+    
+        guac_state.x = clientX - element.offsetLeft;
+        guac_state.y = clientY - element.offsetTop;
+
+        // This is all JUST so we can get the mouse position within the element
+        var parent = element.offsetParent;
+        while (parent &amp;&amp; !(parent === document.body)) {
+            guac_state.x -= parent.offsetLeft - parent.scrollLeft;
+            guac_state.y -= parent.offsetTop  - parent.scrollTop;
+
+            parent = parent.offsetParent;
+        }
+
+        // Element ultimately depends on positioning within document body,
+        // take document scroll into account. 
+        if (parent) {
+            var documentScrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft;
+            var documentScrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+
+            guac_state.x -= parent.offsetLeft - documentScrollLeft;
+            guac_state.y -= parent.offsetTop  - documentScrollTop;
+        }
+
+    };
+
+};
+
+/**
+ * Provides cross-browser relative touch event translation for a given element.
+ * 
+ * Touch events are translated into mouse events as if the touches occurred
+ * on a touchpad (drag to push the mouse pointer, tap to click).
+ * 
+ * @constructor
+ * @param {Element} element The Element to use to provide touch events.
+ */
+Guacamole.Mouse.Touchpad = function(element) {
+
+    /**
+     * Reference to this Guacamole.Mouse.Touchpad.
+     * @private
+     */
+    var guac_touchpad = this;
+
+    /**
+     * The distance a two-finger touch must move per scrollwheel event, in
+     * pixels.
+     */
+    this.scrollThreshold = 20 * (window.devicePixelRatio || 1);
+
+    /**
+     * The maximum number of milliseconds to wait for a touch to end for the
+     * gesture to be considered a click.
+     */
+    this.clickTimingThreshold = 250;
+
+    /**
+     * The maximum number of pixels to allow a touch to move for the gesture to
+     * be considered a click.
+     */
+    this.clickMoveThreshold = 10 * (window.devicePixelRatio || 1);
+
+    /**
+     * 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
+    );
+
+    /**
+     * Fired whenever a mouse button is effectively pressed. This can happen
+     * as part of a "click" gesture initiated by the user by tapping one
+     * or more fingers over the touchpad element, as part of a "scroll"
+     * gesture initiated by dragging two fingers up or down, etc.
+     * 
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmousedown = null;
+
+    /**
+     * Fired whenever a mouse button is effectively released. This can happen
+     * as part of a "click" gesture initiated by the user by tapping one
+     * or more fingers over the touchpad element, as part of a "scroll"
+     * gesture initiated by dragging two fingers up or down, etc.
+     * 
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmouseup = null;
+
+    /**
+     * Fired whenever the user moves the mouse by dragging their finger over
+     * the touchpad element.
+     * 
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmousemove = null;
+
+    var touch_count = 0;
+    var last_touch_x = 0;
+    var last_touch_y = 0;
+    var last_touch_time = 0;
+    var pixels_moved = 0;
+
+    var touch_buttons = {
+        1: "left",
+        2: "right",
+        3: "middle"
+    };
+
+    var gesture_in_progress = false;
+    var click_release_timeout = null;
+
+    element.addEventListener("touchend", function(e) {
+        
+        e.preventDefault();
+            
+        // If we're handling a gesture AND this is the last touch
+        if (gesture_in_progress &amp;&amp; e.touches.length === 0) {
+            
+            var time = new Date().getTime();
+
+            // Get corresponding mouse button
+            var button = touch_buttons[touch_count];
+
+            // If mouse already down, release anad clear timeout
+            if (guac_touchpad.currentState[button]) {
+
+                // Fire button up event
+                guac_touchpad.currentState[button] = false;
+                if (guac_touchpad.onmouseup)
+                    guac_touchpad.onmouseup(guac_touchpad.currentState);
+
+                // Clear timeout, if set
+                if (click_release_timeout) {
+                    window.clearTimeout(click_release_timeout);
+                    click_release_timeout = null;
+                }
+
+            }
+
+            // If single tap detected (based on time and distance)
+            if (time - last_touch_time &lt;= guac_touchpad.clickTimingThreshold
+                    &amp;&amp; pixels_moved &lt; guac_touchpad.clickMoveThreshold) {
+
+                // Fire button down event
+                guac_touchpad.currentState[button] = true;
+                if (guac_touchpad.onmousedown)
+                    guac_touchpad.onmousedown(guac_touchpad.currentState);
+
+                // Delay mouse up - mouse up should be canceled if
+                // touchstart within timeout.
+                click_release_timeout = window.setTimeout(function() {
+                    
+                    // Fire button up event
+                    guac_touchpad.currentState[button] = false;
+                    if (guac_touchpad.onmouseup)
+                        guac_touchpad.onmouseup(guac_touchpad.currentState);
+                    
+                    // Gesture now over
+                    gesture_in_progress = false;
+
+                }, guac_touchpad.clickTimingThreshold);
+
+            }
+
+            // If we're not waiting to see if this is a click, stop gesture
+            if (!click_release_timeout)
+                gesture_in_progress = false;
+
+        }
+
+    }, false);
+
+    element.addEventListener("touchstart", function(e) {
+
+        e.preventDefault();
+
+        // Track number of touches, but no more than three
+        touch_count = Math.min(e.touches.length, 3);
+
+        // Clear timeout, if set
+        if (click_release_timeout) {
+            window.clearTimeout(click_release_timeout);
+            click_release_timeout = null;
+        }
+
+        // Record initial touch location and time for touch movement
+        // and tap gestures
+        if (!gesture_in_progress) {
+
+            // Stop mouse events while touching
+            gesture_in_progress = true;
+
+            // Record touch location and time
+            var starting_touch = e.touches[0];
+            last_touch_x = starting_touch.clientX;
+            last_touch_y = starting_touch.clientY;
+            last_touch_time = new Date().getTime();
+            pixels_moved = 0;
+
+        }
+
+    }, false);
+
+    element.addEventListener("touchmove", function(e) {
+
+        e.preventDefault();
+
+        // Get change in touch location
+        var touch = e.touches[0];
+        var delta_x = touch.clientX - last_touch_x;
+        var delta_y = touch.clientY - last_touch_y;
+
+        // Track pixels moved
+        pixels_moved += Math.abs(delta_x) + Math.abs(delta_y);
+
+        // If only one touch involved, this is mouse move
+        if (touch_count === 1) {
+
+            // Calculate average velocity in Manhatten pixels per millisecond
+            var velocity = pixels_moved / (new Date().getTime() - last_touch_time);
+
+            // Scale mouse movement relative to velocity
+            var scale = 1 + velocity;
+
+            // Update mouse location
+            guac_touchpad.currentState.x += delta_x*scale;
+            guac_touchpad.currentState.y += delta_y*scale;
+
+            // Prevent mouse from leaving screen
+
+            if (guac_touchpad.currentState.x &lt; 0)
+                guac_touchpad.currentState.x = 0;
+            else if (guac_touchpad.currentState.x >= element.offsetWidth)
+                guac_touchpad.currentState.x = element.offsetWidth - 1;
+
+            if (guac_touchpad.currentState.y &lt; 0)
+                guac_touchpad.currentState.y = 0;
+            else if (guac_touchpad.currentState.y >= element.offsetHeight)
+                guac_touchpad.currentState.y = element.offsetHeight - 1;
+
+            // Fire movement event, if defined
+            if (guac_touchpad.onmousemove)
+                guac_touchpad.onmousemove(guac_touchpad.currentState);
+
+            // Update touch location
+            last_touch_x = touch.clientX;
+            last_touch_y = touch.clientY;
+
+        }
+
+        // Interpret two-finger swipe as scrollwheel
+        else if (touch_count === 2) {
+
+            // If change in location passes threshold for scroll
+            if (Math.abs(delta_y) >= guac_touchpad.scrollThreshold) {
+
+                // Decide button based on Y movement direction
+                var button;
+                if (delta_y > 0) button = "down";
+                else             button = "up";
+
+                // Fire button down event
+                guac_touchpad.currentState[button] = true;
+                if (guac_touchpad.onmousedown)
+                    guac_touchpad.onmousedown(guac_touchpad.currentState);
+
+                // Fire button up event
+                guac_touchpad.currentState[button] = false;
+                if (guac_touchpad.onmouseup)
+                    guac_touchpad.onmouseup(guac_touchpad.currentState);
+
+                // Only update touch location after a scroll has been
+                // detected
+                last_touch_x = touch.clientX;
+                last_touch_y = touch.clientY;
+
+            }
+
+        }
+
+    }, false);
+
+};
+
+/**
+ * Provides cross-browser absolute touch event translation for a given element.
+ *
+ * Touch events are translated into mouse events as if the touches occurred
+ * on a touchscreen (tapping anywhere on the screen clicks at that point,
+ * long-press to right-click).
+ *
+ * @constructor
+ * @param {Element} element The Element to use to provide touch events.
+ */
+Guacamole.Mouse.Touchscreen = function(element) {
+
+    /**
+     * Reference to this Guacamole.Mouse.Touchscreen.
+     * @private
+     */
+    var guac_touchscreen = this;
+
+    /**
+     * Whether a gesture is known to be in progress. If false, touch events
+     * will be ignored.
+     *
+     * @private
+     */
+    var gesture_in_progress = false;
+
+    /**
+     * The start X location of a gesture.
+     * @private
+     */
+    var gesture_start_x = null;
+
+    /**
+     * The start Y location of a gesture.
+     * @private
+     */
+    var gesture_start_y = null;
+
+    /**
+     * The timeout associated with the delayed, cancellable click release.
+     *
+     * @private
+     */
+    var click_release_timeout = null;
+
+    /**
+     * The timeout associated with long-press for right click.
+     *
+     * @private
+     */
+    var long_press_timeout = null;
+
+    /**
+     * The distance a two-finger touch must move per scrollwheel event, in
+     * pixels.
+     */
+    this.scrollThreshold = 20 * (window.devicePixelRatio || 1);
+
+    /**
+     * The maximum number of milliseconds to wait for a touch to end for the
+     * gesture to be considered a click.
+     */
+    this.clickTimingThreshold = 250;
+
+    /**
+     * The maximum number of pixels to allow a touch to move for the gesture to
+     * be considered a click.
+     */
+    this.clickMoveThreshold = 16 * (window.devicePixelRatio || 1);
+
+    /**
+     * The amount of time a press must be held for long press to be
+     * detected.
+     */
+    this.longPressThreshold = 500;
+
+    /**
+     * 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
+    );
+
+    /**
+     * Fired whenever a mouse button is effectively pressed. This can happen
+     * as part of a "mousedown" gesture initiated by the user by pressing one
+     * finger over the touchscreen element, as part of a "scroll" gesture
+     * initiated by dragging two fingers up or down, etc.
+     *
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmousedown = null;
+
+    /**
+     * Fired whenever a mouse button is effectively released. This can happen
+     * as part of a "mouseup" gesture initiated by the user by removing the
+     * finger pressed against the touchscreen element, or as part of a "scroll"
+     * gesture initiated by dragging two fingers up or down, etc.
+     *
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmouseup = null;
+
+    /**
+     * Fired whenever the user moves the mouse by dragging their finger over
+     * the touchscreen element. Note that unlike Guacamole.Mouse.Touchpad,
+     * dragging a finger over the touchscreen element will always cause
+     * the mouse button to be effectively down, as if clicking-and-dragging.
+     *
+     * @event
+     * @param {Guacamole.Mouse.State} state The current mouse state.
+     */
+	this.onmousemove = null;
+
+    /**
+     * Presses the given mouse button, if it isn't already pressed. Valid
+     * button values are "left", "middle", "right", "up", and "down".
+     *
+     * @private
+     * @param {String} button The mouse button to press.
+     */
+    function press_button(button) {
+        if (!guac_touchscreen.currentState[button]) {
+            guac_touchscreen.currentState[button] = true;
+            if (guac_touchscreen.onmousedown)
+                guac_touchscreen.onmousedown(guac_touchscreen.currentState);
+        }
+    }
+
+    /**
+     * Releases the given mouse button, if it isn't already released. Valid
+     * button values are "left", "middle", "right", "up", and "down".
+     *
+     * @private
+     * @param {String} button The mouse button to release.
+     */
+    function release_button(button) {
+        if (guac_touchscreen.currentState[button]) {
+            guac_touchscreen.currentState[button] = false;
+            if (guac_touchscreen.onmouseup)
+                guac_touchscreen.onmouseup(guac_touchscreen.currentState);
+        }
+    }
+
+    /**
+     * Clicks (presses and releases) the given mouse button. Valid button
+     * values are "left", "middle", "right", "up", and "down".
+     *
+     * @private
+     * @param {String} button The mouse button to click.
+     */
+    function click_button(button) {
+        press_button(button);
+        release_button(button);
+    }
+
+    /**
+     * Moves the mouse to the given coordinates. These coordinates must be
+     * relative to the browser window, as they will be translated based on
+     * the touch event target's location within the browser window.
+     *
+     * @private
+     * @param {Number} x The X coordinate of the mouse pointer.
+     * @param {Number} y The Y coordinate of the mouse pointer.
+     */
+    function move_mouse(x, y) {
+        guac_touchscreen.currentState.fromClientPosition(element, x, y);
+        if (guac_touchscreen.onmousemove)
+            guac_touchscreen.onmousemove(guac_touchscreen.currentState);
+    }
+
+    /**
+     * Returns whether the given touch event exceeds the movement threshold for
+     * clicking, based on where the touch gesture began.
+     *
+     * @private
+     * @param {TouchEvent} e The touch event to check.
+     * @return {Boolean} true if the movement threshold is exceeded, false
+     *                   otherwise.
+     */
+    function finger_moved(e) {
+        var touch = e.touches[0] || e.changedTouches[0];
+        var delta_x = touch.clientX - gesture_start_x;
+        var delta_y = touch.clientY - gesture_start_y;
+        return Math.sqrt(delta_x*delta_x + delta_y*delta_y) >= guac_touchscreen.clickMoveThreshold;
+    }
+
+    /**
+     * Begins a new gesture at the location of the first touch in the given
+     * touch event.
+     * 
+     * @private
+     * @param {TouchEvent} e The touch event beginning this new gesture.
+     */
+    function begin_gesture(e) {
+        var touch = e.touches[0];
+        gesture_in_progress = true;
+        gesture_start_x = touch.clientX;
+        gesture_start_y = touch.clientY;
+    }
+
+    /**
+     * End the current gesture entirely. Wait for all touches to be done before
+     * resuming gesture detection.
+     * 
+     * @private
+     */
+    function end_gesture() {
+        window.clearTimeout(click_release_timeout);
+        window.clearTimeout(long_press_timeout);
+        gesture_in_progress = false;
+    }
+
+    element.addEventListener("touchend", function(e) {
+
+        // Do not handle if no gesture
+        if (!gesture_in_progress)
+            return;
+
+        // Ignore if more than one touch
+        if (e.touches.length !== 0 || e.changedTouches.length !== 1) {
+            end_gesture();
+            return;
+        }
+
+        // Long-press, if any, is over
+        window.clearTimeout(long_press_timeout);
+
+        // Always release mouse button if pressed
+        release_button("left");
+
+        // If finger hasn't moved enough to cancel the click
+        if (!finger_moved(e)) {
+
+            e.preventDefault();
+
+            // If not yet pressed, press and start delay release
+            if (!guac_touchscreen.currentState.left) {
+
+                var touch = e.changedTouches[0];
+                move_mouse(touch.clientX, touch.clientY);
+                press_button("left");
+
+                // Release button after a delay, if not canceled
+                click_release_timeout = window.setTimeout(function() {
+                    release_button("left");
+                    end_gesture();
+                }, guac_touchscreen.clickTimingThreshold);
+
+            }
+
+        } // end if finger not moved
+
+    }, false);
+
+    element.addEventListener("touchstart", function(e) {
+
+        // Ignore if more than one touch
+        if (e.touches.length !== 1) {
+            end_gesture();
+            return;
+        }
+
+        e.preventDefault();
+
+        // New touch begins a new gesture
+        begin_gesture(e);
+
+        // Keep button pressed if tap after left click
+        window.clearTimeout(click_release_timeout);
+
+        // Click right button if this turns into a long-press
+        long_press_timeout = window.setTimeout(function() {
+            var touch = e.touches[0];
+            move_mouse(touch.clientX, touch.clientY);
+            click_button("right");
+            end_gesture();
+        }, guac_touchscreen.longPressThreshold);
+
+    }, false);
+
+    element.addEventListener("touchmove", function(e) {
+
+        // Do not handle if no gesture
+        if (!gesture_in_progress)
+            return;
+
+        // Cancel long press if finger moved
+        if (finger_moved(e))
+            window.clearTimeout(long_press_timeout);
+
+        // Ignore if more than one touch
+        if (e.touches.length !== 1) {
+            end_gesture();
+            return;
+        }
+
+        // Update mouse position if dragging
+        if (guac_touchscreen.currentState.left) {
+
+            e.preventDefault();
+
+            // Update state
+            var touch = e.touches[0];
+            move_mouse(touch.clientX, touch.clientY);
+
+        }
+
+    }, false);
+
+};
+</code></pre>
+        </article>
+    </section>
+
+
+
+
+</div>
+
+<nav>
+    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Client</a></li><li><a href="Guacamole.DataURIReader.html">DataURIReader</a></li><li><a href="Guacamole.Display.html">Display</a></li><li><a href="Guacamole.Display.VisibleLayer.html">VisibleLayer</a></li><li><a href="Guacamole.HTTPTunnel.html">HTTPTunnel</a></li><li><a href="Guacamole.InputStream.html">InputStream</a></li><li><a href="Guacamole.IntegerPool.html">IntegerPool</a></li><li><a href="Guacamole.JSONReader.html">JSONReader</a></li>
 <li><a href="Guacamole.Keyboard.html">Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">ModifierState</a></li><li><a href="Guacamole.Layer.html">Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Pixel</a></li><li><a href="Guacamole.Mouse.html">Mouse</a></li><li><a href="Guacamole.Mouse.State.html">State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Touchscreen</a></li><li><a href="Guacamole.Object.html">Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Layout</a></li><li><a href="Guacamole.OutputStream.html">OutputStream</a></li><li><a href="Guacamole.Parser.html">Parser</a></li><li><a href="Guacamole.RawAudioFormat.html">RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html">RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioR
 ecorder.html">RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Status</a></li><li><a href="Guacamole.StringReader.html">StringReader</a></li><li><a href="Guacamole.StringWriter.html">StringWriter</a></li><li><a href="Guacamole.Tunnel.html">Tunnel</a></li><li><a href="Guacamole.VideoPlayer.html">VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobReader.html#event:onend">onend</a></li><
 li><a href="Guacamole.BlobReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onaudio">onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">onfilesystem</a></li><li><a href="Guacamole.Client.html#event:onna
 me">onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">onpipe</a></li><li><a href="Guacamole.Client.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">onresize</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.InputStream.html#event:onblob">onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onprogress">onprog
 ress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.html#event:onmouseout">onmouseout</a></li><li><a href="Guacamole.Mouse.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Object.html#event:onbody">onbody</a></li><
 li><a href="Guacamole.Object.html#event:onundefine">onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">onpause</a></li><li><a href="Guacamole.SessionRecording.html#event:onplay">onplay</a></li><li><a href="Guacamole
 .SessionRecording.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">onseek</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.StringReader.html#event:onend">onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">onack</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamol
 e.WebSocketTunnel.html#event:onstatechange">onstatechange</a></li></ul><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">AudioContextFactory</a></li></ul>
+</nav>
+
+<br class="clear">
+
+<footer>
+    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Sun Jul 02 2017 12:09:53 GMT-0700 (PDT)
+</footer>
+
+<script> prettyPrint(); </script>
+<script src="scripts/linenumber.js"> </script>
+</body>
+</html>



Mime
View raw message