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