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