1 /*
  2  * Copyright (C) 2014 Glyptodon LLC
  3  *
  4  * Permission is hereby granted, free of charge, to any person obtaining a copy
  5  * of this software and associated documentation files (the "Software"), to deal
  6  * in the Software without restriction, including without limitation the rights
  7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8  * copies of the Software, and to permit persons to whom the Software is
  9  * furnished to do so, subject to the following conditions:
 10  *
 11  * The above copyright notice and this permission notice shall be included in
 12  * all copies or substantial portions of the Software.
 13  *
 14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 20  * THE SOFTWARE.
 21  */
 22 
 23 var Guacamole = Guacamole || {};
 24 
 25 /**
 26  * The Guacamole display. The display does not deal with the Guacamole
 27  * protocol, and instead implements a set of graphical operations which
 28  * embody the set of operations present in the protocol. The order operations
 29  * are executed is guaranteed to be in the same order as their corresponding
 30  * functions are called.
 31  * 
 32  * @constructor
 33  */
 34 Guacamole.Display = function() {
 35 
 36     /**
 37      * Reference to this Guacamole.Display.
 38      * @private
 39      */
 40     var guac_display = this;
 41 
 42     var displayWidth = 0;
 43     var displayHeight = 0;
 44     var displayScale = 1;
 45 
 46     // Create display
 47     var display = document.createElement("div");
 48     display.style.position = "relative";
 49     display.style.width = displayWidth + "px";
 50     display.style.height = displayHeight + "px";
 51 
 52     // Ensure transformations on display originate at 0,0
 53     display.style.transformOrigin =
 54     display.style.webkitTransformOrigin =
 55     display.style.MozTransformOrigin =
 56     display.style.OTransformOrigin =
 57     display.style.msTransformOrigin =
 58         "0 0";
 59 
 60     // Create default layer
 61     var default_layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight);
 62 
 63     // Create cursor layer
 64     var cursor = new Guacamole.Display.VisibleLayer(0, 0);
 65     cursor.setChannelMask(Guacamole.Layer.SRC);
 66 
 67     // Add default layer and cursor to display
 68     display.appendChild(default_layer.getElement());
 69     display.appendChild(cursor.getElement());
 70 
 71     // Create bounding div 
 72     var bounds = document.createElement("div");
 73     bounds.style.position = "relative";
 74     bounds.style.width = (displayWidth*displayScale) + "px";
 75     bounds.style.height = (displayHeight*displayScale) + "px";
 76 
 77     // Add display to bounds
 78     bounds.appendChild(display);
 79 
 80     /**
 81      * The X coordinate of the hotspot of the mouse cursor. The hotspot is
 82      * the relative location within the image of the mouse cursor at which
 83      * each click occurs.
 84      * 
 85      * @type Number
 86      */
 87     this.cursorHotspotX = 0;
 88 
 89     /**
 90      * The Y coordinate of the hotspot of the mouse cursor. The hotspot is
 91      * the relative location within the image of the mouse cursor at which
 92      * each click occurs.
 93      * 
 94      * @type Number
 95      */
 96     this.cursorHotspotY = 0;
 97 
 98     /**
 99      * The current X coordinate of the local mouse cursor. This is not
100      * necessarily the location of the actual mouse - it refers only to
101      * the location of the cursor image within the Guacamole display, as
102      * last set by moveCursor().
103      * 
104      * @type Number
105      */
106     this.cursorX = 0;
107 
108     /**
109      * The current X coordinate of the local mouse cursor. This is not
110      * necessarily the location of the actual mouse - it refers only to
111      * the location of the cursor image within the Guacamole display, as
112      * last set by moveCursor().
113      * 
114      * @type Number
115      */
116     this.cursorY = 0;
117 
118     /**
119      * Fired when the default layer (and thus the entire Guacamole display)
120      * is resized.
121      * 
122      * @event
123      * @param {Number} width The new width of the Guacamole display.
124      * @param {Number} height The new height of the Guacamole display.
125      */
126     this.onresize = null;
127 
128     /**
129      * Fired whenever the local cursor image is changed. This can be used to
130      * implement special handling of the client-side cursor, or to override
131      * the default use of a software cursor layer.
132      * 
133      * @event
134      * @param {HTMLCanvasElement} canvas The cursor image.
135      * @param {Number} x The X-coordinate of the cursor hotspot.
136      * @param {Number} y The Y-coordinate of the cursor hotspot.
137      */
138     this.oncursor = null;
139 
140     /**
141      * The queue of all pending Tasks. Tasks will be run in order, with new
142      * tasks added at the end of the queue and old tasks removed from the
143      * front of the queue (FIFO). These tasks will eventually be grouped
144      * into a Frame.
145      * @private
146      * @type Task[]
147      */
148     var tasks = [];
149 
150     /**
151      * The queue of all frames. Each frame is a pairing of an array of tasks
152      * and a callback which must be called when the frame is rendered.
153      * @private
154      * @type Frame[]
155      */
156     var frames = [];
157 
158     /**
159      * Flushes all pending frames.
160      * @private
161      */
162     function __flush_frames() {
163 
164         var rendered_frames = 0;
165 
166         // Draw all pending frames, if ready
167         while (rendered_frames < frames.length) {
168 
169             var frame = frames[rendered_frames];
170             if (!frame.isReady())
171                 break;
172 
173             frame.flush();
174             rendered_frames++;
175 
176         } 
177 
178         // Remove rendered frames from array
179         frames.splice(0, rendered_frames);
180 
181     }
182 
183     /**
184      * An ordered list of tasks which must be executed atomically. Once
185      * executed, an associated (and optional) callback will be called.
186      *
187      * @private
188      * @constructor
189      * @param {function} callback The function to call when this frame is
190      *                            rendered.
191      * @param {Task[]} tasks The set of tasks which must be executed to render
192      *                       this frame.
193      */
194     function Frame(callback, tasks) {
195 
196         /**
197          * Returns whether this frame is ready to be rendered. This function
198          * returns true if and only if ALL underlying tasks are unblocked.
199          * 
200          * @returns {Boolean} true if all underlying tasks are unblocked,
201          *                    false otherwise.
202          */
203         this.isReady = function() {
204 
205             // Search for blocked tasks
206             for (var i=0; i < tasks.length; i++) {
207                 if (tasks[i].blocked)
208                     return false;
209             }
210 
211             // If no blocked tasks, the frame is ready
212             return true;
213 
214         };
215 
216         /**
217          * Renders this frame, calling the associated callback, if any, after
218          * the frame is complete. This function MUST only be called when no
219          * blocked tasks exist. Calling this function with blocked tasks
220          * will result in undefined behavior.
221          */
222         this.flush = function() {
223 
224             // Draw all pending tasks.
225             for (var i=0; i < tasks.length; i++)
226                 tasks[i].execute();
227 
228             // Call callback
229             if (callback) callback();
230 
231         };
232 
233     }
234 
235     /**
236      * A container for an task handler. Each operation which must be ordered
237      * is associated with a Task that goes into a task queue. Tasks in this
238      * queue are executed in order once their handlers are set, while Tasks 
239      * without handlers block themselves and any following Tasks from running.
240      *
241      * @constructor
242      * @private
243      * @param {function} taskHandler The function to call when this task 
244      *                               runs, if any.
245      * @param {boolean} blocked Whether this task should start blocked.
246      */
247     function Task(taskHandler, blocked) {
248        
249         var task = this;
250        
251         /**
252          * Whether this Task is blocked.
253          * 
254          * @type boolean
255          */
256         this.blocked = blocked;
257 
258         /**
259          * Unblocks this Task, allowing it to run.
260          */
261         this.unblock = function() {
262             if (task.blocked) {
263                 task.blocked = false;
264                 __flush_frames();
265             }
266         };
267 
268         /**
269          * Calls the handler associated with this task IMMEDIATELY. This
270          * function does not track whether this task is marked as blocked.
271          * Enforcing the blocked status of tasks is up to the caller.
272          */
273         this.execute = function() {
274             if (taskHandler) taskHandler();
275         };
276 
277     }
278 
279     /**
280      * Schedules a task for future execution. The given handler will execute
281      * immediately after all previous tasks upon frame flush, unless this
282      * task is blocked. If any tasks is blocked, the entire frame will not
283      * render (and no tasks within will execute) until all tasks are unblocked.
284      * 
285      * @private
286      * @param {function} handler The function to call when possible, if any.
287      * @param {boolean} blocked Whether the task should start blocked.
288      * @returns {Task} The Task created and added to the queue for future
289      *                 running.
290      */
291     function scheduleTask(handler, blocked) {
292         var task = new Task(handler, blocked);
293         tasks.push(task);
294         return task;
295     }
296 
297     /**
298      * Returns the element which contains the Guacamole display.
299      * 
300      * @return {Element} The element containing the Guacamole display.
301      */
302     this.getElement = function() {
303         return bounds;
304     };
305 
306     /**
307      * Returns the width of this display.
308      * 
309      * @return {Number} The width of this display;
310      */
311     this.getWidth = function() {
312         return displayWidth;
313     };
314 
315     /**
316      * Returns the height of this display.
317      * 
318      * @return {Number} The height of this display;
319      */
320     this.getHeight = function() {
321         return displayHeight;
322     };
323 
324     /**
325      * Returns the default layer of this display. Each Guacamole display always
326      * has at least one layer. Other layers can optionally be created within
327      * this layer, but the default layer cannot be removed and is the absolute
328      * ancestor of all other layers.
329      * 
330      * @return {Guacamole.Display.VisibleLayer} The default layer.
331      */
332     this.getDefaultLayer = function() {
333         return default_layer;
334     };
335 
336     /**
337      * Returns the cursor layer of this display. Each Guacamole display contains
338      * a layer for the image of the mouse cursor. This layer is a special case
339      * and exists above all other layers, similar to the hardware mouse cursor.
340      * 
341      * @return {Guacamole.Display.VisibleLayer} The cursor layer.
342      */
343     this.getCursorLayer = function() {
344         return cursor;
345     };
346 
347     /**
348      * Creates a new layer. The new layer will be a direct child of the default
349      * layer, but can be moved to be a child of any other layer. Layers returned
350      * by this function are visible.
351      * 
352      * @return {Guacamole.Display.VisibleLayer} The newly-created layer.
353      */
354     this.createLayer = function() {
355         var layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight);
356         layer.move(default_layer, 0, 0, 0);
357         return layer;
358     };
359 
360     /**
361      * Creates a new buffer. Buffers are invisible, off-screen surfaces. They
362      * are implemented in the same manner as layers, but do not provide the
363      * same nesting semantics.
364      * 
365      * @return {Guacamole.Layer} The newly-created buffer.
366      */
367     this.createBuffer = function() {
368         var buffer = new Guacamole.Layer(0, 0);
369         buffer.autosize = 1;
370         return buffer;
371     };
372 
373     /**
374      * Flush all pending draw tasks, if possible, as a new frame. If the entire
375      * frame is not ready, the flush will wait until all required tasks are
376      * unblocked.
377      * 
378      * @param {function} callback The function to call when this frame is
379      *                            flushed. This may happen immediately, or
380      *                            later when blocked tasks become unblocked.
381      */
382     this.flush = function(callback) {
383 
384         // Add frame, reset tasks
385         frames.push(new Frame(callback, tasks));
386         tasks = [];
387 
388         // Attempt flush
389         __flush_frames();
390 
391     };
392 
393     /**
394      * Sets the hotspot and image of the mouse cursor displayed within the
395      * Guacamole display.
396      * 
397      * @param {Number} hotspotX The X coordinate of the cursor hotspot.
398      * @param {Number} hotspotY The Y coordinate of the cursor hotspot.
399      * @param {Guacamole.Layer} layer The source layer containing the data which
400      *                                should be used as the mouse cursor image.
401      * @param {Number} srcx The X coordinate of the upper-left corner of the
402      *                      rectangle within the source layer's coordinate
403      *                      space to copy data from.
404      * @param {Number} srcy The Y coordinate of the upper-left corner of the
405      *                      rectangle within the source layer's coordinate
406      *                      space to copy data from.
407      * @param {Number} srcw The width of the rectangle within the source layer's
408      *                      coordinate space to copy data from.
409      * @param {Number} srch The height of the rectangle within the source
410      *                      layer's coordinate space to copy data from.
411 
412      */
413     this.setCursor = function(hotspotX, hotspotY, layer, srcx, srcy, srcw, srch) {
414         scheduleTask(function __display_set_cursor() {
415 
416             // Set hotspot
417             guac_display.cursorHotspotX = hotspotX;
418             guac_display.cursorHotspotY = hotspotY;
419 
420             // Reset cursor size
421             cursor.resize(srcw, srch);
422 
423             // Draw cursor to cursor layer
424             cursor.copy(layer, srcx, srcy, srcw, srch, 0, 0);
425             guac_display.moveCursor(guac_display.cursorX, guac_display.cursorY);
426 
427             // Fire cursor change event
428             if (guac_display.oncursor)
429                 guac_display.oncursor(cursor.getCanvas(), hotspotX, hotspotY);
430 
431         });
432     };
433 
434     /**
435      * Sets whether the software-rendered cursor is shown. This cursor differs
436      * from the hardware cursor in that it is built into the Guacamole.Display,
437      * and relies on its own Guacamole layer to render.
438      *
439      * @param {Boolean} [shown=true] Whether to show the software cursor.
440      */
441     this.showCursor = function(shown) {
442 
443         var element = cursor.getElement();
444         var parent = element.parentNode;
445 
446         // Remove from DOM if hidden
447         if (shown === false) {
448             if (parent)
449                 parent.removeChild(element);
450         }
451 
452         // Otherwise, ensure cursor is child of display
453         else if (parent !== display)
454             display.appendChild(element);
455 
456     };
457 
458     /**
459      * Sets the location of the local cursor to the given coordinates. For the
460      * sake of responsiveness, this function performs its action immediately.
461      * Cursor motion is not maintained within atomic frames.
462      * 
463      * @param {Number} x The X coordinate to move the cursor to.
464      * @param {Number} y The Y coordinate to move the cursor to.
465      */
466     this.moveCursor = function(x, y) {
467 
468         // Move cursor layer
469         cursor.translate(x - guac_display.cursorHotspotX,
470                          y - guac_display.cursorHotspotY);
471 
472         // Update stored position
473         guac_display.cursorX = x;
474         guac_display.cursorY = y;
475 
476     };
477 
478     /**
479      * Changes the size of the given Layer to the given width and height.
480      * Resizing is only attempted if the new size provided is actually different
481      * from the current size.
482      * 
483      * @param {Guacamole.Layer} layer The layer to resize.
484      * @param {Number} width The new width.
485      * @param {Number} height The new height.
486      */
487     this.resize = function(layer, width, height) {
488         scheduleTask(function __display_resize() {
489 
490             layer.resize(width, height);
491 
492             // Resize display if default layer is resized
493             if (layer === default_layer) {
494 
495                 // Update (set) display size
496                 displayWidth = width;
497                 displayHeight = height;
498                 display.style.width = displayWidth + "px";
499                 display.style.height = displayHeight + "px";
500 
501                 // Update bounds size
502                 bounds.style.width = (displayWidth*displayScale) + "px";
503                 bounds.style.height = (displayHeight*displayScale) + "px";
504 
505                 // Notify of resize
506                 if (guac_display.onresize)
507                     guac_display.onresize(width, height);
508 
509             }
510 
511         });
512     };
513 
514     /**
515      * Draws the specified image at the given coordinates. The image specified
516      * must already be loaded.
517      * 
518      * @param {Guacamole.Layer} layer The layer to draw upon.
519      * @param {Number} x The destination X coordinate.
520      * @param {Number} y The destination Y coordinate.
521      * @param {Image} image The image to draw. Note that this is an Image
522      *                      object - not a URL.
523      */
524     this.drawImage = function(layer, x, y, image) {
525         scheduleTask(function __display_drawImage() {
526             layer.drawImage(x, y, image);
527         });
528     };
529 
530     /**
531      * Draws the image contained within the specified Blob at the given
532      * coordinates. The Blob specified must already be populated with image
533      * data.
534      *
535      * @param {Guacamole.Layer} layer
536      *     The layer to draw upon.
537      *
538      * @param {Number} x
539      *     The destination X coordinate.
540      *
541      * @param {Number} y
542      *     The destination Y coordinate.
543      *
544      * @param {Blob} blob
545      *     The Blob containing the image data to draw.
546      */
547     this.drawBlob = function(layer, x, y, blob) {
548 
549         // Create URL for blob
550         var url = URL.createObjectURL(blob);
551 
552         // Draw and free blob URL when ready
553         var task = scheduleTask(function __display_drawBlob() {
554             layer.drawImage(x, y, image);
555             URL.revokeObjectURL(url);
556         }, true);
557 
558         // Load image from URL
559         var image = new Image();
560         image.onload = task.unblock;
561         image.src = url;
562 
563     };
564 
565     /**
566      * Draws the image at the specified URL at the given coordinates. The image
567      * will be loaded automatically, and this and any future operations will
568      * wait for the image to finish loading.
569      * 
570      * @param {Guacamole.Layer} layer The layer to draw upon.
571      * @param {Number} x The destination X coordinate.
572      * @param {Number} y The destination Y coordinate.
573      * @param {String} url The URL of the image to draw.
574      */
575     this.draw = function(layer, x, y, url) {
576 
577         var task = scheduleTask(function __display_draw() {
578             layer.drawImage(x, y, image);
579         }, true);
580 
581         var image = new Image();
582         image.onload = task.unblock;
583         image.src = url;
584 
585     };
586 
587     /**
588      * Plays the video at the specified URL within this layer. The video
589      * will be loaded automatically, and this and any future operations will
590      * wait for the video to finish loading. Future operations will not be
591      * executed until the video finishes playing.
592      * 
593      * @param {Guacamole.Layer} layer The layer to draw upon.
594      * @param {String} mimetype The mimetype of the video to play.
595      * @param {Number} duration The duration of the video in milliseconds.
596      * @param {String} url The URL of the video to play.
597      */
598     this.play = function(layer, mimetype, duration, url) {
599 
600         // Start loading the video
601         var video = document.createElement("video");
602         video.type = mimetype;
603         video.src = url;
604 
605         // Start copying frames when playing
606         video.addEventListener("play", function() {
607             
608             function render_callback() {
609                 layer.drawImage(0, 0, video);
610                 if (!video.ended)
611                     window.setTimeout(render_callback, 20);
612             }
613             
614             render_callback();
615             
616         }, false);
617 
618         scheduleTask(video.play);
619 
620     };
621 
622     /**
623      * Transfer a rectangle of image data from one Layer to this Layer using the
624      * specified transfer function.
625      * 
626      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
627      * @param {Number} srcx The X coordinate of the upper-left corner of the
628      *                      rectangle within the source Layer's coordinate
629      *                      space to copy data from.
630      * @param {Number} srcy The Y coordinate of the upper-left corner of the
631      *                      rectangle within the source Layer's coordinate
632      *                      space to copy data from.
633      * @param {Number} srcw The width of the rectangle within the source Layer's
634      *                      coordinate space to copy data from.
635      * @param {Number} srch The height of the rectangle within the source
636      *                      Layer's coordinate space to copy data from.
637      * @param {Guacamole.Layer} dstLayer The layer to draw upon.
638      * @param {Number} x The destination X coordinate.
639      * @param {Number} y The destination Y coordinate.
640      * @param {Function} transferFunction The transfer function to use to
641      *                                    transfer data from source to
642      *                                    destination.
643      */
644     this.transfer = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y, transferFunction) {
645         scheduleTask(function __display_transfer() {
646             dstLayer.transfer(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction);
647         });
648     };
649 
650     /**
651      * Put a rectangle of image data from one Layer to this Layer directly
652      * without performing any alpha blending. Simply copy the data.
653      * 
654      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
655      * @param {Number} srcx The X coordinate of the upper-left corner of the
656      *                      rectangle within the source Layer's coordinate
657      *                      space to copy data from.
658      * @param {Number} srcy The Y coordinate of the upper-left corner of the
659      *                      rectangle within the source Layer's coordinate
660      *                      space to copy data from.
661      * @param {Number} srcw The width of the rectangle within the source Layer's
662      *                      coordinate space to copy data from.
663      * @param {Number} srch The height of the rectangle within the source
664      *                      Layer's coordinate space to copy data from.
665      * @param {Guacamole.Layer} dstLayer The layer to draw upon.
666      * @param {Number} x The destination X coordinate.
667      * @param {Number} y The destination Y coordinate.
668      */
669     this.put = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
670         scheduleTask(function __display_put() {
671             dstLayer.put(srcLayer, srcx, srcy, srcw, srch, x, y);
672         });
673     };
674 
675     /**
676      * Copy a rectangle of image data from one Layer to this Layer. This
677      * operation will copy exactly the image data that will be drawn once all
678      * operations of the source Layer that were pending at the time this
679      * function was called are complete. This operation will not alter the
680      * size of the source Layer even if its autosize property is set to true.
681      * 
682      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
683      * @param {Number} srcx The X coordinate of the upper-left corner of the
684      *                      rectangle within the source Layer's coordinate
685      *                      space to copy data from.
686      * @param {Number} srcy The Y coordinate of the upper-left corner of the
687      *                      rectangle within the source Layer's coordinate
688      *                      space to copy data from.
689      * @param {Number} srcw The width of the rectangle within the source Layer's
690      *                      coordinate space to copy data from.
691      * @param {Number} srch The height of the rectangle within the source
692      *                      Layer's coordinate space to copy data from.
693      * @param {Guacamole.Layer} dstLayer The layer to draw upon.
694      * @param {Number} x The destination X coordinate.
695      * @param {Number} y The destination Y coordinate.
696      */
697     this.copy = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
698         scheduleTask(function __display_copy() {
699             dstLayer.copy(srcLayer, srcx, srcy, srcw, srch, x, y);
700         });
701     };
702 
703     /**
704      * Starts a new path at the specified point.
705      * 
706      * @param {Guacamole.Layer} layer The layer to draw upon.
707      * @param {Number} x The X coordinate of the point to draw.
708      * @param {Number} y The Y coordinate of the point to draw.
709      */
710     this.moveTo = function(layer, x, y) {
711         scheduleTask(function __display_moveTo() {
712             layer.moveTo(x, y);
713         });
714     };
715 
716     /**
717      * Add the specified line to the current path.
718      * 
719      * @param {Guacamole.Layer} layer The layer to draw upon.
720      * @param {Number} x The X coordinate of the endpoint of the line to draw.
721      * @param {Number} y The Y coordinate of the endpoint of the line to draw.
722      */
723     this.lineTo = function(layer, x, y) {
724         scheduleTask(function __display_lineTo() {
725             layer.lineTo(x, y);
726         });
727     };
728 
729     /**
730      * Add the specified arc to the current path.
731      * 
732      * @param {Guacamole.Layer} layer The layer to draw upon.
733      * @param {Number} x The X coordinate of the center of the circle which
734      *                   will contain the arc.
735      * @param {Number} y The Y coordinate of the center of the circle which
736      *                   will contain the arc.
737      * @param {Number} radius The radius of the circle.
738      * @param {Number} startAngle The starting angle of the arc, in radians.
739      * @param {Number} endAngle The ending angle of the arc, in radians.
740      * @param {Boolean} negative Whether the arc should be drawn in order of
741      *                           decreasing angle.
742      */
743     this.arc = function(layer, x, y, radius, startAngle, endAngle, negative) {
744         scheduleTask(function __display_arc() {
745             layer.arc(x, y, radius, startAngle, endAngle, negative);
746         });
747     };
748 
749     /**
750      * Starts a new path at the specified point.
751      * 
752      * @param {Guacamole.Layer} layer The layer to draw upon.
753      * @param {Number} cp1x The X coordinate of the first control point.
754      * @param {Number} cp1y The Y coordinate of the first control point.
755      * @param {Number} cp2x The X coordinate of the second control point.
756      * @param {Number} cp2y The Y coordinate of the second control point.
757      * @param {Number} x The X coordinate of the endpoint of the curve.
758      * @param {Number} y The Y coordinate of the endpoint of the curve.
759      */
760     this.curveTo = function(layer, cp1x, cp1y, cp2x, cp2y, x, y) {
761         scheduleTask(function __display_curveTo() {
762             layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
763         });
764     };
765 
766     /**
767      * Closes the current path by connecting the end point with the start
768      * point (if any) with a straight line.
769      * 
770      * @param {Guacamole.Layer} layer The layer to draw upon.
771      */
772     this.close = function(layer) {
773         scheduleTask(function __display_close() {
774             layer.close();
775         });
776     };
777 
778     /**
779      * Add the specified rectangle to the current path.
780      * 
781      * @param {Guacamole.Layer} layer The layer to draw upon.
782      * @param {Number} x The X coordinate of the upper-left corner of the
783      *                   rectangle to draw.
784      * @param {Number} y The Y coordinate of the upper-left corner of the
785      *                   rectangle to draw.
786      * @param {Number} w The width of the rectangle to draw.
787      * @param {Number} h The height of the rectangle to draw.
788      */
789     this.rect = function(layer, x, y, w, h) {
790         scheduleTask(function __display_rect() {
791             layer.rect(x, y, w, h);
792         });
793     };
794 
795     /**
796      * Clip all future drawing operations by the current path. The current path
797      * is implicitly closed. The current path can continue to be reused
798      * for other operations (such as fillColor()) but a new path will be started
799      * once a path drawing operation (path() or rect()) is used.
800      * 
801      * @param {Guacamole.Layer} layer The layer to affect.
802      */
803     this.clip = function(layer) {
804         scheduleTask(function __display_clip() {
805             layer.clip();
806         });
807     };
808 
809     /**
810      * Stroke the current path with the specified color. The current path
811      * is implicitly closed. The current path can continue to be reused
812      * for other operations (such as clip()) but a new path will be started
813      * once a path drawing operation (path() or rect()) is used.
814      * 
815      * @param {Guacamole.Layer} layer The layer to draw upon.
816      * @param {String} cap The line cap style. Can be "round", "square",
817      *                     or "butt".
818      * @param {String} join The line join style. Can be "round", "bevel",
819      *                      or "miter".
820      * @param {Number} thickness The line thickness in pixels.
821      * @param {Number} r The red component of the color to fill.
822      * @param {Number} g The green component of the color to fill.
823      * @param {Number} b The blue component of the color to fill.
824      * @param {Number} a The alpha component of the color to fill.
825      */
826     this.strokeColor = function(layer, cap, join, thickness, r, g, b, a) {
827         scheduleTask(function __display_strokeColor() {
828             layer.strokeColor(cap, join, thickness, r, g, b, a);
829         });
830     };
831 
832     /**
833      * Fills the current path with the specified color. The current path
834      * is implicitly closed. The current path can continue to be reused
835      * for other operations (such as clip()) but a new path will be started
836      * once a path drawing operation (path() or rect()) is used.
837      * 
838      * @param {Guacamole.Layer} layer The layer to draw upon.
839      * @param {Number} r The red component of the color to fill.
840      * @param {Number} g The green component of the color to fill.
841      * @param {Number} b The blue component of the color to fill.
842      * @param {Number} a The alpha component of the color to fill.
843      */
844     this.fillColor = function(layer, r, g, b, a) {
845         scheduleTask(function __display_fillColor() {
846             layer.fillColor(r, g, b, a);
847         });
848     };
849 
850     /**
851      * Stroke the current path with the image within the specified layer. The
852      * image data will be tiled infinitely within the stroke. The current path
853      * is implicitly closed. The current path can continue to be reused
854      * for other operations (such as clip()) but a new path will be started
855      * once a path drawing operation (path() or rect()) is used.
856      * 
857      * @param {Guacamole.Layer} layer The layer to draw upon.
858      * @param {String} cap The line cap style. Can be "round", "square",
859      *                     or "butt".
860      * @param {String} join The line join style. Can be "round", "bevel",
861      *                      or "miter".
862      * @param {Number} thickness The line thickness in pixels.
863      * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
864      *                                   within the stroke.
865      */
866     this.strokeLayer = function(layer, cap, join, thickness, srcLayer) {
867         scheduleTask(function __display_strokeLayer() {
868             layer.strokeLayer(cap, join, thickness, srcLayer);
869         });
870     };
871 
872     /**
873      * Fills the current path with the image within the specified layer. The
874      * image data will be tiled infinitely within the stroke. The current path
875      * is implicitly closed. The current path can continue to be reused
876      * for other operations (such as clip()) but a new path will be started
877      * once a path drawing operation (path() or rect()) is used.
878      * 
879      * @param {Guacamole.Layer} layer The layer to draw upon.
880      * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
881      *                                   within the fill.
882      */
883     this.fillLayer = function(layer, srcLayer) {
884         scheduleTask(function __display_fillLayer() {
885             layer.fillLayer(srcLayer);
886         });
887     };
888 
889     /**
890      * Push current layer state onto stack.
891      * 
892      * @param {Guacamole.Layer} layer The layer to draw upon.
893      */
894     this.push = function(layer) {
895         scheduleTask(function __display_push() {
896             layer.push();
897         });
898     };
899 
900     /**
901      * Pop layer state off stack.
902      * 
903      * @param {Guacamole.Layer} layer The layer to draw upon.
904      */
905     this.pop = function(layer) {
906         scheduleTask(function __display_pop() {
907             layer.pop();
908         });
909     };
910 
911     /**
912      * Reset the layer, clearing the stack, the current path, and any transform
913      * matrix.
914      * 
915      * @param {Guacamole.Layer} layer The layer to draw upon.
916      */
917     this.reset = function(layer) {
918         scheduleTask(function __display_reset() {
919             layer.reset();
920         });
921     };
922 
923     /**
924      * Sets the given affine transform (defined with six values from the
925      * transform's matrix).
926      * 
927      * @param {Guacamole.Layer} layer The layer to modify.
928      * @param {Number} a The first value in the affine transform's matrix.
929      * @param {Number} b The second value in the affine transform's matrix.
930      * @param {Number} c The third value in the affine transform's matrix.
931      * @param {Number} d The fourth value in the affine transform's matrix.
932      * @param {Number} e The fifth value in the affine transform's matrix.
933      * @param {Number} f The sixth value in the affine transform's matrix.
934      */
935     this.setTransform = function(layer, a, b, c, d, e, f) {
936         scheduleTask(function __display_setTransform() {
937             layer.setTransform(a, b, c, d, e, f);
938         });
939     };
940 
941     /**
942      * Applies the given affine transform (defined with six values from the
943      * transform's matrix).
944      * 
945      * @param {Guacamole.Layer} layer The layer to modify.
946      * @param {Number} a The first value in the affine transform's matrix.
947      * @param {Number} b The second value in the affine transform's matrix.
948      * @param {Number} c The third value in the affine transform's matrix.
949      * @param {Number} d The fourth value in the affine transform's matrix.
950      * @param {Number} e The fifth value in the affine transform's matrix.
951      * @param {Number} f The sixth value in the affine transform's matrix.
952      */
953     this.transform = function(layer, a, b, c, d, e, f) {
954         scheduleTask(function __display_transform() {
955             layer.transform(a, b, c, d, e, f);
956         });
957     };
958 
959     /**
960      * Sets the channel mask for future operations on this Layer.
961      * 
962      * The channel mask is a Guacamole-specific compositing operation identifier
963      * with a single bit representing each of four channels (in order): source
964      * image where destination transparent, source where destination opaque,
965      * destination where source transparent, and destination where source
966      * opaque.
967      * 
968      * @param {Guacamole.Layer} layer The layer to modify.
969      * @param {Number} mask The channel mask for future operations on this
970      *                      Layer.
971      */
972     this.setChannelMask = function(layer, mask) {
973         scheduleTask(function __display_setChannelMask() {
974             layer.setChannelMask(mask);
975         });
976     };
977 
978     /**
979      * Sets the miter limit for stroke operations using the miter join. This
980      * limit is the maximum ratio of the size of the miter join to the stroke
981      * width. If this ratio is exceeded, the miter will not be drawn for that
982      * joint of the path.
983      * 
984      * @param {Guacamole.Layer} layer The layer to modify.
985      * @param {Number} limit The miter limit for stroke operations using the
986      *                       miter join.
987      */
988     this.setMiterLimit = function(layer, limit) {
989         scheduleTask(function __display_setMiterLimit() {
990             layer.setMiterLimit(limit);
991         });
992     };
993 
994     /**
995      * Sets the scale of the client display element such that it renders at
996      * a relatively smaller or larger size, without affecting the true
997      * resolution of the display.
998      *
999      * @param {Number} scale The scale to resize to, where 1.0 is normal
1000      *                       size (1:1 scale).
1001      */
1002     this.scale = function(scale) {
1003 
1004         display.style.transform =
1005         display.style.WebkitTransform =
1006         display.style.MozTransform =
1007         display.style.OTransform =
1008         display.style.msTransform =
1009 
1010             "scale(" + scale + "," + scale + ")";
1011 
1012         displayScale = scale;
1013 
1014         // Update bounds size
1015         bounds.style.width = (displayWidth*displayScale) + "px";
1016         bounds.style.height = (displayHeight*displayScale) + "px";
1017 
1018     };
1019 
1020     /**
1021      * Returns the scale of the display.
1022      *
1023      * @return {Number} The scale of the display.
1024      */
1025     this.getScale = function() {
1026         return displayScale;
1027     };
1028 
1029     /**
1030      * Returns a canvas element containing the entire display, with all child
1031      * layers composited within.
1032      *
1033      * @return {HTMLCanvasElement} A new canvas element containing a copy of
1034      *                             the display.
1035      */
1036     this.flatten = function() {
1037        
1038         // Get destination canvas
1039         var canvas = document.createElement("canvas");
1040         canvas.width = default_layer.width;
1041         canvas.height = default_layer.height;
1042 
1043         var context = canvas.getContext("2d");
1044 
1045         // Returns sorted array of children
1046         function get_children(layer) {
1047 
1048             // Build array of children
1049             var children = [];
1050             for (var index in layer.children)
1051                 children.push(layer.children[index]);
1052 
1053             // Sort
1054             children.sort(function children_comparator(a, b) {
1055 
1056                 // Compare based on Z order
1057                 var diff = a.z - b.z;
1058                 if (diff !== 0)
1059                     return diff;
1060 
1061                 // If Z order identical, use document order
1062                 var a_element = a.getElement();
1063                 var b_element = b.getElement();
1064                 var position = b_element.compareDocumentPosition(a_element);
1065 
1066                 if (position & Node.DOCUMENT_POSITION_PRECEDING) return -1;
1067                 if (position & Node.DOCUMENT_POSITION_FOLLOWING) return  1;
1068 
1069                 // Otherwise, assume same
1070                 return 0;
1071 
1072             });
1073 
1074             // Done
1075             return children;
1076 
1077         }
1078 
1079         // Draws the contents of the given layer at the given coordinates
1080         function draw_layer(layer, x, y) {
1081 
1082             // Draw layer
1083             if (layer.width > 0 && layer.height > 0) {
1084 
1085                 // Save and update alpha
1086                 var initial_alpha = context.globalAlpha;
1087                 context.globalAlpha *= layer.alpha / 255.0;
1088 
1089                 // Copy data
1090                 context.drawImage(layer.getCanvas(), x, y);
1091 
1092                 // Draw all children
1093                 var children = get_children(layer);
1094                 for (var i=0; i<children.length; i++) {
1095                     var child = children[i];
1096                     draw_layer(child, x + child.x, y + child.y);
1097                 }
1098 
1099                 // Restore alpha
1100                 context.globalAlpha = initial_alpha;
1101 
1102             }
1103 
1104         }
1105 
1106         // Draw default layer and all children
1107         draw_layer(default_layer, 0, 0);
1108 
1109         // Return new canvas copy
1110         return canvas;
1111         
1112     };
1113 
1114 };
1115 
1116 /**
1117  * Simple container for Guacamole.Layer, allowing layers to be easily
1118  * repositioned and nested. This allows certain operations to be accelerated
1119  * through DOM manipulation, rather than raster operations.
1120  * 
1121  * @constructor
1122  * @augments Guacamole.Layer
1123  * @param {Number} width The width of the Layer, in pixels. The canvas element
1124  *                       backing this Layer will be given this width.
1125  * @param {Number} height The height of the Layer, in pixels. The canvas element
1126  *                        backing this Layer will be given this height.
1127  */
1128 Guacamole.Display.VisibleLayer = function(width, height) {
1129 
1130     Guacamole.Layer.apply(this, [width, height]);
1131 
1132     /**
1133      * Reference to this layer.
1134      * @private
1135      */
1136     var layer = this;
1137 
1138     /**
1139      * Identifier which uniquely identifies this layer. This is COMPLETELY
1140      * UNRELATED to the index of the underlying layer, which is specific
1141      * to the Guacamole protocol, and not relevant at this level.
1142      * 
1143      * @private
1144      * @type Number
1145      */
1146     this.__unique_id = Guacamole.Display.VisibleLayer.__next_id++;
1147 
1148     /**
1149      * The opacity of the layer container, where 255 is fully opaque and 0 is
1150      * fully transparent.
1151      */
1152     this.alpha = 0xFF;
1153 
1154     /**
1155      * X coordinate of the upper-left corner of this layer container within
1156      * its parent, in pixels.
1157      * @type Number
1158      */
1159     this.x = 0;
1160 
1161     /**
1162      * Y coordinate of the upper-left corner of this layer container within
1163      * its parent, in pixels.
1164      * @type Number
1165      */
1166     this.y = 0;
1167 
1168     /**
1169      * Z stacking order of this layer relative to other sibling layers.
1170      * @type Number
1171      */
1172     this.z = 0;
1173 
1174     /**
1175      * The affine transformation applied to this layer container. Each element
1176      * corresponds to a value from the transformation matrix, with the first
1177      * three values being the first row, and the last three values being the
1178      * second row. There are six values total.
1179      * 
1180      * @type Number[]
1181      */
1182     this.matrix = [1, 0, 0, 1, 0, 0];
1183 
1184     /**
1185      * The parent layer container of this layer, if any.
1186      * @type Guacamole.Display.LayerContainer
1187      */
1188     this.parent = null;
1189 
1190     /**
1191      * Set of all children of this layer, indexed by layer index. This object
1192      * will have one property per child.
1193      */
1194     this.children = {};
1195 
1196     // Set layer position
1197     var canvas = layer.getCanvas();
1198     canvas.style.position = "absolute";
1199     canvas.style.left = "0px";
1200     canvas.style.top = "0px";
1201 
1202     // Create div with given size
1203     var div = document.createElement("div");
1204     div.appendChild(canvas);
1205     div.style.width = width + "px";
1206     div.style.height = height + "px";
1207     div.style.position = "absolute";
1208     div.style.left = "0px";
1209     div.style.top = "0px";
1210     div.style.overflow = "hidden";
1211 
1212     /**
1213      * Superclass resize() function.
1214      * @private
1215      */
1216     var __super_resize = this.resize;
1217 
1218     this.resize = function(width, height) {
1219 
1220         // Resize containing div
1221         div.style.width = width + "px";
1222         div.style.height = height + "px";
1223 
1224         __super_resize(width, height);
1225 
1226     };
1227   
1228     /**
1229      * Returns the element containing the canvas and any other elements
1230      * associated with this layer.
1231      * @returns {Element} The element containing this layer's canvas.
1232      */
1233     this.getElement = function() {
1234         return div;
1235     };
1236 
1237     /**
1238      * The translation component of this layer's transform.
1239      * @private
1240      */
1241     var translate = "translate(0px, 0px)"; // (0, 0)
1242 
1243     /**
1244      * The arbitrary matrix component of this layer's transform.
1245      * @private
1246      */
1247     var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
1248 
1249     /**
1250      * Moves the upper-left corner of this layer to the given X and Y
1251      * coordinate.
1252      * 
1253      * @param {Number} x The X coordinate to move to.
1254      * @param {Number} y The Y coordinate to move to.
1255      */
1256     this.translate = function(x, y) {
1257 
1258         layer.x = x;
1259         layer.y = y;
1260 
1261         // Generate translation
1262         translate = "translate("
1263                         + x + "px,"
1264                         + y + "px)";
1265 
1266         // Set layer transform 
1267         div.style.transform =
1268         div.style.WebkitTransform =
1269         div.style.MozTransform =
1270         div.style.OTransform =
1271         div.style.msTransform =
1272 
1273             translate + " " + matrix;
1274 
1275     };
1276 
1277     /**
1278      * Moves the upper-left corner of this LayerContainer to the given X and Y
1279      * coordinate, sets the Z stacking order, and reparents this LayerContainer
1280      * to the given LayerContainer.
1281      * 
1282      * @param {Guacamole.Display.LayerContainer} parent The parent to set.
1283      * @param {Number} x The X coordinate to move to.
1284      * @param {Number} y The Y coordinate to move to.
1285      * @param {Number} z The Z coordinate to move to.
1286      */
1287     this.move = function(parent, x, y, z) {
1288 
1289         // Set parent if necessary
1290         if (layer.parent !== parent) {
1291 
1292             // Maintain relationship
1293             if (layer.parent)
1294                 delete layer.parent.children[layer.__unique_id];
1295             layer.parent = parent;
1296             parent.children[layer.__unique_id] = layer;
1297 
1298             // Reparent element
1299             var parent_element = parent.getElement();
1300             parent_element.appendChild(div);
1301 
1302         }
1303 
1304         // Set location
1305         layer.translate(x, y);
1306         layer.z = z;
1307         div.style.zIndex = z;
1308 
1309     };
1310 
1311     /**
1312      * Sets the opacity of this layer to the given value, where 255 is fully
1313      * opaque and 0 is fully transparent.
1314      * 
1315      * @param {Number} a The opacity to set.
1316      */
1317     this.shade = function(a) {
1318         layer.alpha = a;
1319         div.style.opacity = a/255.0;
1320     };
1321 
1322     /**
1323      * Removes this layer container entirely, such that it is no longer
1324      * contained within its parent layer, if any.
1325      */
1326     this.dispose = function() {
1327 
1328         // Remove from parent container
1329         if (layer.parent) {
1330             delete layer.parent.children[layer.__unique_id];
1331             layer.parent = null;
1332         }
1333 
1334         // Remove from parent element
1335         if (div.parentNode)
1336             div.parentNode.removeChild(div);
1337         
1338     };
1339 
1340     /**
1341      * Applies the given affine transform (defined with six values from the
1342      * transform's matrix).
1343      * 
1344      * @param {Number} a The first value in the affine transform's matrix.
1345      * @param {Number} b The second value in the affine transform's matrix.
1346      * @param {Number} c The third value in the affine transform's matrix.
1347      * @param {Number} d The fourth value in the affine transform's matrix.
1348      * @param {Number} e The fifth value in the affine transform's matrix.
1349      * @param {Number} f The sixth value in the affine transform's matrix.
1350      */
1351     this.distort = function(a, b, c, d, e, f) {
1352 
1353         // Store matrix
1354         layer.matrix = [a, b, c, d, e, f];
1355 
1356         // Generate matrix transformation
1357         matrix =
1358 
1359             /* a c e
1360              * b d f
1361              * 0 0 1
1362              */
1363     
1364             "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
1365 
1366         // Set layer transform 
1367         div.style.transform =
1368         div.style.WebkitTransform =
1369         div.style.MozTransform =
1370         div.style.OTransform =
1371         div.style.msTransform =
1372 
1373             translate + " " + matrix;
1374 
1375     };
1376 
1377 };
1378 
1379 /**
1380  * The next identifier to be assigned to the layer container. This identifier
1381  * uniquely identifies each LayerContainer, but is unrelated to the index of
1382  * the layer, which exists at the protocol/client level only.
1383  * 
1384  * @private
1385  * @type Number
1386  */
1387 Guacamole.Display.VisibleLayer.__next_id = 0;
1388