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