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 at the specified URL at the given coordinates. The image
532      * will be loaded automatically, and this and any future operations will
533      * wait for the image to finish loading.
534      * 
535      * @param {Guacamole.Layer} layer The layer to draw upon.
536      * @param {Number} x The destination X coordinate.
537      * @param {Number} y The destination Y coordinate.
538      * @param {String} url The URL of the image to draw.
539      */
540     this.draw = function(layer, x, y, url) {
541 
542         var task = scheduleTask(function __display_draw() {
543             layer.drawImage(x, y, image);
544         }, true);
545 
546         var image = new Image();
547         image.onload = task.unblock;
548         image.src = url;
549 
550     };
551 
552     /**
553      * Plays the video at the specified URL within this layer. The video
554      * will be loaded automatically, and this and any future operations will
555      * wait for the video to finish loading. Future operations will not be
556      * executed until the video finishes playing.
557      * 
558      * @param {Guacamole.Layer} layer The layer to draw upon.
559      * @param {String} mimetype The mimetype of the video to play.
560      * @param {Number} duration The duration of the video in milliseconds.
561      * @param {String} url The URL of the video to play.
562      */
563     this.play = function(layer, mimetype, duration, url) {
564 
565         // Start loading the video
566         var video = document.createElement("video");
567         video.type = mimetype;
568         video.src = url;
569 
570         // Start copying frames when playing
571         video.addEventListener("play", function() {
572             
573             function render_callback() {
574                 layer.drawImage(0, 0, video);
575                 if (!video.ended)
576                     window.setTimeout(render_callback, 20);
577             }
578             
579             render_callback();
580             
581         }, false);
582 
583         scheduleTask(video.play);
584 
585     };
586 
587     /**
588      * Transfer a rectangle of image data from one Layer to this Layer using the
589      * specified transfer function.
590      * 
591      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
592      * @param {Number} srcx The X coordinate of the upper-left corner of the
593      *                      rectangle within the source Layer's coordinate
594      *                      space to copy data from.
595      * @param {Number} srcy The Y coordinate of the upper-left corner of the
596      *                      rectangle within the source Layer's coordinate
597      *                      space to copy data from.
598      * @param {Number} srcw The width of the rectangle within the source Layer's
599      *                      coordinate space to copy data from.
600      * @param {Number} srch The height of the rectangle within the source
601      *                      Layer's coordinate space to copy data from.
602      * @param {Guacamole.Layer} dstLayer The layer to draw upon.
603      * @param {Number} x The destination X coordinate.
604      * @param {Number} y The destination Y coordinate.
605      * @param {Function} transferFunction The transfer function to use to
606      *                                    transfer data from source to
607      *                                    destination.
608      */
609     this.transfer = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y, transferFunction) {
610         scheduleTask(function __display_transfer() {
611             dstLayer.transfer(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction);
612         });
613     };
614 
615     /**
616      * Put a rectangle of image data from one Layer to this Layer directly
617      * without performing any alpha blending. Simply copy the data.
618      * 
619      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
620      * @param {Number} srcx The X coordinate of the upper-left corner of the
621      *                      rectangle within the source Layer's coordinate
622      *                      space to copy data from.
623      * @param {Number} srcy The Y coordinate of the upper-left corner of the
624      *                      rectangle within the source Layer's coordinate
625      *                      space to copy data from.
626      * @param {Number} srcw The width of the rectangle within the source Layer's
627      *                      coordinate space to copy data from.
628      * @param {Number} srch The height of the rectangle within the source
629      *                      Layer's coordinate space to copy data from.
630      * @param {Guacamole.Layer} dstLayer The layer to draw upon.
631      * @param {Number} x The destination X coordinate.
632      * @param {Number} y The destination Y coordinate.
633      */
634     this.put = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
635         scheduleTask(function __display_put() {
636             dstLayer.put(srcLayer, srcx, srcy, srcw, srch, x, y);
637         });
638     };
639 
640     /**
641      * Copy a rectangle of image data from one Layer to this Layer. This
642      * operation will copy exactly the image data that will be drawn once all
643      * operations of the source Layer that were pending at the time this
644      * function was called are complete. This operation will not alter the
645      * size of the source Layer even if its autosize property is set to true.
646      * 
647      * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
648      * @param {Number} srcx The X coordinate of the upper-left corner of the
649      *                      rectangle within the source Layer's coordinate
650      *                      space to copy data from.
651      * @param {Number} srcy The Y coordinate of the upper-left corner of the
652      *                      rectangle within the source Layer's coordinate
653      *                      space to copy data from.
654      * @param {Number} srcw The width of the rectangle within the source Layer's
655      *                      coordinate space to copy data from.
656      * @param {Number} srch The height of the rectangle within the source
657      *                      Layer's coordinate space to copy data from.
658      * @param {Guacamole.Layer} dstLayer The layer to draw upon.
659      * @param {Number} x The destination X coordinate.
660      * @param {Number} y The destination Y coordinate.
661      */
662     this.copy = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) {
663         scheduleTask(function __display_copy() {
664             dstLayer.copy(srcLayer, srcx, srcy, srcw, srch, x, y);
665         });
666     };
667 
668     /**
669      * Starts a new path at the specified point.
670      * 
671      * @param {Guacamole.Layer} layer The layer to draw upon.
672      * @param {Number} x The X coordinate of the point to draw.
673      * @param {Number} y The Y coordinate of the point to draw.
674      */
675     this.moveTo = function(layer, x, y) {
676         scheduleTask(function __display_moveTo() {
677             layer.moveTo(x, y);
678         });
679     };
680 
681     /**
682      * Add the specified line to the current path.
683      * 
684      * @param {Guacamole.Layer} layer The layer to draw upon.
685      * @param {Number} x The X coordinate of the endpoint of the line to draw.
686      * @param {Number} y The Y coordinate of the endpoint of the line to draw.
687      */
688     this.lineTo = function(layer, x, y) {
689         scheduleTask(function __display_lineTo() {
690             layer.lineTo(x, y);
691         });
692     };
693 
694     /**
695      * Add the specified arc to the current path.
696      * 
697      * @param {Guacamole.Layer} layer The layer to draw upon.
698      * @param {Number} x The X coordinate of the center of the circle which
699      *                   will contain the arc.
700      * @param {Number} y The Y coordinate of the center of the circle which
701      *                   will contain the arc.
702      * @param {Number} radius The radius of the circle.
703      * @param {Number} startAngle The starting angle of the arc, in radians.
704      * @param {Number} endAngle The ending angle of the arc, in radians.
705      * @param {Boolean} negative Whether the arc should be drawn in order of
706      *                           decreasing angle.
707      */
708     this.arc = function(layer, x, y, radius, startAngle, endAngle, negative) {
709         scheduleTask(function __display_arc() {
710             layer.arc(x, y, radius, startAngle, endAngle, negative);
711         });
712     };
713 
714     /**
715      * Starts a new path at the specified point.
716      * 
717      * @param {Guacamole.Layer} layer The layer to draw upon.
718      * @param {Number} cp1x The X coordinate of the first control point.
719      * @param {Number} cp1y The Y coordinate of the first control point.
720      * @param {Number} cp2x The X coordinate of the second control point.
721      * @param {Number} cp2y The Y coordinate of the second control point.
722      * @param {Number} x The X coordinate of the endpoint of the curve.
723      * @param {Number} y The Y coordinate of the endpoint of the curve.
724      */
725     this.curveTo = function(layer, cp1x, cp1y, cp2x, cp2y, x, y) {
726         scheduleTask(function __display_curveTo() {
727             layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
728         });
729     };
730 
731     /**
732      * Closes the current path by connecting the end point with the start
733      * point (if any) with a straight line.
734      * 
735      * @param {Guacamole.Layer} layer The layer to draw upon.
736      */
737     this.close = function(layer) {
738         scheduleTask(function __display_close() {
739             layer.close();
740         });
741     };
742 
743     /**
744      * Add the specified rectangle to the current path.
745      * 
746      * @param {Guacamole.Layer} layer The layer to draw upon.
747      * @param {Number} x The X coordinate of the upper-left corner of the
748      *                   rectangle to draw.
749      * @param {Number} y The Y coordinate of the upper-left corner of the
750      *                   rectangle to draw.
751      * @param {Number} w The width of the rectangle to draw.
752      * @param {Number} h The height of the rectangle to draw.
753      */
754     this.rect = function(layer, x, y, w, h) {
755         scheduleTask(function __display_rect() {
756             layer.rect(x, y, w, h);
757         });
758     };
759 
760     /**
761      * Clip all future drawing operations by the current path. The current path
762      * is implicitly closed. The current path can continue to be reused
763      * for other operations (such as fillColor()) but a new path will be started
764      * once a path drawing operation (path() or rect()) is used.
765      * 
766      * @param {Guacamole.Layer} layer The layer to affect.
767      */
768     this.clip = function(layer) {
769         scheduleTask(function __display_clip() {
770             layer.clip();
771         });
772     };
773 
774     /**
775      * Stroke the current path with the specified color. The current path
776      * is implicitly closed. The current path can continue to be reused
777      * for other operations (such as clip()) but a new path will be started
778      * once a path drawing operation (path() or rect()) is used.
779      * 
780      * @param {Guacamole.Layer} layer The layer to draw upon.
781      * @param {String} cap The line cap style. Can be "round", "square",
782      *                     or "butt".
783      * @param {String} join The line join style. Can be "round", "bevel",
784      *                      or "miter".
785      * @param {Number} thickness The line thickness in pixels.
786      * @param {Number} r The red component of the color to fill.
787      * @param {Number} g The green component of the color to fill.
788      * @param {Number} b The blue component of the color to fill.
789      * @param {Number} a The alpha component of the color to fill.
790      */
791     this.strokeColor = function(layer, cap, join, thickness, r, g, b, a) {
792         scheduleTask(function __display_strokeColor() {
793             layer.strokeColor(cap, join, thickness, r, g, b, a);
794         });
795     };
796 
797     /**
798      * Fills the current path with the specified color. The current path
799      * is implicitly closed. The current path can continue to be reused
800      * for other operations (such as clip()) but a new path will be started
801      * once a path drawing operation (path() or rect()) is used.
802      * 
803      * @param {Guacamole.Layer} layer The layer to draw upon.
804      * @param {Number} r The red component of the color to fill.
805      * @param {Number} g The green component of the color to fill.
806      * @param {Number} b The blue component of the color to fill.
807      * @param {Number} a The alpha component of the color to fill.
808      */
809     this.fillColor = function(layer, r, g, b, a) {
810         scheduleTask(function __display_fillColor() {
811             layer.fillColor(r, g, b, a);
812         });
813     };
814 
815     /**
816      * Stroke the current path with the image within the specified layer. The
817      * image data will be tiled infinitely within the stroke. The current path
818      * is implicitly closed. The current path can continue to be reused
819      * for other operations (such as clip()) but a new path will be started
820      * once a path drawing operation (path() or rect()) is used.
821      * 
822      * @param {Guacamole.Layer} layer The layer to draw upon.
823      * @param {String} cap The line cap style. Can be "round", "square",
824      *                     or "butt".
825      * @param {String} join The line join style. Can be "round", "bevel",
826      *                      or "miter".
827      * @param {Number} thickness The line thickness in pixels.
828      * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
829      *                                   within the stroke.
830      */
831     this.strokeLayer = function(layer, cap, join, thickness, srcLayer) {
832         scheduleTask(function __display_strokeLayer() {
833             layer.strokeLayer(cap, join, thickness, srcLayer);
834         });
835     };
836 
837     /**
838      * Fills the current path with the image within the specified layer. The
839      * image data will be tiled infinitely within the stroke. The current path
840      * is implicitly closed. The current path can continue to be reused
841      * for other operations (such as clip()) but a new path will be started
842      * once a path drawing operation (path() or rect()) is used.
843      * 
844      * @param {Guacamole.Layer} layer The layer to draw upon.
845      * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern
846      *                                   within the fill.
847      */
848     this.fillLayer = function(layer, srcLayer) {
849         scheduleTask(function __display_fillLayer() {
850             layer.fillLayer(srcLayer);
851         });
852     };
853 
854     /**
855      * Push current layer state onto stack.
856      * 
857      * @param {Guacamole.Layer} layer The layer to draw upon.
858      */
859     this.push = function(layer) {
860         scheduleTask(function __display_push() {
861             layer.push();
862         });
863     };
864 
865     /**
866      * Pop layer state off stack.
867      * 
868      * @param {Guacamole.Layer} layer The layer to draw upon.
869      */
870     this.pop = function(layer) {
871         scheduleTask(function __display_pop() {
872             layer.pop();
873         });
874     };
875 
876     /**
877      * Reset the layer, clearing the stack, the current path, and any transform
878      * matrix.
879      * 
880      * @param {Guacamole.Layer} layer The layer to draw upon.
881      */
882     this.reset = function(layer) {
883         scheduleTask(function __display_reset() {
884             layer.reset();
885         });
886     };
887 
888     /**
889      * Sets the given affine transform (defined with six values from the
890      * transform's matrix).
891      * 
892      * @param {Guacamole.Layer} layer The layer to modify.
893      * @param {Number} a The first value in the affine transform's matrix.
894      * @param {Number} b The second value in the affine transform's matrix.
895      * @param {Number} c The third value in the affine transform's matrix.
896      * @param {Number} d The fourth value in the affine transform's matrix.
897      * @param {Number} e The fifth value in the affine transform's matrix.
898      * @param {Number} f The sixth value in the affine transform's matrix.
899      */
900     this.setTransform = function(layer, a, b, c, d, e, f) {
901         scheduleTask(function __display_setTransform() {
902             layer.setTransform(a, b, c, d, e, f);
903         });
904     };
905 
906     /**
907      * Applies the given affine transform (defined with six values from the
908      * transform's matrix).
909      * 
910      * @param {Guacamole.Layer} layer The layer to modify.
911      * @param {Number} a The first value in the affine transform's matrix.
912      * @param {Number} b The second value in the affine transform's matrix.
913      * @param {Number} c The third value in the affine transform's matrix.
914      * @param {Number} d The fourth value in the affine transform's matrix.
915      * @param {Number} e The fifth value in the affine transform's matrix.
916      * @param {Number} f The sixth value in the affine transform's matrix.
917      */
918     this.transform = function(layer, a, b, c, d, e, f) {
919         scheduleTask(function __display_transform() {
920             layer.transform(a, b, c, d, e, f);
921         });
922     };
923 
924     /**
925      * Sets the channel mask for future operations on this Layer.
926      * 
927      * The channel mask is a Guacamole-specific compositing operation identifier
928      * with a single bit representing each of four channels (in order): source
929      * image where destination transparent, source where destination opaque,
930      * destination where source transparent, and destination where source
931      * opaque.
932      * 
933      * @param {Guacamole.Layer} layer The layer to modify.
934      * @param {Number} mask The channel mask for future operations on this
935      *                      Layer.
936      */
937     this.setChannelMask = function(layer, mask) {
938         scheduleTask(function __display_setChannelMask() {
939             layer.setChannelMask(mask);
940         });
941     };
942 
943     /**
944      * Sets the miter limit for stroke operations using the miter join. This
945      * limit is the maximum ratio of the size of the miter join to the stroke
946      * width. If this ratio is exceeded, the miter will not be drawn for that
947      * joint of the path.
948      * 
949      * @param {Guacamole.Layer} layer The layer to modify.
950      * @param {Number} limit The miter limit for stroke operations using the
951      *                       miter join.
952      */
953     this.setMiterLimit = function(layer, limit) {
954         scheduleTask(function __display_setMiterLimit() {
955             layer.setMiterLimit(limit);
956         });
957     };
958 
959     /**
960      * Sets the scale of the client display element such that it renders at
961      * a relatively smaller or larger size, without affecting the true
962      * resolution of the display.
963      *
964      * @param {Number} scale The scale to resize to, where 1.0 is normal
965      *                       size (1:1 scale).
966      */
967     this.scale = function(scale) {
968 
969         display.style.transform =
970         display.style.WebkitTransform =
971         display.style.MozTransform =
972         display.style.OTransform =
973         display.style.msTransform =
974 
975             "scale(" + scale + "," + scale + ")";
976 
977         displayScale = scale;
978 
979         // Update bounds size
980         bounds.style.width = (displayWidth*displayScale) + "px";
981         bounds.style.height = (displayHeight*displayScale) + "px";
982 
983     };
984 
985     /**
986      * Returns the scale of the display.
987      *
988      * @return {Number} The scale of the display.
989      */
990     this.getScale = function() {
991         return displayScale;
992     };
993 
994     /**
995      * Returns a canvas element containing the entire display, with all child
996      * layers composited within.
997      *
998      * @return {HTMLCanvasElement} A new canvas element containing a copy of
999      *                             the display.
1000      */
1001     this.flatten = function() {
1002        
1003         // Get destination canvas
1004         var canvas = document.createElement("canvas");
1005         canvas.width = default_layer.width;
1006         canvas.height = default_layer.height;
1007 
1008         var context = canvas.getContext("2d");
1009 
1010         // Returns sorted array of children
1011         function get_children(layer) {
1012 
1013             // Build array of children
1014             var children = [];
1015             for (var index in layer.children)
1016                 children.push(layer.children[index]);
1017 
1018             // Sort
1019             children.sort(function children_comparator(a, b) {
1020 
1021                 // Compare based on Z order
1022                 var diff = a.z - b.z;
1023                 if (diff !== 0)
1024                     return diff;
1025 
1026                 // If Z order identical, use document order
1027                 var a_element = a.getElement();
1028                 var b_element = b.getElement();
1029                 var position = b_element.compareDocumentPosition(a_element);
1030 
1031                 if (position & Node.DOCUMENT_POSITION_PRECEDING) return -1;
1032                 if (position & Node.DOCUMENT_POSITION_FOLLOWING) return  1;
1033 
1034                 // Otherwise, assume same
1035                 return 0;
1036 
1037             });
1038 
1039             // Done
1040             return children;
1041 
1042         }
1043 
1044         // Draws the contents of the given layer at the given coordinates
1045         function draw_layer(layer, x, y) {
1046 
1047             // Draw layer
1048             if (layer.width > 0 && layer.height > 0) {
1049 
1050                 // Save and update alpha
1051                 var initial_alpha = context.globalAlpha;
1052                 context.globalAlpha *= layer.alpha / 255.0;
1053 
1054                 // Copy data
1055                 context.drawImage(layer.getCanvas(), x, y);
1056 
1057                 // Draw all children
1058                 var children = get_children(layer);
1059                 for (var i=0; i<children.length; i++) {
1060                     var child = children[i];
1061                     draw_layer(child, x + child.x, y + child.y);
1062                 }
1063 
1064                 // Restore alpha
1065                 context.globalAlpha = initial_alpha;
1066 
1067             }
1068 
1069         }
1070 
1071         // Draw default layer and all children
1072         draw_layer(default_layer, 0, 0);
1073 
1074         // Return new canvas copy
1075         return canvas;
1076         
1077     };
1078 
1079 };
1080 
1081 /**
1082  * Simple container for Guacamole.Layer, allowing layers to be easily
1083  * repositioned and nested. This allows certain operations to be accelerated
1084  * through DOM manipulation, rather than raster operations.
1085  * 
1086  * @constructor
1087  * @augments Guacamole.Layer
1088  * @param {Number} width The width of the Layer, in pixels. The canvas element
1089  *                       backing this Layer will be given this width.
1090  * @param {Number} height The height of the Layer, in pixels. The canvas element
1091  *                        backing this Layer will be given this height.
1092  */
1093 Guacamole.Display.VisibleLayer = function(width, height) {
1094 
1095     Guacamole.Layer.apply(this, [width, height]);
1096 
1097     /**
1098      * Reference to this layer.
1099      * @private
1100      */
1101     var layer = this;
1102 
1103     /**
1104      * Identifier which uniquely identifies this layer. This is COMPLETELY
1105      * UNRELATED to the index of the underlying layer, which is specific
1106      * to the Guacamole protocol, and not relevant at this level.
1107      * 
1108      * @private
1109      * @type Number
1110      */
1111     this.__unique_id = Guacamole.Display.VisibleLayer.__next_id++;
1112 
1113     /**
1114      * The opacity of the layer container, where 255 is fully opaque and 0 is
1115      * fully transparent.
1116      */
1117     this.alpha = 0xFF;
1118 
1119     /**
1120      * X coordinate of the upper-left corner of this layer container within
1121      * its parent, in pixels.
1122      * @type Number
1123      */
1124     this.x = 0;
1125 
1126     /**
1127      * Y coordinate of the upper-left corner of this layer container within
1128      * its parent, in pixels.
1129      * @type Number
1130      */
1131     this.y = 0;
1132 
1133     /**
1134      * Z stacking order of this layer relative to other sibling layers.
1135      * @type Number
1136      */
1137     this.z = 0;
1138 
1139     /**
1140      * The affine transformation applied to this layer container. Each element
1141      * corresponds to a value from the transformation matrix, with the first
1142      * three values being the first row, and the last three values being the
1143      * second row. There are six values total.
1144      * 
1145      * @type Number[]
1146      */
1147     this.matrix = [1, 0, 0, 1, 0, 0];
1148 
1149     /**
1150      * The parent layer container of this layer, if any.
1151      * @type Guacamole.Display.LayerContainer
1152      */
1153     this.parent = null;
1154 
1155     /**
1156      * Set of all children of this layer, indexed by layer index. This object
1157      * will have one property per child.
1158      */
1159     this.children = {};
1160 
1161     // Set layer position
1162     var canvas = layer.getCanvas();
1163     canvas.style.position = "absolute";
1164     canvas.style.left = "0px";
1165     canvas.style.top = "0px";
1166 
1167     // Create div with given size
1168     var div = document.createElement("div");
1169     div.appendChild(canvas);
1170     div.style.width = width + "px";
1171     div.style.height = height + "px";
1172     div.style.position = "absolute";
1173     div.style.left = "0px";
1174     div.style.top = "0px";
1175     div.style.overflow = "hidden";
1176 
1177     /**
1178      * Superclass resize() function.
1179      * @private
1180      */
1181     var __super_resize = this.resize;
1182 
1183     this.resize = function(width, height) {
1184 
1185         // Resize containing div
1186         div.style.width = width + "px";
1187         div.style.height = height + "px";
1188 
1189         __super_resize(width, height);
1190 
1191     };
1192   
1193     /**
1194      * Returns the element containing the canvas and any other elements
1195      * associated with this layer.
1196      * @returns {Element} The element containing this layer's canvas.
1197      */
1198     this.getElement = function() {
1199         return div;
1200     };
1201 
1202     /**
1203      * The translation component of this layer's transform.
1204      * @private
1205      */
1206     var translate = "translate(0px, 0px)"; // (0, 0)
1207 
1208     /**
1209      * The arbitrary matrix component of this layer's transform.
1210      * @private
1211      */
1212     var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
1213 
1214     /**
1215      * Moves the upper-left corner of this layer to the given X and Y
1216      * coordinate.
1217      * 
1218      * @param {Number} x The X coordinate to move to.
1219      * @param {Number} y The Y coordinate to move to.
1220      */
1221     this.translate = function(x, y) {
1222 
1223         layer.x = x;
1224         layer.y = y;
1225 
1226         // Generate translation
1227         translate = "translate("
1228                         + x + "px,"
1229                         + y + "px)";
1230 
1231         // Set layer transform 
1232         div.style.transform =
1233         div.style.WebkitTransform =
1234         div.style.MozTransform =
1235         div.style.OTransform =
1236         div.style.msTransform =
1237 
1238             translate + " " + matrix;
1239 
1240     };
1241 
1242     /**
1243      * Moves the upper-left corner of this LayerContainer to the given X and Y
1244      * coordinate, sets the Z stacking order, and reparents this LayerContainer
1245      * to the given LayerContainer.
1246      * 
1247      * @param {Guacamole.Display.LayerContainer} parent The parent to set.
1248      * @param {Number} x The X coordinate to move to.
1249      * @param {Number} y The Y coordinate to move to.
1250      * @param {Number} z The Z coordinate to move to.
1251      */
1252     this.move = function(parent, x, y, z) {
1253 
1254         // Set parent if necessary
1255         if (layer.parent !== parent) {
1256 
1257             // Maintain relationship
1258             if (layer.parent)
1259                 delete layer.parent.children[layer.__unique_id];
1260             layer.parent = parent;
1261             parent.children[layer.__unique_id] = layer;
1262 
1263             // Reparent element
1264             var parent_element = parent.getElement();
1265             parent_element.appendChild(div);
1266 
1267         }
1268 
1269         // Set location
1270         layer.translate(x, y);
1271         layer.z = z;
1272         div.style.zIndex = z;
1273 
1274     };
1275 
1276     /**
1277      * Sets the opacity of this layer to the given value, where 255 is fully
1278      * opaque and 0 is fully transparent.
1279      * 
1280      * @param {Number} a The opacity to set.
1281      */
1282     this.shade = function(a) {
1283         layer.alpha = a;
1284         div.style.opacity = a/255.0;
1285     };
1286 
1287     /**
1288      * Removes this layer container entirely, such that it is no longer
1289      * contained within its parent layer, if any.
1290      */
1291     this.dispose = function() {
1292 
1293         // Remove from parent container
1294         if (layer.parent) {
1295             delete layer.parent.children[layer.__unique_id];
1296             layer.parent = null;
1297         }
1298 
1299         // Remove from parent element
1300         if (div.parentNode)
1301             div.parentNode.removeChild(div);
1302         
1303     };
1304 
1305     /**
1306      * Applies the given affine transform (defined with six values from the
1307      * transform's matrix).
1308      * 
1309      * @param {Number} a The first value in the affine transform's matrix.
1310      * @param {Number} b The second value in the affine transform's matrix.
1311      * @param {Number} c The third value in the affine transform's matrix.
1312      * @param {Number} d The fourth value in the affine transform's matrix.
1313      * @param {Number} e The fifth value in the affine transform's matrix.
1314      * @param {Number} f The sixth value in the affine transform's matrix.
1315      */
1316     this.distort = function(a, b, c, d, e, f) {
1317 
1318         // Store matrix
1319         layer.matrix = [a, b, c, d, e, f];
1320 
1321         // Generate matrix transformation
1322         matrix =
1323 
1324             /* a c e
1325              * b d f
1326              * 0 0 1
1327              */
1328     
1329             "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
1330 
1331         // Set layer transform 
1332         div.style.transform =
1333         div.style.WebkitTransform =
1334         div.style.MozTransform =
1335         div.style.OTransform =
1336         div.style.msTransform =
1337 
1338             translate + " " + matrix;
1339 
1340     };
1341 
1342 };
1343 
1344 /**
1345  * The next identifier to be assigned to the layer container. This identifier
1346  * uniquely identifies each LayerContainer, but is unrelated to the index of
1347  * the layer, which exists at the protocol/client level only.
1348  * 
1349  * @private
1350  * @type Number
1351  */
1352 Guacamole.Display.VisibleLayer.__next_id = 0;
1353