]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadway.js
[broadway] Fix various js warnings
[~andy/gtk] / gdk / broadway / broadway.js
1 /* Helper functions for debugging */
2 var logDiv = null;
3 function log(str) {
4     if (!logDiv) {
5         logDiv = document.createElement('div');
6         document.body.appendChild(logDiv);
7         logDiv.style["position"] = "absolute";
8         logDiv.style["right"] = "0px";
9     }
10     logDiv.appendChild(document.createTextNode(str));
11     logDiv.appendChild(document.createElement('br'));
12 }
13
14 function getStackTrace()
15 {
16     var callstack = [];
17     var isCallstackPopulated = false;
18     try {
19         i.dont.exist+=0;
20     } catch(e) {
21         if (e.stack) { // Firefox
22             var lines = e.stack.split("\n");
23             for (var i=0, len=lines.length; i<len; i++) {
24                 if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
25                     callstack.push(lines[i]);
26                 }
27             }
28             // Remove call to getStackTrace()
29             callstack.shift();
30             isCallstackPopulated = true;
31         } else if (window.opera && e.message) { // Opera
32             var lines = e.message.split("\n");
33             for (var i=0, len=lines.length; i<len; i++) {
34                 if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
35                     var entry = lines[i];
36                     // Append next line also since it has the file info
37                     if (lines[i+1]) {
38                         entry += " at " + lines[i+1];
39                         i++;
40                     }
41                     callstack.push(entry);
42                 }
43             }
44             // Remove call to getStackTrace()
45             callstack.shift();
46             isCallstackPopulated = true;
47         }
48     }
49     if (!isCallstackPopulated) { //IE and Safari
50         var currentFunction = arguments.callee.caller;
51         while (currentFunction) {
52             var fn = currentFunction.toString();
53             var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
54             callstack.push(fname);
55             currentFunction = currentFunction.caller;
56         }
57     }
58     return callstack;
59 }
60
61 function logStackTrace(len) {
62     var callstack = getStackTrace();
63     var end = callstack.length;
64     if (len > 0)
65         end = Math.min(len + 1, end);
66     for (var i = 1; i < end; i++)
67         log(callstack[i]);
68 }
69
70 var base64Values = [
71     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
72     255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
73     255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
74     52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,
75     255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
76     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
77     255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
78     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255
79 ];
80
81 function base64_8(str, index) {
82     var v =
83         (base64Values[str.charCodeAt(index)]) +
84         (base64Values[str.charCodeAt(index+1)] << 6);
85     return v;
86 }
87
88 function base64_16(str, index) {
89     var v =
90         (base64Values[str.charCodeAt(index)]) +
91         (base64Values[str.charCodeAt(index+1)] << 6) +
92         (base64Values[str.charCodeAt(index+2)] << 12);
93     return v;
94 }
95
96 function base64_16s(str, index) {
97     var v = base64_16(str, index);
98     if (v > 32767)
99         return v - 65536;
100     else
101         return v;
102 }
103
104 function base64_24(str, index) {
105     var v =
106         (base64Values[str.charCodeAt(index)]) +
107         (base64Values[str.charCodeAt(index+1)] << 6) +
108         (base64Values[str.charCodeAt(index+2)] << 12) +
109         (base64Values[str.charCodeAt(index+3)] << 18);
110     return v;
111 }
112
113 function base64_32(str, index) {
114     var v =
115         (base64Values[str.charCodeAt(index)]) +
116         (base64Values[str.charCodeAt(index+1)] << 6) +
117         (base64Values[str.charCodeAt(index+2)] << 12) +
118         (base64Values[str.charCodeAt(index+3)] << 18) +
119         (base64Values[str.charCodeAt(index+4)] << 24) +
120         (base64Values[str.charCodeAt(index+5)] << 30);
121     return v;
122 }
123
124 function createXHR()
125 {
126     try { return new XMLHttpRequest(); } catch(e) {}
127     try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
128     try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
129     try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
130     try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
131
132     return null;
133 }
134
135 /* This resizes the window so the *inner* size is the specified size */
136 function resizeBrowserWindow(window, w, h) {
137     var innerW = window.innerWidth;
138     var innerH = window.innerHeight;
139
140     var outerW = window.outerWidth;
141     var outerH = window.outerHeight;
142
143     window.resizeTo(w + outerW - innerW,
144                     h + outerH - innerH);
145 }
146
147 function resizeCanvas(canvas, w, h)
148 {
149     /* Canvas resize clears the data, so we need to save it first */
150     var tmpCanvas = canvas.ownerDocument.createElement("canvas");
151     tmpCanvas.width = canvas.width;
152     tmpCanvas.height = canvas.height;
153     var tmpContext = tmpCanvas.getContext("2d");
154     tmpContext.globalCompositeOperation = "copy";
155     tmpContext.drawImage(canvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
156
157     canvas.width = w;
158     canvas.height = h;
159
160     var context = canvas.getContext("2d");
161
162     context.globalCompositeOperation = "copy";
163     context.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
164 }
165
166 var useToplevelWindows = false;
167 var toplevelWindows = [];
168 var grab = new Object();
169 grab.window = null;
170 grab.ownerEvents = false;
171 grab.implicit = false;
172 var localGrab = null;
173 var lastSerial = 0;
174 var lastX = 0;
175 var lastY = 0;
176 var lastState;
177 var lastTimeStamp = 0;
178 var realWindowWithMouse = 0;
179 var windowWithMouse = 0;
180 var surfaces = {};
181 var stackingOrder = [];
182 var outstandingCommands = new Array();
183 var inputSocket = null;
184
185 var GDK_CROSSING_NORMAL = 0;
186 var GDK_CROSSING_GRAB = 1;
187 var GDK_CROSSING_UNGRAB = 2;
188
189 // GdkModifierType
190 var GDK_SHIFT_MASK = 1 << 0;
191 var GDK_LOCK_MASK     = 1 << 1;
192 var GDK_CONTROL_MASK  = 1 << 2;
193 var GDK_MOD1_MASK     = 1 << 3;
194 var GDK_MOD2_MASK     = 1 << 4;
195 var GDK_MOD3_MASK     = 1 << 5;
196 var GDK_MOD4_MASK     = 1 << 6;
197 var GDK_MOD5_MASK     = 1 << 7;
198 var GDK_BUTTON1_MASK  = 1 << 8;
199 var GDK_BUTTON2_MASK  = 1 << 9;
200 var GDK_BUTTON3_MASK  = 1 << 10;
201 var GDK_BUTTON4_MASK  = 1 << 11;
202 var GDK_BUTTON5_MASK  = 1 << 12;
203 var GDK_SUPER_MASK    = 1 << 26;
204 var GDK_HYPER_MASK    = 1 << 27;
205 var GDK_META_MASK     = 1 << 28;
206 var GDK_RELEASE_MASK  = 1 << 30;
207
208 function getButtonMask (button) {
209     if (button == 1)
210         return GDK_BUTTON1_MASK;
211     if (button == 2)
212         return GDK_BUTTON2_MASK;
213     if (button == 3)
214         return GDK_BUTTON3_MASK;
215     if (button == 4)
216         return GDK_BUTTON4_MASK;
217     if (button == 5)
218         return GDK_BUTTON5_MASK;
219     return 0;
220 }
221
222 function flushSurface(surface)
223 {
224     var commands = surface.drawQueue;
225     surface.queue = [];
226     var context = surface.canvas.getContext("2d");
227     context.globalCompositeOperation = "source-over";
228     var i = 0;
229     for (i = 0; i < commands.length; i++) {
230         var cmd = commands[i];
231         switch (cmd.op) {
232         case 'i': // put image data surface
233             context.globalCompositeOperation = "source-over";
234             context.drawImage(cmd.img, cmd.x, cmd.y);
235             break;
236
237         case 'b': // copy rects
238             context.save();
239             context.beginPath();
240
241             var minx;
242             var miny;
243             var maxx;
244             var maxy;
245             for (var j = 0; j < cmd.rects.length; j++) {
246                 var rect = cmd.rects[j];
247                 context.rect(rect.x, rect.y, rect.w, rect.h);
248                 if (j == 0) {
249                     minx = rect.x;
250                     miny = rect.y;
251                     maxx = rect.x + rect.w;
252                     maxy = rect.y + rect.h;
253                 } else {
254                     if (rect.x < minx)
255                         minx = rect.x;
256                     if (rect.y < miny)
257                         miny = rect.y;
258                     if (rect.x + rect.w > maxx)
259                         maxx = rect.x + rect.w;
260                     if (rect.y + rect.h > maxy)
261                         maxy = rect.y + rect.h;
262                 }
263             }
264             context.clip();
265             context.globalCompositeOperation = "copy";
266             context.drawImage(context.canvas,
267                               minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,
268                               minx, miny, maxx - minx, maxy - miny);
269             context.restore();
270             break;
271
272         default:
273             alert("Unknown drawing op " + cmd.op);
274         }
275     }
276 }
277
278 function ensureSurfaceInDocument(surface, doc)
279 {
280     if (surface.document != doc) {
281         var oldCanvas = surface.canvas;
282         var canvas = doc.importNode(oldCanvas, false);
283         doc.body.appendChild(canvas);
284         canvas.surface = surface;
285         oldCanvas.parentNode.removeChild(oldCanvas);
286
287         surface.canvas = canvas;
288         if (surface.toplevelElement == oldCanvas)
289             surface.toplevelElement = canvas;
290         surface.document = doc;
291     }
292 }
293
294 function sendConfigureNotify(surface)
295 {
296     sendInput("w", [surface.id, surface.x, surface.y, surface.width, surface.height]);
297 }
298
299 var windowGeometryTimeout = null;
300
301 function updateBrowserWindowGeometry(win, alwaysSendConfigure) {
302     if (win.closed)
303         return;
304
305     var surface = win.surface;
306
307     var innerW = win.innerWidth;
308     var innerH = win.innerHeight;
309
310     var x = surface.x;
311     var y = surface.y;
312
313     if (win.mozInnerScreenX != undefined) {
314         x = win.mozInnerScreenX;
315         y = win.mozInnerScreenY;
316     } else if (win.screenTop != undefined) {
317         x = win.screenTop;
318         y = win.screenLeft;
319     } else {
320         alert("No implementation to get window position");
321     }
322
323     if (alwaysSendConfigure || x != surface.x || y != surface.y ||
324        innerW != surface.width || innerH != surface.height) {
325         var oldX = surface.x;
326         var oldY = surface.y;
327         surface.x = x;
328         surface.y = y;
329         if (surface.width != innerW || surface.height != innerH)
330             resizeCanvas(surface.canvas, innerW, innerH);
331         surface.width = innerW;
332         surface.height = innerH;
333         sendConfigureNotify(surface);
334         for (id in surfaces) {
335             var childSurface = surfaces[id];
336             var transientToplevel = getTransientToplevel(childSurface);
337             if (transientToplevel != null && transientToplevel == surface) {
338                 childSurface.x += surface.x - oldX;
339                 childSurface.y += surface.y - oldY;
340                 sendConfigureNotify(childSurface);
341             }
342         }
343     }
344 }
345
346 function browserWindowClosed(win) {
347     var surface = win.surface;
348
349     sendInput ("W", [surface.id]);
350     for (id in surfaces) {
351         var childSurface = surfaces[id];
352         var transientToplevel = getTransientToplevel(childSurface);
353         if (transientToplevel != null && transientToplevel == surface) {
354             sendInput ("W", [childSurface.id]);
355         }
356     }
357 }
358
359 function registerWindow(win)
360 {
361     toplevelWindows.push(win);
362     win.onresize = function(ev) { updateBrowserWindowGeometry(ev.target, false); };
363     if (!windowGeometryTimeout)
364         windowGeometryTimeout = setInterval(function () {
365                                                 for (var i = 0; i < toplevelWindows.length; i++)
366                                                     toplevelWindows[i].updateBrowserWindowGeometry(toplevelWindows[i], false);
367                                             }, 2000);
368     win.onunload = function(ev) { browserWindowClosed(ev.target.defaultView); };
369 }
370
371 function unregisterWindow(win)
372 {
373     var i = toplevelWindows.indexOf(win);
374     if (i >= 0)
375         toplevelWindows.splice(i, 1);
376
377     if (windowGeometryTimeout && toplevelWindows.length == 0) {
378         clearInterval(windowGeometryTimeout);
379         windowGeometryTimeout = null;
380     }
381 }
382
383 function getTransientToplevel(surface)
384 {
385     while (surface && surface.transientParent != 0) {
386         surface = surfaces[surface.transientParent];
387         if (surface && surface.window)
388             return surface;
389     }
390     return null;
391 }
392
393 function getStyle(el, styleProp)
394 {
395     if (el.currentStyle) {
396         return el.currentStyle[styleProp];
397     }  else if (window.getComputedStyle) {
398         var win = el.ownerDocument.defaultView;
399         return win.getComputedStyle(el, null).getPropertyValue(styleProp);
400     }
401     return undefined;
402 }
403
404 function parseOffset(value)
405 {
406     var px = value.indexOf("px");
407     if (px > 0)
408         return parseInt(value.slice(0,px));
409     return 0;
410 }
411
412 function getFrameOffset(surface) {
413     var x = 0;
414     var y = 0;
415     var el = surface.canvas;
416     while (el != null && el != surface.frame) {
417         x += el.offsetLeft;
418         y += el.offsetTop;
419
420         /* For some reason the border is not includes in the offsets.. */
421         x += parseOffset(getStyle(el, "border-left-width"));
422         y += parseOffset(getStyle(el, "border-top-width"));
423
424         el = el.offsetParent;
425     }
426
427     /* Also include frame border as per above */
428     x += parseOffset(getStyle(el, "border-left-width"));
429     y += parseOffset(getStyle(el, "border-top-width"));
430
431     return {x: x, y: y};
432 }
433
434 var positionIndex = 0;
435 function cmdCreateSurface(id, x, y, width, height, isTemp)
436 {
437     var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };
438     surface.positioned = isTemp;
439     surface.drawQueue = [];
440     surface.transientParent = 0;
441     surface.visible = false;
442     surface.window = null;
443     surface.document = document;
444     surface.frame = null;
445
446     var canvas = document.createElement("canvas");
447     canvas.width = width;
448     canvas.height = height;
449     canvas.surface = surface;
450     surface.canvas = canvas;
451     var toplevelElement;
452
453     if (useToplevelWindows || isTemp) {
454         toplevelElement = canvas;
455         document.body.appendChild(canvas);
456     } else {
457         var frame = document.createElement("div");
458         frame.frameFor = surface;
459         frame.className = "frame-window";
460         surface.frame = frame;
461
462         var button = document.createElement("center");
463         var X = document.createTextNode("X");
464         button.appendChild(X);
465         button.className = "frame-close";
466         frame.appendChild(button);
467
468         var contents = document.createElement("div");
469         contents.className = "frame-contents";
470         frame.appendChild(contents);
471
472         canvas.style["display"] = "block";
473         contents.appendChild(canvas);
474
475         toplevelElement = frame;
476         document.body.appendChild(frame);
477
478         surface.x = 100 + positionIndex * 10;
479         surface.y = 100 + positionIndex * 10;
480         positionIndex = (positionIndex + 1) % 20;
481     }
482
483     surface.toplevelElement = toplevelElement;
484     toplevelElement.style["position"] = "absolute";
485     /* This positioning isn't strictly right for apps in another topwindow,
486      * but that will be fixed up when showing. */
487     toplevelElement.style["left"] = surface.x + "px";
488     toplevelElement.style["top"] = surface.y + "px";
489     toplevelElement.style["display"] = "inline";
490
491     /* We hide the frame with visibility rather than display none
492      * so getFrameOffset still works with hidden windows. */
493     toplevelElement.style["visibility"] = "hidden";
494
495     surfaces[id] = surface;
496     stackingOrder.push(surface);
497
498     sendConfigureNotify(surface);
499 }
500
501 function cmdShowSurface(id)
502 {
503     var surface = surfaces[id];
504
505     if (surface.visible)
506         return;
507     surface.visible = true;
508
509     var xOffset = surface.x;
510     var yOffset = surface.y;
511
512     if (useToplevelWindows) {
513         var doc = document;
514         if (!surface.isTemp) {
515             var options =
516                 'width='+surface.width+',height='+surface.height+
517                 ',location=no,menubar=no,scrollbars=no,toolbar=no';
518             if (surface.positioned)
519                 options = options +
520                 ',left='+surface.x+',top='+surface.y+',screenX='+surface.x+',screenY='+surface.y;
521             var win = window.open('','_blank', options);
522             win.surface = surface;
523             registerWindow(win);
524             doc = win.document;
525             doc.open();
526             doc.write("<body></body>");
527             setupDocument(doc);
528
529             surface.window = win;
530             xOffset = 0;
531             yOffset = 0;
532         } else {
533             var transientToplevel = getTransientToplevel(surface);
534             if (transientToplevel) {
535                 doc = transientToplevel.window.document;
536                 xOffset = surface.x - transientToplevel.x;
537                 yOffset = surface.y - transientToplevel.y;
538             }
539         }
540
541         ensureSurfaceInDocument(surface, doc);
542     } else {
543         if (surface.frame) {
544             var offset = getFrameOffset(surface);
545             xOffset -= offset.x;
546             yOffset -= offset.y;
547         }
548     }
549
550     surface.toplevelElement.style["left"] = xOffset + "px";
551     surface.toplevelElement.style["top"] = yOffset + "px";
552     surface.toplevelElement.style["visibility"] = "visible";
553
554     restackWindows();
555
556     if (surface.window)
557         updateBrowserWindowGeometry(surface.window, false);
558 }
559
560 function cmdHideSurface(id)
561 {
562     var surface = surfaces[id];
563
564     if (!surface.visible)
565         return;
566     surface.visible = false;
567
568     var element = surface.toplevelElement;
569
570     element.style["visibility"] = "hidden";
571
572     // Import the canvas into the main document
573     ensureSurfaceInDocument(surface, document);
574
575     if (surface.window) {
576         unregisterWindow(surface.window);
577         surface.window.close();
578         surface.window = null;
579     }
580 }
581
582 function cmdSetTransientFor(id, parentId)
583 {
584     var surface = surfaces[id];
585
586     if (surface.transientParent == parentId)
587         return;
588
589     surface.transientParent = parentId;
590     if (parentId != 0 && surfaces[parentId]) {
591         moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1);
592     }
593
594     if (surface.visible) {
595         restackWindows();
596     }
597 }
598
599 function restackWindows() {
600     for (var i = 0; i < stackingOrder.length; i++) {
601         var surface = stackingOrder[i];
602         surface.toplevelElement.style.zIndex = i;
603     }
604 }
605
606 function moveToHelper(surface, position) {
607     var i = stackingOrder.indexOf(surface);
608     stackingOrder.splice(i, 1);
609     if (position != undefined)
610         stackingOrder.splice(position, 0, surface);
611     else
612         stackingOrder.push(surface);
613
614     for (var cid in surfaces) {
615         var child = surfaces[cid];
616         if (child.transientParent == surface.id)
617             moveToHelper(child, stackingOrder.indexOf(surface) + 1);
618     }
619 }
620
621 function moveToTop(surface) {
622     moveToHelper(surface);
623     restackWindows();
624 }
625
626
627 function cmdDeleteSurface(id)
628 {
629     var surface = surfaces[id];
630     var i = stackingOrder.indexOf(surface);
631     if (i >= 0)
632         stackingOrder.splice(i, 1);
633     var canvas = surface.canvas;
634     canvas.parentNode.removeChild(canvas);
635     var frame = surface.frame;
636     if (frame)
637         frame.parentNode.removeChild(frame);
638     delete surfaces[id];
639 }
640
641 function cmdMoveSurface(id, x, y)
642 {
643     var surface = surfaces[id];
644     surface.positioned = true;
645     surface.x = x;
646     surface.y = y;
647
648     if (surface.visible) {
649         if (surface.window) {
650             /* TODO: This moves the outer frame position, we really want the inner position.
651              * However this isn't *strictly* invalid, as any WM could have done whatever it
652              * wanted with the positioning of the window.
653              */
654             surface.window.moveTo(surface.x, surface.y);
655         } else {
656             var xOffset = surface.x;
657             var yOffset = surface.y;
658
659             var transientToplevel = getTransientToplevel(surface);
660             if (transientToplevel) {
661                 xOffset = surface.x - transientToplevel.x;
662                 yOffset = surface.y - transientToplevel.y;
663             }
664
665             var element = surface.canvas;
666             if (surface.frame) {
667                 element = surface.frame;
668                 var offset = getFrameOffset(surface);
669                 xOffset -= offset.x;
670                 yOffset -= offset.y;
671             }
672
673             element.style["left"] = xOffset + "px";
674             element.style["top"] = yOffset + "px";
675         }
676     }
677
678     if (surface.window) {
679         updateBrowserWindowGeometry(surface.window, true);
680     } else {
681         sendConfigureNotify(surface);
682     }
683 }
684
685 function cmdResizeSurface(id, w, h)
686 {
687     var surface = surfaces[id];
688
689     surface.width = w;
690     surface.height = h;
691
692     /* Flush any outstanding draw ops before changing size */
693     flushSurface(surface);
694
695     resizeCanvas(surface.canvas, w, h);
696
697     if (surface.window) {
698         resizeBrowserWindow(surface.window, w, h);
699         updateBrowserWindowGeometry(surface.window, true);
700     } else {
701         sendConfigureNotify(surface);
702     }
703 }
704
705 function cmdFlushSurface(id)
706 {
707     flushSurface(surfaces[id]);
708 }
709
710 function cmdGrabPointer(id, ownerEvents)
711 {
712     doGrab(id, ownerEvents, false);
713     sendInput ("g", []);
714 }
715
716 function cmdUngrabPointer()
717 {
718     sendInput ("u", []);
719
720     grab.window = null;
721 }
722
723 function handleCommands(cmdObj)
724 {
725     var cmd = cmdObj.data;
726     var i = cmdObj.pos;
727
728     while (i < cmd.length) {
729         var id, x, y, w, h, q;
730         var command = cmd[i++];
731         lastSerial = base64_32(cmd, i);
732         i = i + 6;
733         switch (command) {
734         case 's': // create new surface
735             id = base64_16(cmd, i);
736             i = i + 3;
737             x = base64_16s(cmd, i);
738             i = i + 3;
739             y = base64_16s(cmd, i);
740             i = i + 3;
741             w = base64_16(cmd, i);
742             i = i + 3;
743             h = base64_16(cmd, i);
744             i = i + 3;
745             var isTemp = cmd[i] == '1';
746             i = i + 1;
747             cmdCreateSurface(id, x, y, w, h, isTemp);
748             break;
749
750         case 'S': // Show a surface
751             id = base64_16(cmd, i);
752             i = i + 3;
753             cmdShowSurface(id);
754             break;
755
756         case 'H': // Hide a surface
757             id = base64_16(cmd, i);
758             i = i + 3;
759             cmdHideSurface(id);
760             break;
761
762         case 'p': // Set transient parent
763             id = base64_16(cmd, i);
764             i = i + 3;
765             var parentId = base64_16(cmd, i);
766             i = i + 3;
767             cmdSetTransientFor(id, parentId);
768             break;
769
770         case 'd': // Delete surface
771             id = base64_16(cmd, i);
772             i = i + 3;
773             cmdDeleteSurface(id);
774             break;
775
776         case 'm': // Move a surface
777             id = base64_16(cmd, i);
778             i = i + 3;
779             x = base64_16(cmd, i);
780             i = i + 3;
781             y = base64_16(cmd, i);
782             i = i + 3;
783             cmdMoveSurface(id, x, y);
784             break;
785
786         case 'r': // Resize a surface
787             id = base64_16(cmd, i);
788             i = i + 3;
789             w = base64_16(cmd, i);
790             i = i + 3;
791             h = base64_16(cmd, i);
792             i = i + 3;
793             cmdResizeSurface(id, w, h);
794             break;
795
796         case 'i': // Put image data surface
797             q = new Object();
798             q.op = 'i';
799             q.id = base64_16(cmd, i);
800             i = i + 3;
801             q.x = base64_16(cmd, i);
802             i = i + 3;
803             q.y = base64_16(cmd, i);
804             i = i + 3;
805             var size = base64_32(cmd, i);
806             i = i + 6;
807             var url = cmd.slice(i, i + size);
808             i = i + size;
809             q.img = new Image();
810             q.img.src = url;
811             surfaces[q.id].drawQueue.push(q);
812             if (!q.img.complete) {
813                 cmdObj.pos = i;
814                 q.img.onload = function() { handleOutstanding(); };
815                 return false;
816             }
817             break;
818
819         case 'b': // Copy rects
820             q = new Object();
821             q.op = 'b';
822             q.id = base64_16(cmd, i);
823             i = i + 3;
824
825             var nrects = base64_16(cmd, i);
826             i = i + 3;
827
828             q.rects = [];
829             for (var r = 0; r < nrects; r++) {
830                 var rect = new Object();
831                 rect.x = base64_16(cmd, i);
832                 i = i + 3;
833                 rect.y = base64_16(cmd, i);
834                 i = i + 3;
835                 rect.w = base64_16(cmd, i);
836                 i = i + 3;
837                 rect.h = base64_16(cmd, i);
838                 i = i + 3;
839                 q.rects.push (rect);
840             }
841
842             q.dx = base64_16s(cmd, i);
843             i = i + 3;
844             q.dy = base64_16s(cmd, i);
845             i = i + 3;
846             surfaces[q.id].drawQueue.push(q);
847             break;
848
849         case 'f': // Flush surface
850             id = base64_16(cmd, i);
851             i = i + 3;
852
853             cmdFlushSurface(id);
854             break;
855
856         case 'g': // Grab
857             id = base64_16(cmd, i);
858             i = i + 3;
859             var ownerEvents = cmd[i++] == '1';
860
861             cmdGrabPointer(id, ownerEvents);
862             break;
863
864         case 'u': // Ungrab
865             cmdUngrabPointer();
866             break;
867         default:
868             alert("Unknown op " + command);
869         }
870     }
871     return true;
872 }
873
874 function handleOutstanding()
875 {
876     while (outstandingCommands.length > 0) {
877         var cmd = outstandingCommands.shift();
878         if (!handleCommands(cmd)) {
879             outstandingCommands.unshift(cmd);
880             return;
881         }
882     }
883 }
884
885 function handleLoad(event)
886 {
887     var cmdObj = {};
888     cmdObj.data = event.target.responseText;
889     cmdObj.pos = 0;
890
891     outstandingCommands.push(cmdObj);
892     if (outstandingCommands.length == 1) {
893         handleOutstanding();
894     }
895 }
896
897 function getSurfaceId(ev) {
898     var surface = ev.target.surface;
899     if (surface != undefined)
900         return surface.id;
901     return 0;
902 }
903
904 function sendInput(cmd, args)
905 {
906     if (inputSocket != null) {
907         inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
908     }
909 }
910
911 function getPositionsFromAbsCoord(absX, absY, relativeId) {
912     var res = Object();
913
914     res.rootX = absX;
915     res.rootY = absY;
916     res.winX = absX;
917     res.winY = absY;
918     if (relativeId != 0) {
919         var surface = surfaces[relativeId];
920         res.winX = res.winX - surface.x;
921         res.winY = res.winY - surface.y;
922     }
923
924     return res;
925 }
926
927 function getPositionsFromEvent(ev, relativeId) {
928     var absX, absY;
929     if (useToplevelWindows) {
930         absX = ev.screenX;
931         absY = ev.screenY;
932     } else {
933         absX = ev.pageX;
934         absY = ev.pageY;
935     }
936     var res = getPositionsFromAbsCoord(absX, absY, relativeId);
937
938     lastX = res.rootX;
939     lastY = res.rootY;
940
941     return res;
942 }
943
944 function getEffectiveEventTarget (id) {
945     if (grab.window != null) {
946         if (!grab.ownerEvents)
947             return grab.window;
948         if (id == 0)
949             return grab.window;
950     }
951     return id;
952 }
953
954 function updateForEvent(ev) {
955     lastTimeStamp = ev.timeStamp;
956     if (ev.target.surface && ev.target.surface.window) {
957         var win = ev.target.surface.window;
958         updateBrowserWindowGeometry(win, false);
959     }
960 }
961
962 function onMouseMove (ev) {
963     updateForEvent(ev);
964     if (localGrab) {
965         var dx = ev.pageX - localGrab.lastX;
966         var dy = ev.pageY - localGrab.lastY;
967         var surface = localGrab.frame.frameFor;
968         surface.x += dx;
969         surface.y += dy;
970         var offset = getFrameOffset(surface);
971         localGrab.frame.style["left"] = (surface.x - offset.x) + "px";
972         localGrab.frame.style["top"] = (surface.y - offset.y) + "px";
973         sendConfigureNotify(surface);
974         localGrab.lastX = ev.pageX;
975         localGrab.lastY = ev.pageY;
976         return;
977     }
978     var id = getSurfaceId(ev);
979     id = getEffectiveEventTarget (id);
980     var pos = getPositionsFromEvent(ev, id);
981     sendInput ("m", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
982 }
983
984 function onMouseOver (ev) {
985     updateForEvent(ev);
986     if (localGrab)
987         return;
988     var id = getSurfaceId(ev);
989     realWindowWithMouse = id;
990     id = getEffectiveEventTarget (id);
991     var pos = getPositionsFromEvent(ev, id);
992     windowWithMouse = id;
993     if (windowWithMouse != 0) {
994         sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
995     }
996 }
997
998 function onMouseOut (ev) {
999     updateForEvent(ev);
1000     if (localGrab)
1001         return;
1002     var id = getSurfaceId(ev);
1003     var origId = id;
1004     id = getEffectiveEventTarget (id);
1005     var pos = getPositionsFromEvent(ev, id);
1006
1007     if (id != 0) {
1008         sendInput ("l", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
1009     }
1010     realWindowWithMouse = 0;
1011     windowWithMouse = 0;
1012 }
1013
1014 function doGrab(id, ownerEvents, implicit) {
1015     var pos;
1016
1017     if (windowWithMouse != id) {
1018         if (windowWithMouse != 0) {
1019             pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
1020             sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
1021         }
1022         pos = getPositionsFromAbsCoord(lastX, lastY, id);
1023         sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
1024         windowWithMouse = id;
1025     }
1026
1027     grab.window = id;
1028     grab.ownerEvents = ownerEvents;
1029     grab.implicit = implicit;
1030 }
1031
1032 function doUngrab() {
1033     var pos;
1034     if (realWindowWithMouse != windowWithMouse) {
1035         if (windowWithMouse != 0) {
1036             pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
1037             sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
1038         }
1039         if (realWindowWithMouse != 0) {
1040             pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);
1041             sendInput ("e", [realWindowWithMouse, realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
1042         }
1043         windowWithMouse = realWindowWithMouse;
1044     }
1045     grab.window = null;
1046 }
1047
1048 function onMouseDown (ev) {
1049     updateForEvent(ev);
1050     var button = ev.button + 1;
1051     lastState = lastState | getButtonMask (button);
1052     var id = getSurfaceId(ev);
1053     id = getEffectiveEventTarget (id);
1054
1055     if (id == 0 && ev.target.frameFor) { /* mouse click on frame */
1056         localGrab = new Object();
1057         localGrab.frame = ev.target;
1058         localGrab.lastX = ev.pageX;
1059         localGrab.lastY = ev.pageY;
1060         moveToTop(localGrab.frame.frameFor);
1061         return;
1062     }
1063
1064     var pos = getPositionsFromEvent(ev, id);
1065     if (grab.window == null)
1066         doGrab (id, false, true);
1067     sendInput ("b", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
1068 }
1069
1070 function onMouseUp (ev) {
1071     updateForEvent(ev);
1072     var button = ev.button + 1;
1073     lastState = lastState & ~getButtonMask (button);
1074     var evId = getSurfaceId(ev);
1075     id = getEffectiveEventTarget (evId);
1076     var pos = getPositionsFromEvent(ev, id);
1077
1078     if (localGrab) {
1079         localGrab = null;
1080         realWindowWithMouse = evId;
1081         if (windowWithMouse != id) {
1082             if (windowWithMouse != 0) {
1083                 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
1084             }
1085             windowWithMouse = id;
1086             if (windowWithMouse != 0) {
1087                 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
1088             }
1089         }
1090         return;
1091     }
1092
1093     sendInput ("B", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
1094
1095     if (grab.window != null && grab.implicit)
1096         doUngrab(ev.timeStamp);
1097 }
1098
1099 var lastKeyDown = 0;
1100 function onKeyDown (ev) {
1101     updateForEvent(ev);
1102     if (localGrab)
1103         return;
1104     var keyCode = ev.keyCode;
1105     if (keyCode != lastKeyDown) {
1106         sendInput ("k", [keyCode]);
1107         lastKeyDown = keyCode;
1108     }
1109 }
1110
1111 function onKeyUp (ev) {
1112     updateForEvent(ev);
1113     if (localGrab)
1114         return;
1115     var keyCode = ev.keyCode;
1116     sendInput ("K", [keyCode]);
1117     lastKeyDown = 0;
1118 }
1119
1120 function cancelEvent(ev)
1121 {
1122     ev = ev ? ev : window.event;
1123     if (ev.stopPropagation)
1124         ev.stopPropagation();
1125     if (ev.preventDefault)
1126         ev.preventDefault();
1127     ev.cancelBubble = true;
1128     ev.cancel = true;
1129     ev.returnValue = false;
1130     return false;
1131 }
1132
1133 function onMouseWheel(ev)
1134 {
1135     updateForEvent(ev);
1136     if (localGrab)
1137         return false;
1138     ev = ev ? ev : window.event;
1139
1140     var id = getSurfaceId(ev);
1141     var pos = getPositionsFromEvent(ev, id);
1142
1143     var offset = ev.detail ? ev.detail : ev.wheelDelta;
1144     var dir = 0;
1145     if (offset > 0)
1146         dir = 1;
1147     sendInput ("s", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
1148
1149     return cancelEvent(ev);
1150 }
1151
1152 function setupDocument(document)
1153 {
1154     document.oncontextmenu = function () { return false; };
1155     document.onmousemove = onMouseMove;
1156     document.onmouseover = onMouseOver;
1157     document.onmouseout = onMouseOut;
1158     document.onmousedown = onMouseDown;
1159     document.onmouseup = onMouseUp;
1160     document.onkeydown = onKeyDown;
1161     document.onkeyup = onKeyUp;
1162
1163     if (document.addEventListener) {
1164       document.addEventListener('DOMMouseScroll', onMouseWheel, false);
1165       document.addEventListener('mousewheel', onMouseWheel, false);
1166     } else if (document.attachEvent) {
1167       element.attachEvent("onmousewheel", onMouseWheel);
1168     }
1169 }
1170
1171 function connect()
1172 {
1173     var url = window.location.toString();
1174     var query_string = url.split("?");
1175     if (query_string.length > 1) {
1176         var params = query_string[1].split("&");
1177         if (params[0].indexOf("toplevel") != -1)
1178             useToplevelWindows = true;
1179     }
1180     var xhr = createXHR();
1181     if (xhr) {
1182         if (typeof xhr.multipart == 'undefined') {
1183             alert("Sorry, this example only works in browsers that support multipart.");
1184             return;
1185         }
1186
1187         xhr.multipart = true;
1188         xhr.open("GET", "/output", true);
1189         xhr.onload = handleLoad;
1190         xhr.send(null);
1191     }
1192
1193     if ("WebSocket" in window) {
1194         var loc = window.location.toString().replace("http:", "ws:");
1195         loc = loc.substr(0, loc.lastIndexOf('/')) + "/input";
1196         var ws = new WebSocket(loc, "broadway");
1197         ws.onopen = function() {
1198             inputSocket = ws;
1199             var w, h;
1200             if (useToplevelWindows) {
1201                 w = window.screen.width;
1202                 h = window.screen.height;
1203             } else {
1204                 w = window.innerWidth;
1205                 h = window.innerHeight;
1206                 window.onresize = function(ev) {
1207                     var w, h;
1208                     w = window.innerWidth;
1209                     h = window.innerHeight;
1210                     sendInput ("d", [w, h]);
1211                 };
1212             }
1213             sendInput ("d", [w, h]);
1214         };
1215         ws.onclose = function() {
1216             inputSocket = null;
1217         };
1218     } else {
1219         alert("WebSocket not supported, input will not work!");
1220     }
1221     setupDocument(document);
1222     window.onunload = function (ev) {
1223         for (var i = 0; i < toplevelWindows.length; i++)
1224             toplevelWindows[i].close();
1225     };
1226 }