Add scale function, use bounding div for main display element, such that the display...
[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             mouseState.x,
199             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", mouseState.x, 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 };
845
846
847 /**
848  * Simple container for Guacamole.Layer, allowing layers to be easily
849  * repositioned and nested. This allows certain operations to be accelerated
850  * through DOM manipulation, rather than raster operations.
851  * 
852  * @constructor
853  * 
854  * @param {Number} width The width of the Layer, in pixels. The canvas element
855  *                       backing this Layer will be given this width.
856  *                       
857  * @param {Number} height The height of the Layer, in pixels. The canvas element
858  *                        backing this Layer will be given this height.
859  */
860 Guacamole.Client.LayerContainer = function(width, height) {
861
862     /**
863      * Reference to this LayerContainer.
864      * @private
865      */
866     var layer_container = this;
867
868     // Create layer with given size
869     var layer = new Guacamole.Layer(width, height);
870
871     // Set layer position
872     var canvas = layer.getCanvas();
873     canvas.style.position = "absolute";
874     canvas.style.left = "0px";
875     canvas.style.top = "0px";
876
877     // Create div with given size
878     var div = document.createElement("div");
879     div.appendChild(canvas);
880     div.style.width = width + "px";
881     div.style.height = height + "px";
882
883     /**
884      * Changes the size of this LayerContainer and the contained Layer to the
885      * given width and height.
886      * 
887      * @param {Number} width The new width to assign to this Layer.
888      * @param {Number} height The new height to assign to this Layer.
889      */
890     layer_container.resize = function(width, height) {
891
892         // Resize layer
893         layer.resize(width, height);
894
895         // Resize containing div
896         div.style.width = width + "px";
897         div.style.height = height + "px";
898
899     };
900   
901     /**
902      * Returns the Layer contained within this LayerContainer.
903      * @returns {Guacamole.Layer} The Layer contained within this LayerContainer.
904      */
905     layer_container.getLayer = function() {
906         return layer;
907     };
908
909     /**
910      * Returns the element containing the Layer within this LayerContainer.
911      * @returns {Element} The element containing the Layer within this LayerContainer.
912      */
913     layer_container.getElement = function() {
914         return div;
915     };
916
917     /**
918      * The translation component of this LayerContainer's transform.
919      */
920     var translate = "translate(0px, 0px)"; // (0, 0)
921
922     /**
923      * The arbitrary matrix component of this LayerContainer's transform.
924      */
925     var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity
926
927     /**
928      * Moves the upper-left corner of this LayerContainer to the given X and Y
929      * coordinate.
930      * 
931      * @param {Number} x The X coordinate to move to.
932      * @param {Number} y The Y coordinate to move to.
933      */
934     layer_container.translate = function(x, y) {
935
936         // Generate translation
937         translate = "translate("
938                         + x + "px,"
939                         + y + "px)";
940
941         // Set layer transform 
942         div.style.transform =
943         div.style.WebkitTransform =
944         div.style.MozTransform =
945         div.style.OTransform =
946         div.style.msTransform =
947
948             translate + " " + matrix;
949
950     };
951
952     /**
953      * Applies the given affine transform (defined with six values from the
954      * transform's matrix).
955      * 
956      * @param {Number} a The first value in the affine transform's matrix.
957      * @param {Number} b The second value in the affine transform's matrix.
958      * @param {Number} c The third value in the affine transform's matrix.
959      * @param {Number} d The fourth value in the affine transform's matrix.
960      * @param {Number} e The fifth value in the affine transform's matrix.
961      * @param {Number} f The sixth value in the affine transform's matrix.
962      */
963     layer_container.transform = function(a, b, c, d, e, f) {
964
965         // Generate matrix transformation
966         matrix =
967
968             /* a c e
969              * b d f
970              * 0 0 1
971              */
972     
973             "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")";
974
975         // Set layer transform 
976         div.style.transform =
977         div.style.WebkitTransform =
978         div.style.MozTransform =
979         div.style.OTransform =
980         div.style.msTransform =
981
982             translate + " " + matrix;
983
984     };
985
986 };
987
988 /**
989  * Map of all Guacamole binary raster operations to transfer functions.
990  * @private
991  */
992 Guacamole.Client.DefaultTransferFunction = {
993
994     /* BLACK */
995     0x0: function (src, dst) {
996         dst.red = dst.green = dst.blue = 0x00;
997     },
998
999     /* WHITE */
1000     0xF: function (src, dst) {
1001         dst.red = dst.green = dst.blue = 0xFF;
1002     },
1003
1004     /* SRC */
1005     0x3: function (src, dst) {
1006         dst.red   = src.red;
1007         dst.green = src.green;
1008         dst.blue  = src.blue;
1009         dst.alpha = src.alpha;
1010     },
1011
1012     /* DEST (no-op) */
1013     0x5: function (src, dst) {
1014         // Do nothing
1015     },
1016
1017     /* Invert SRC */
1018     0xC: function (src, dst) {
1019         dst.red   = 0xFF & ~src.red;
1020         dst.green = 0xFF & ~src.green;
1021         dst.blue  = 0xFF & ~src.blue;
1022         dst.alpha =  src.alpha;
1023     },
1024     
1025     /* Invert DEST */
1026     0xA: function (src, dst) {
1027         dst.red   = 0xFF & ~dst.red;
1028         dst.green = 0xFF & ~dst.green;
1029         dst.blue  = 0xFF & ~dst.blue;
1030     },
1031
1032     /* AND */
1033     0x1: function (src, dst) {
1034         dst.red   =  ( src.red   &  dst.red);
1035         dst.green =  ( src.green &  dst.green);
1036         dst.blue  =  ( src.blue  &  dst.blue);
1037     },
1038
1039     /* NAND */
1040     0xE: function (src, dst) {
1041         dst.red   = 0xFF & ~( src.red   &  dst.red);
1042         dst.green = 0xFF & ~( src.green &  dst.green);
1043         dst.blue  = 0xFF & ~( src.blue  &  dst.blue);
1044     },
1045
1046     /* OR */
1047     0x7: function (src, dst) {
1048         dst.red   =  ( src.red   |  dst.red);
1049         dst.green =  ( src.green |  dst.green);
1050         dst.blue  =  ( src.blue  |  dst.blue);
1051     },
1052
1053     /* NOR */
1054     0x8: function (src, dst) {
1055         dst.red   = 0xFF & ~( src.red   |  dst.red);
1056         dst.green = 0xFF & ~( src.green |  dst.green);
1057         dst.blue  = 0xFF & ~( src.blue  |  dst.blue);
1058     },
1059
1060     /* XOR */
1061     0x6: function (src, dst) {
1062         dst.red   =  ( src.red   ^  dst.red);
1063         dst.green =  ( src.green ^  dst.green);
1064         dst.blue  =  ( src.blue  ^  dst.blue);
1065     },
1066
1067     /* XNOR */
1068     0x9: function (src, dst) {
1069         dst.red   = 0xFF & ~( src.red   ^  dst.red);
1070         dst.green = 0xFF & ~( src.green ^  dst.green);
1071         dst.blue  = 0xFF & ~( src.blue  ^  dst.blue);
1072     },
1073
1074     /* AND inverted source */
1075     0x4: function (src, dst) {
1076         dst.red   =  0xFF & (~src.red   &  dst.red);
1077         dst.green =  0xFF & (~src.green &  dst.green);
1078         dst.blue  =  0xFF & (~src.blue  &  dst.blue);
1079     },
1080
1081     /* OR inverted source */
1082     0xD: function (src, dst) {
1083         dst.red   =  0xFF & (~src.red   |  dst.red);
1084         dst.green =  0xFF & (~src.green |  dst.green);
1085         dst.blue  =  0xFF & (~src.blue  |  dst.blue);
1086     },
1087
1088     /* AND inverted destination */
1089     0x2: function (src, dst) {
1090         dst.red   =  0xFF & ( src.red   & ~dst.red);
1091         dst.green =  0xFF & ( src.green & ~dst.green);
1092         dst.blue  =  0xFF & ( src.blue  & ~dst.blue);
1093     },
1094
1095     /* OR inverted destination */
1096     0xB: function (src, dst) {
1097         dst.red   =  0xFF & ( src.red   | ~dst.red);
1098         dst.green =  0xFF & ( src.green | ~dst.green);
1099         dst.blue  =  0xFF & ( src.blue  | ~dst.blue);
1100     }
1101
1102 };