71679c084e75a8add0716074c2fe1f69bdda4d2e
[guacamole-common-js.git] / src / main / resources / guacamole.js
1
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is guacamole-common-js.
16  *
17  * The Initial Developer of the Original Code is
18  * Michael Jumper.
19  * Portions created by the Initial Developer are Copyright (C) 2010
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * Matt Hortman
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK ***** */
38
39 // Guacamole namespace
40 var Guacamole = Guacamole || {};
41
42
43 /**
44  * Guacamole protocol client. Given a display element and {@link Guacamole.Tunnel},
45  * automatically handles incoming and outgoing Guacamole instructions via the
46  * provided tunnel, updating the display using one or more canvas elements.
47  * 
48  * @constructor
49  * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive
50  *                                  Guacamole instructions.
51  */
52 Guacamole.Client = function(tunnel) {
53
54     var guac_client = this;
55
56     var STATE_IDLE          = 0;
57     var STATE_CONNECTING    = 1;
58     var STATE_WAITING       = 2;
59     var STATE_CONNECTED     = 3;
60     var STATE_DISCONNECTING = 4;
61     var STATE_DISCONNECTED  = 5;
62
63     var currentState = STATE_IDLE;
64     
65     var currentTimestamp = 0;
66     var pingInterval = null;
67
68     var displayWidth = 0;
69     var displayHeight = 0;
70     var displayScale = 1;
71
72     /**
73      * Translation from Guacamole protocol line caps to Layer line caps.
74      */
75     var lineCap = {
76         0: "butt",
77         1: "round",
78         2: "square"
79     };
80
81     /**
82      * Translation from Guacamole protocol line caps to Layer line caps.
83      */
84     var lineJoin = {
85         0: "bevel",
86         1: "miter",
87         2: "round"
88     };
89
90     // Create bounding div 
91     var bounds = document.createElement("div");
92     bounds.style.position = "relative";
93     bounds.style.width = (displayWidth*displayScale) + "px";
94     bounds.style.height = (displayHeight*displayScale) + "px";
95
96     // Create display
97     var display = document.createElement("div");
98     display.style.position = "relative";
99     display.style.width = displayWidth + "px";
100     display.style.height = displayHeight + "px";
101
102     // Ensure transformations on display originate at 0,0
103     display.style.transformOrigin =
104     display.style.webkitTransformOrigin =
105     display.style.MozTransformOrigin =
106     display.style.OTransformOrigin =
107     display.style.msTransformOrigin =
108         "0 0";
109
110     // Create default layer
111     var default_layer_container = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
112
113     // Position default layer
114     var default_layer_container_element = default_layer_container.getElement();
115     default_layer_container_element.style.position = "absolute";
116     default_layer_container_element.style.left = "0px";
117     default_layer_container_element.style.top  = "0px";
118     default_layer_container_element.style.overflow = "hidden";
119
120     // Create cursor layer
121     var cursor = new Guacamole.Client.LayerContainer(0, 0);
122     cursor.getLayer().setChannelMask(Guacamole.Layer.SRC);
123
124     // Position cursor layer
125     var cursor_element = cursor.getElement();
126     cursor_element.style.position = "absolute";
127     cursor_element.style.left = "0px";
128     cursor_element.style.top  = "0px";
129
130     // Add default layer and cursor to display
131     display.appendChild(default_layer_container.getElement());
132     display.appendChild(cursor.getElement());
133
134     // Add display to bounds
135     bounds.appendChild(display);
136
137     // Initially, only default layer exists
138     var layers =  [default_layer_container];
139
140     // No initial buffers
141     var buffers = [];
142
143     tunnel.onerror = function(message) {
144         if (guac_client.onerror)
145             guac_client.onerror(message);
146     };
147
148     function setState(state) {
149         if (state != currentState) {
150             currentState = state;
151             if (guac_client.onstatechange)
152                 guac_client.onstatechange(currentState);
153         }
154     }
155
156     function isConnected() {
157         return currentState == STATE_CONNECTED
158             || currentState == STATE_WAITING;
159     }
160
161     var cursorHotspotX = 0;
162     var cursorHotspotY = 0;
163
164     var cursorX = 0;
165     var cursorY = 0;
166
167     function moveCursor(x, y) {
168
169         // Move cursor layer
170         cursor.translate(x - cursorHotspotX, y - cursorHotspotY);
171
172         // Update stored position
173         cursorX = x;
174         cursorY = y;
175
176     }
177
178     guac_client.getDisplay = function() {
179         return bounds;
180     };
181
182     guac_client.sendKeyEvent = function(pressed, keysym) {
183         // Do not send requests if not connected
184         if (!isConnected())
185             return;
186
187         tunnel.sendMessage("key", keysym, pressed);
188     };
189
190     guac_client.sendMouseState = function(mouseState) {
191
192         // Do not send requests if not connected
193         if (!isConnected())
194             return;
195
196         // Update client-side cursor
197         moveCursor(
198             Math.floor(mouseState.x),
199             Math.floor(mouseState.y)
200         );
201
202         // Build mask
203         var buttonMask = 0;
204         if (mouseState.left)   buttonMask |= 1;
205         if (mouseState.middle) buttonMask |= 2;
206         if (mouseState.right)  buttonMask |= 4;
207         if (mouseState.up)     buttonMask |= 8;
208         if (mouseState.down)   buttonMask |= 16;
209
210         // Send message
211         tunnel.sendMessage("mouse", Math.floor(mouseState.x), Math.floor(mouseState.y), buttonMask);
212     };
213
214     guac_client.setClipboard = function(data) {
215
216         // Do not send requests if not connected
217         if (!isConnected())
218             return;
219
220         tunnel.sendMessage("clipboard", data);
221     };
222
223     // Handlers
224     guac_client.onstatechange = null;
225     guac_client.onname = null;
226     guac_client.onerror = null;
227     guac_client.onclipboard = null;
228
229     // Layers
230     function getBufferLayer(index) {
231
232         index = -1 - index;
233         var buffer = buffers[index];
234
235         // Create buffer if necessary
236         if (buffer == null) {
237             buffer = new Guacamole.Layer(0, 0);
238             buffer.autosize = 1;
239             buffers[index] = buffer;
240         }
241
242         return buffer;
243
244     }
245
246     function getLayerContainer(index) {
247
248         var layer = layers[index];
249         if (layer == null) {
250
251             // Add new layer
252             layer = new Guacamole.Client.LayerContainer(displayWidth, displayHeight);
253             layers[index] = layer;
254
255             // Get and position layer
256             var layer_element = layer.getElement();
257             layer_element.style.position = "absolute";
258             layer_element.style.left = "0px";
259             layer_element.style.top = "0px";
260             layer_element.style.overflow = "hidden";
261
262             // Add to default layer container
263             default_layer_container.getElement().appendChild(layer_element);
264
265         }
266
267         return layer;
268
269     }
270
271     function getLayer(index) {
272        
273         // If buffer, just get layer
274         if (index < 0)
275             return getBufferLayer(index);
276
277         // Otherwise, retrieve layer from layer container
278         return getLayerContainer(index).getLayer();
279
280     }
281
282     /**
283      * Handlers for all defined layer properties.
284      */
285     var layerPropertyHandlers = {
286
287         "miter-limit": function(layer, value) {
288             layer.setMiterLimit(parseFloat(value));
289         }
290
291     };
292     
293     /**
294      * Handlers for all instruction opcodes receivable by a Guacamole protocol
295      * client.
296      */
297     var instructionHandlers = {
298
299         "arc": function(parameters) {
300
301             var layer = getLayer(parseInt(parameters[0]));
302             var x = parseInt(parameters[1]);
303             var y = parseInt(parameters[2]);
304             var radius = parseInt(parameters[3]);
305             var startAngle = parseFloat(parameters[4]);
306             var endAngle = parseFloat(parameters[5]);
307             var negative = parseInt(parameters[6]);
308
309             layer.arc(x, y, radius, startAngle, endAngle, negative != 0);
310
311         },
312
313         "cfill": function(parameters) {
314
315             var channelMask = parseInt(parameters[0]);
316             var layer = getLayer(parseInt(parameters[1]));
317             var r = parseInt(parameters[2]);
318             var g = parseInt(parameters[3]);
319             var b = parseInt(parameters[4]);
320             var a = parseInt(parameters[5]);
321
322             layer.setChannelMask(channelMask);
323
324             layer.fillColor(r, g, b, a);
325
326         },
327
328         "clip": function(parameters) {
329
330             var layer = getLayer(parseInt(parameters[0]));
331
332             layer.clip();
333
334         },
335
336         "clipboard": function(parameters) {
337             if (guac_client.onclipboard) guac_client.onclipboard(parameters[0]);
338         },
339
340         "close": function(parameters) {
341
342             var layer = getLayer(parseInt(parameters[0]));
343
344             layer.close();
345
346         },
347
348         "copy": function(parameters) {
349
350             var srcL = getLayer(parseInt(parameters[0]));
351             var srcX = parseInt(parameters[1]);
352             var srcY = parseInt(parameters[2]);
353             var srcWidth = parseInt(parameters[3]);
354             var srcHeight = parseInt(parameters[4]);
355             var channelMask = parseInt(parameters[5]);
356             var dstL = getLayer(parseInt(parameters[6]));
357             var dstX = parseInt(parameters[7]);
358             var dstY = parseInt(parameters[8]);
359
360             dstL.setChannelMask(channelMask);
361
362             dstL.copy(
363                 srcL,
364                 srcX,
365                 srcY,
366                 srcWidth, 
367                 srcHeight, 
368                 dstX,
369                 dstY 
370             );
371
372         },
373
374         "cstroke": function(parameters) {
375
376             var channelMask = parseInt(parameters[0]);
377             var layer = getLayer(parseInt(parameters[1]));
378             var cap = lineCap[parseInt(parameters[2])];
379             var join = lineJoin[parseInt(parameters[3])];
380             var thickness = parseInt(parameters[4]);
381             var r = parseInt(parameters[5]);
382             var g = parseInt(parameters[6]);
383             var b = parseInt(parameters[7]);
384             var a = parseInt(parameters[8]);
385
386             layer.setChannelMask(channelMask);
387
388             layer.strokeColor(cap, join, thickness, r, g, b, a);
389
390         },
391
392         "cursor": function(parameters) {
393
394             cursorHotspotX = parseInt(parameters[0]);
395             cursorHotspotY = parseInt(parameters[1]);
396             var srcL = getLayer(parseInt(parameters[2]));
397             var srcX = parseInt(parameters[3]);
398             var srcY = parseInt(parameters[4]);
399             var srcWidth = parseInt(parameters[5]);
400             var srcHeight = parseInt(parameters[6]);
401
402             // Reset cursor size
403             cursor.resize(srcWidth, srcHeight);
404
405             // Draw cursor to cursor layer
406             cursor.getLayer().copy(
407                 srcL,
408                 srcX,
409                 srcY,
410                 srcWidth, 
411                 srcHeight, 
412                 0,
413                 0 
414             );
415
416             // Update cursor position (hotspot may have changed)
417             moveCursor(cursorX, cursorY);
418
419         },
420
421         "curve": function(parameters) {
422
423             var layer = getLayer(parseInt(parameters[0]));
424             var cp1x = parseInt(parameters[1]);
425             var cp1y = parseInt(parameters[2]);
426             var cp2x = parseInt(parameters[3]);
427             var cp2y = parseInt(parameters[4]);
428             var x = parseInt(parameters[5]);
429             var y = parseInt(parameters[6]);
430
431             layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y);
432
433         },
434
435         "dispose": function(parameters) {
436             
437             var layer_index = parseInt(parameters[0]);
438
439             // If visible layer, remove from parent
440             if (layer_index > 0) {
441
442                 // Get container element
443                 var layer_container = getLayerContainer(layer_index).getElement();
444
445                 // Remove from parent
446                 layer_container.parentNode.removeChild(layer_container);
447
448                 // Delete reference
449                 delete layers[layer_index];
450
451             }
452
453             // If buffer, just delete reference
454             else if (layer_index < 0)
455                 delete buffers[-1 - layer_index];
456
457             // Attempting to dispose the root layer currently has no effect.
458
459         },
460
461         "distort": function(parameters) {
462
463             var layer_index = parseInt(parameters[0]);
464             var a = parseFloat(parameters[1]);
465             var b = parseFloat(parameters[2]);
466             var c = parseFloat(parameters[3]);
467             var d = parseFloat(parameters[4]);
468             var e = parseFloat(parameters[5]);
469             var f = parseFloat(parameters[6]);
470
471             // Only valid for visible layers (not buffers)
472             if (layer_index >= 0) {
473
474                 // Get container element
475                 var layer_container = getLayerContainer(layer_index).getElement();
476
477                 // Set layer transform 
478                 layer_container.transform(a, b, c, d, e, f);
479
480              }
481
482         },
483  
484         "error": function(parameters) {
485             if (guac_client.onerror) guac_client.onerror(parameters[0]);
486             guac_client.disconnect();
487         },
488
489         "identity": function(parameters) {
490
491             var layer = getLayer(parseInt(parameters[0]));
492
493             layer.setTransform(1, 0, 0, 1, 0, 0);
494
495         },
496
497         "lfill": function(parameters) {
498
499             var channelMask = parseInt(parameters[0]);
500             var layer = getLayer(parseInt(parameters[1]));
501             var srcLayer = getLayer(parseInt(parameters[2]));
502
503             layer.setChannelMask(channelMask);
504
505             layer.fillLayer(srcLayer);
506
507         },
508
509         "line": function(parameters) {
510
511             var layer = getLayer(parseInt(parameters[0]));
512             var x = parseInt(parameters[1]);
513             var y = parseInt(parameters[2]);
514
515             layer.lineTo(x, y);
516
517         },
518
519         "lstroke": function(parameters) {
520
521             var channelMask = parseInt(parameters[0]);
522             var layer = getLayer(parseInt(parameters[1]));
523             var srcLayer = getLayer(parseInt(parameters[2]));
524
525             layer.setChannelMask(channelMask);
526
527             layer.strokeLayer(srcLayer);
528
529         },
530
531         "move": function(parameters) {
532             
533             var layer_index = parseInt(parameters[0]);
534             var parent_index = parseInt(parameters[1]);
535             var x = parseInt(parameters[2]);
536             var y = parseInt(parameters[3]);
537             var z = parseInt(parameters[4]);
538
539             // Only valid for non-default layers
540             if (layer_index > 0 && parent_index >= 0) {
541
542                 // Get container element
543                 var layer_container = getLayerContainer(layer_index);
544                 var layer_container_element = layer_container.getElement();
545                 var parent = getLayerContainer(parent_index).getElement();
546
547                 // Set parent if necessary
548                 if (!(layer_container_element.parentNode === parent))
549                     parent.appendChild(layer_container_element);
550
551                 // Move layer
552                 layer_container.translate(x, y);
553                 layer_container_element.style.zIndex = z;
554
555             }
556
557         },
558
559         "name": function(parameters) {
560             if (guac_client.onname) guac_client.onname(parameters[0]);
561         },
562
563         "png": function(parameters) {
564
565             var channelMask = parseInt(parameters[0]);
566             var layer = getLayer(parseInt(parameters[1]));
567             var x = parseInt(parameters[2]);
568             var y = parseInt(parameters[3]);
569             var data = parameters[4];
570
571             layer.setChannelMask(channelMask);
572
573             layer.draw(
574                 x,
575                 y,
576                 "data:image/png;base64," + data
577             );
578
579             // If received first update, no longer waiting.
580             if (currentState == STATE_WAITING)
581                 setState(STATE_CONNECTED);
582
583         },
584
585         "pop": function(parameters) {
586
587             var layer = getLayer(parseInt(parameters[0]));
588
589             layer.pop();
590
591         },
592
593         "push": function(parameters) {
594
595             var layer = getLayer(parseInt(parameters[0]));
596
597             layer.push();
598
599         },
600  
601         "rect": function(parameters) {
602
603             var layer = getLayer(parseInt(parameters[0]));
604             var x = parseInt(parameters[1]);
605             var y = parseInt(parameters[2]);
606             var w = parseInt(parameters[3]);
607             var h = parseInt(parameters[4]);
608
609             layer.rect(x, y, w, h);
610
611         },
612         
613         "reset": function(parameters) {
614
615             var layer = getLayer(parseInt(parameters[0]));
616
617             layer.reset();
618
619         },
620         
621         "set": function(parameters) {
622
623             var layer = getLayer(parseInt(parameters[0]));
624             var name = parameters[1];
625             var value = parameters[2];
626
627             // Call property handler if defined
628             var handler = layerPropertyHandlers[name];
629             if (handler)
630                 handler(layer, value);
631
632         },
633
634         "shade": function(parameters) {
635             
636             var layer_index = parseInt(parameters[0]);
637             var a = parseInt(parameters[1]);
638
639             // Only valid for visible layers (not buffers)
640             if (layer_index >= 0) {
641
642                 // Get container element
643                 var layer_container = getLayerContainer(layer_index).getElement();
644
645                 // Set layer opacity
646                 layer_container.style.opacity = a/255.0;
647
648             }
649
650         },
651
652         "size": function(parameters) {
653
654             var layer_index = parseInt(parameters[0]);
655             var width = parseInt(parameters[1]);
656             var height = parseInt(parameters[2]);
657
658             // Resize layer
659             var layer_container = getLayerContainer(layer_index);
660             layer_container.resize(width, height);
661
662             // If layer is default, resize display
663             if (layer_index == 0) {
664
665                 displayWidth = width;
666                 displayHeight = height;
667
668                 // Update (set) display size
669                 display.style.width = displayWidth + "px";
670                 display.style.height = displayHeight + "px";
671
672                 // Update bounds size
673                 bounds.style.width = (displayWidth*displayScale) + "px";
674                 bounds.style.height = (displayHeight*displayScale) + "px";
675
676             }
677
678         },
679         
680         "start": function(parameters) {
681
682             var layer = getLayer(parseInt(parameters[0]));
683             var x = parseInt(parameters[1]);
684             var y = parseInt(parameters[2]);
685
686             layer.moveTo(x, y);
687
688         },
689
690         "sync": function(parameters) {
691
692             var timestamp = parameters[0];
693
694             // When all layers have finished rendering all instructions
695             // UP TO THIS POINT IN TIME, send sync response.
696
697             var layersToSync = 0;
698             function syncLayer() {
699
700                 layersToSync--;
701
702                 // Send sync response when layers are finished
703                 if (layersToSync == 0) {
704                     if (timestamp != currentTimestamp) {
705                         tunnel.sendMessage("sync", timestamp);
706                         currentTimestamp = timestamp;
707                     }
708                 }
709
710             }
711
712             // Count active, not-ready layers and install sync tracking hooks
713             for (var i=0; i<layers.length; i++) {
714
715                 var layer = layers[i].getLayer();
716                 if (layer && !layer.isReady()) {
717                     layersToSync++;
718                     layer.sync(syncLayer);
719                 }
720
721             }
722
723             // If all layers are ready, then we didn't install any hooks.
724             // Send sync message now,
725             if (layersToSync == 0) {
726                 if (timestamp != currentTimestamp) {
727                     tunnel.sendMessage("sync", timestamp);
728                     currentTimestamp = timestamp;
729                 }
730             }
731
732         },
733
734         "transfer": function(parameters) {
735
736             var srcL = getLayer(parseInt(parameters[0]));
737             var srcX = parseInt(parameters[1]);
738             var srcY = parseInt(parameters[2]);
739             var srcWidth = parseInt(parameters[3]);
740             var srcHeight = parseInt(parameters[4]);
741             var transferFunction = Guacamole.Client.DefaultTransferFunction[parameters[5]];
742             var dstL = getLayer(parseInt(parameters[6]));
743             var dstX = parseInt(parameters[7]);
744             var dstY = parseInt(parameters[8]);
745
746             dstL.transfer(
747                 srcL,
748                 srcX,
749                 srcY,
750                 srcWidth, 
751                 srcHeight, 
752                 dstX,
753                 dstY,
754                 transferFunction
755             );
756
757         },
758
759         "transform": function(parameters) {
760
761             var layer = getLayer(parseInt(parameters[0]));
762             var a = parseFloat(parameters[1]);
763             var b = parseFloat(parameters[2]);
764             var c = parseFloat(parameters[3]);
765             var d = parseFloat(parameters[4]);
766             var e = parseFloat(parameters[5]);
767             var f = parseFloat(parameters[6]);
768
769             layer.transform(a, b, c, d, e, f);
770
771         }
772       
773     };
774
775
776     tunnel.oninstruction = function(opcode, parameters) {
777
778         var handler = instructionHandlers[opcode];
779         if (handler)
780             handler(parameters);
781
782     };
783
784
785     guac_client.disconnect = function() {
786
787         // Only attempt disconnection not disconnected.
788         if (currentState != STATE_DISCONNECTED
789                 && currentState != STATE_DISCONNECTING) {
790
791             setState(STATE_DISCONNECTING);
792
793             // Stop ping
794             if (pingInterval)
795                 window.clearInterval(pingInterval);
796
797             // Send disconnect message and disconnect
798             tunnel.sendMessage("disconnect");
799             tunnel.disconnect();
800             setState(STATE_DISCONNECTED);
801
802         }
803
804     };
805     
806     guac_client.connect = function(data) {
807
808         setState(STATE_CONNECTING);
809
810         try {
811             tunnel.connect(data);
812         }
813         catch (e) {
814             setState(STATE_IDLE);
815             throw e;
816         }
817
818         // Ping every 5 seconds (ensure connection alive)
819         pingInterval = window.setInterval(function() {
820             tunnel.sendMessage("sync", currentTimestamp);
821         }, 5000);
822
823         setState(STATE_WAITING);
824     };
825
826     guac_client.scale = function(scale) {
827
828         display.style.transform =
829         display.style.WebkitTransform =
830         display.style.MozTransform =
831         display.style.OTransform =
832         display.style.msTransform =
833
834             "scale(" + scale + "," + scale + ")";
835
836         displayScale = scale;
837
838         // Update bounds size
839         bounds.style.width = (displayWidth*displayScale) + "px";
840         bounds.style.height = (displayHeight*displayScale) + "px";
841
842     };
843
844     guac_client.getScale = function() {
845         return displayScale;
846     };
847
848 };
849
850 /**
851  * Simple container for Guacamole.Layer, allowing layers to be easily
852  * repositioned and nested. This allows certain operations to be accelerated
853  * through DOM manipulation, rather than raster operations.
854  * 
855  * @constructor
856  * 
857  * @param {Number} width The width of the Layer, in pixels. The canvas element
858  *                       backing this Layer will be given this width.
859  *                       
860  * @param {Number} height The height of the Layer, in pixels. The canvas element
861  *                        backing this Layer will be given this height.
862  */
863 Guacamole.Client.LayerContainer = function(width, height) {
864
865     /**
866      * Reference to this LayerContainer.
867      * @private
868      */
869     var layer_container = this;
870
871     // Create layer with given size
872     var layer = new Guacamole.Layer(width, height);
873
874     // Set layer position
875     var canvas = layer.getCanvas();
876     canvas.style.position = "absolute";
877     canvas.style.left = "0px";
878     canvas.style.top = "0px";
879
880     // Create div with given size
881     var div = document.createElement("div");
882     div.appendChild(canvas);
883     div.style.width = width + "px";
884     div.style.height = height + "px";
885
886     /**
887      * Changes the size of this LayerContainer and the contained Layer to the
888      * given width and height.
889      * 
890      * @param {Number} width The new width to assign to this Layer.
891      * @param {Number} height The new height to assign to this Layer.
892      */
893     layer_container.resize = function(width, height) {
894
895         // Resize layer
896         layer.resize(width, height);
897
898         // Resize containing div
899         div.style.width = width + "px";
900         div.style.height = height + "px";
901
902     };
903   
904     /**
905      * Returns the Layer contained within this LayerContainer.
906      * @returns {Guacamole.Layer} The Layer contained within this LayerContainer.
907      */
908     layer_container.getLayer = function() {
909         return layer;
910     };
911
912     /**
913      * Returns the element containing the Layer within this LayerContainer.
914      * @returns {Element} The element containing the Layer within this LayerContainer.
915      */
916     layer_container.getElement = function() {
917         return div;
918     };
919
920     /**
921      * The translation component of this LayerContainer's transform.
922      */
923     var translate = "translate(0px, 0px)"; // (0, 0)
924
925     /**
926      * The arbitrary matrix component of this LayerContainer's transform.
927      */
928     var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
929
930     /**
931      * Moves the upper-left corner of this LayerContainer to the given X and Y
932      * coordinate.
933      * 
934      * @param {Number} x The X coordinate to move to.
935      * @param {Number} y The Y coordinate to move to.
936      */
937     layer_container.translate = function(x, y) {
938
939         // Generate translation
940         translate = "translate("
941                         + x + "px,"
942                         + y + "px)";
943
944         // Set layer transform 
945         div.style.transform =
946         div.style.WebkitTransform =
947         div.style.MozTransform =
948         div.style.OTransform =
949         div.style.msTransform =
950
951             translate + " " + matrix;
952
953     };
954
955     /**
956      * Applies the given affine transform (defined with six values from the
957      * transform's matrix).
958      * 
959      * @param {Number} a The first value in the affine transform's matrix.
960      * @param {Number} b The second value in the affine transform's matrix.
961      * @param {Number} c The third value in the affine transform's matrix.
962      * @param {Number} d The fourth value in the affine transform's matrix.
963      * @param {Number} e The fifth value in the affine transform's matrix.
964      * @param {Number} f The sixth value in the affine transform's matrix.
965      */
966     layer_container.transform = function(a, b, c, d, e, f) {
967
968         // Generate matrix transformation
969         matrix =
970
971             /* a c e
972              * b d f
973              * 0 0 1
974              */
975     
976             "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
977
978         // Set layer transform 
979         div.style.transform =
980         div.style.WebkitTransform =
981         div.style.MozTransform =
982         div.style.OTransform =
983         div.style.msTransform =
984
985             translate + " " + matrix;
986
987     };
988
989 };
990
991 /**
992  * Map of all Guacamole binary raster operations to transfer functions.
993  * @private
994  */
995 Guacamole.Client.DefaultTransferFunction = {
996
997     /* BLACK */
998     0x0: function (src, dst) {
999         dst.red = dst.green = dst.blue = 0x00;
1000     },
1001
1002     /* WHITE */
1003     0xF: function (src, dst) {
1004         dst.red = dst.green = dst.blue = 0xFF;
1005     },
1006
1007     /* SRC */
1008     0x3: function (src, dst) {
1009         dst.red   = src.red;
1010         dst.green = src.green;
1011         dst.blue  = src.blue;
1012         dst.alpha = src.alpha;
1013     },
1014
1015     /* DEST (no-op) */
1016     0x5: function (src, dst) {
1017         // Do nothing
1018     },
1019
1020     /* Invert SRC */
1021     0xC: function (src, dst) {
1022         dst.red   = 0xFF & ~src.red;
1023         dst.green = 0xFF & ~src.green;
1024         dst.blue  = 0xFF & ~src.blue;
1025         dst.alpha =  src.alpha;
1026     },
1027     
1028     /* Invert DEST */
1029     0xA: function (src, dst) {
1030         dst.red   = 0xFF & ~dst.red;
1031         dst.green = 0xFF & ~dst.green;
1032         dst.blue  = 0xFF & ~dst.blue;
1033     },
1034
1035     /* AND */
1036     0x1: function (src, dst) {
1037         dst.red   =  ( src.red   &  dst.red);
1038         dst.green =  ( src.green &  dst.green);
1039         dst.blue  =  ( src.blue  &  dst.blue);
1040     },
1041
1042     /* NAND */
1043     0xE: function (src, dst) {
1044         dst.red   = 0xFF & ~( src.red   &  dst.red);
1045         dst.green = 0xFF & ~( src.green &  dst.green);
1046         dst.blue  = 0xFF & ~( src.blue  &  dst.blue);
1047     },
1048
1049     /* OR */
1050     0x7: function (src, dst) {
1051         dst.red   =  ( src.red   |  dst.red);
1052         dst.green =  ( src.green |  dst.green);
1053         dst.blue  =  ( src.blue  |  dst.blue);
1054     },
1055
1056     /* NOR */
1057     0x8: function (src, dst) {
1058         dst.red   = 0xFF & ~( src.red   |  dst.red);
1059         dst.green = 0xFF & ~( src.green |  dst.green);
1060         dst.blue  = 0xFF & ~( src.blue  |  dst.blue);
1061     },
1062
1063     /* XOR */
1064     0x6: function (src, dst) {
1065         dst.red   =  ( src.red   ^  dst.red);
1066         dst.green =  ( src.green ^  dst.green);
1067         dst.blue  =  ( src.blue  ^  dst.blue);
1068     },
1069
1070     /* XNOR */
1071     0x9: function (src, dst) {
1072         dst.red   = 0xFF & ~( src.red   ^  dst.red);
1073         dst.green = 0xFF & ~( src.green ^  dst.green);
1074         dst.blue  = 0xFF & ~( src.blue  ^  dst.blue);
1075     },
1076
1077     /* AND inverted source */
1078     0x4: function (src, dst) {
1079         dst.red   =  0xFF & (~src.red   &  dst.red);
1080         dst.green =  0xFF & (~src.green &  dst.green);
1081         dst.blue  =  0xFF & (~src.blue  &  dst.blue);
1082     },
1083
1084     /* OR inverted source */
1085     0xD: function (src, dst) {
1086         dst.red   =  0xFF & (~src.red   |  dst.red);
1087         dst.green =  0xFF & (~src.green |  dst.green);
1088         dst.blue  =  0xFF & (~src.blue  |  dst.blue);
1089     },
1090
1091     /* AND inverted destination */
1092     0x2: function (src, dst) {
1093         dst.red   =  0xFF & ( src.red   & ~dst.red);
1094         dst.green =  0xFF & ( src.green & ~dst.green);
1095         dst.blue  =  0xFF & ( src.blue  & ~dst.blue);
1096     },
1097
1098     /* OR inverted destination */
1099     0xB: function (src, dst) {
1100         dst.red   =  0xFF & ( src.red   | ~dst.red);
1101         dst.green =  0xFF & ( src.green | ~dst.green);
1102         dst.blue  =  0xFF & ( src.blue  | ~dst.blue);
1103     }
1104
1105 };