]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadway.js
94d9b2e4e9d2354685f9dfd54b9070fad57726b2
[~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 lastSerial = 0;
117 var lastX = 0;
118 var lastY = 0;
119 var lastState;
120 var lastTimeStamp = 0;
121 var realWindowWithMouse = 0;
122 var windowWithMouse = 0;
123 var surfaces = {};
124 var outstandingCommands = new Array();
125 var inputSocket = null;
126 var frameSizeX = -1;
127 var frameSizeY = -1;
128
129
130 var GDK_CROSSING_NORMAL = 0;
131 var GDK_CROSSING_GRAB = 1;
132 var GDK_CROSSING_UNGRAB = 2;
133
134 // GdkModifierType
135 var GDK_SHIFT_MASK = 1 << 0;
136 var GDK_LOCK_MASK     = 1 << 1;
137 var GDK_CONTROL_MASK  = 1 << 2;
138 var GDK_MOD1_MASK     = 1 << 3;
139 var GDK_MOD2_MASK     = 1 << 4;
140 var GDK_MOD3_MASK     = 1 << 5;
141 var GDK_MOD4_MASK     = 1 << 6;
142 var GDK_MOD5_MASK     = 1 << 7;
143 var GDK_BUTTON1_MASK  = 1 << 8;
144 var GDK_BUTTON2_MASK  = 1 << 9;
145 var GDK_BUTTON3_MASK  = 1 << 10;
146 var GDK_BUTTON4_MASK  = 1 << 11;
147 var GDK_BUTTON5_MASK  = 1 << 12;
148 var GDK_SUPER_MASK    = 1 << 26;
149 var GDK_HYPER_MASK    = 1 << 27;
150 var GDK_META_MASK     = 1 << 28;
151 var GDK_RELEASE_MASK  = 1 << 30;
152
153 function getButtonMask (button) {
154     if (button == 1)
155         return GDK_BUTTON1_MASK;
156     if (button == 2)
157         return GDK_BUTTON2_MASK;
158     if (button == 3)
159         return GDK_BUTTON3_MASK;
160     if (button == 4)
161         return GDK_BUTTON4_MASK;
162     if (button == 5)
163         return GDK_BUTTON5_MASK;
164     return 0;
165 }
166
167 function flushSurface(surface)
168 {
169     var commands = surface.drawQueue;
170     surface.queue = [];
171     var context = surface.context;
172     var i = 0;
173     for (i = 0; i < commands.length; i++) {
174         var cmd = commands[i];
175         switch (cmd.op) {
176         case 'i': // put image data surface
177             context.globalCompositeOperation = "source-over";
178             context.drawImage(cmd.img, cmd.x, cmd.y);
179             break;
180
181         case 'b': // copy rects
182             context.save();
183             context.beginPath();
184
185             var minx;
186             var miny;
187             var maxx;
188             var maxy;
189             for (var j = 0; j < cmd.rects.length; j++) {
190                 var rect = cmd.rects[j];
191                 context.rect(rect.x, rect.y, rect.w, rect.h);
192                 if (j == 0) {
193                     minx = rect.x;
194                     miny = rect.y;
195                     maxx = rect.x + rect.w;
196                     maxy = rect.y + rect.h;
197                 } else {
198                     if (rect.x < minx)
199                         minx = rect.x;
200                     if (rect.y < miny)
201                         miny = rect.y;
202                     if (rect.x + rect.w > maxx)
203                         maxx = rect.x + rect.w;
204                     if (rect.y + rect.h > maxy)
205                         maxy = rect.y + rect.h;
206                 }
207             }
208             context.clip();
209             context.globalCompositeOperation = "copy";
210             context.drawImage(context.canvas,
211                               minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,
212                               minx, miny, maxx - minx, maxy - miny);
213             context.restore();
214             break;
215
216         default:
217             alert("Unknown drawing op " + cmd.op);
218         }
219     }
220 }
221
222 function ensureSurfaceInDocument(surface, doc)
223 {
224     if (surface.document != doc) {
225         var oldCanvas = surface.canvas;
226         var canvas = doc.importNode(oldCanvas, false);
227         doc.body.appendChild(canvas);
228         canvas.surface = surface;
229         oldCanvas.parentNode.removeChild(oldCanvas);
230
231         surface.canvas = canvas;
232         surface.document = doc;
233
234         var context = canvas.getContext("2d");
235         context.globalCompositeOperation = "source-over";
236         surface.context = context;
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             if (surfaces[id].transientToplevel != null && surfaces[id].transientToplevel == surface) {
271                 var childSurface = surfaces[id];
272                 childSurface.x += surface.x - oldX;
273                 childSurface.y += surface.y - oldY;
274                 sendInput ("w", [childSurface.id, childSurface.x, childSurface.y, childSurface.width, childSurface.height]);
275             }
276         }
277     }
278
279 }
280
281 function registerWindow(win)
282 {
283     toplevelWindows.push(win);
284     win.onresize = function(ev) { updateBrowserWindowGeometry(ev.target); };
285     if (!windowGeometryTimeout)
286         windowGeometryTimeout = setInterval(function () { toplevelWindows.forEach(updateBrowserWindowGeometry); }, 2000);
287 }
288
289 function unregisterWindow(win)
290 {
291     var i = toplevelWindows.indexOf(win);
292     if (i >= 0)
293         toplevelWindows.splice(i, 1);
294
295     if (windowGeometryTimeout && toplevelWindows.length == 0) {
296         clearInterval(windowGeometryTimeout);
297         windowGeometryTimeout = null;
298     }
299 }
300
301 function getTransientToplevel(surface)
302 {
303     while (surface.transientParent != 0) {
304         surface = surfaces[surface.transientParent];
305         if (surface.window)
306             return surface;
307     }
308     return null;
309 }
310
311 function cmdCreateSurface(id, x, y, width, height, isTemp)
312 {
313     var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };
314     surface.drawQueue = [];
315     surface.transientParent = 0;
316     surface.visible = false;
317     surface.window = null;
318     surface.document = document;
319     surface.transientToplevel = null;
320
321     var canvas = document.createElement("canvas");
322     canvas.width = width;
323     canvas.height = height;
324     canvas.surface = surface;
325     canvas.style["position"] = "absolute";
326     canvas.style["left"] = "0px";
327     canvas.style["top"] = "0px";
328     canvas.style["display"] = "none";
329     document.body.appendChild(canvas);
330     surface.canvas = canvas;
331
332     var context = canvas.getContext("2d");
333     context.globalCompositeOperation = "source-over";
334     surface.context = context;
335
336     surfaces[id] = surface;
337 }
338
339 function cmdShowSurface(id)
340 {
341     var surface = surfaces[id];
342
343     if (surface.visible)
344         return;
345     surface.visible = true;
346
347     var xOffset = surface.x;
348     var yOffset = surface.y;
349
350     if (useToplevelWindows) {
351         var doc = document;
352         if (!surface.isTemp) {
353             var win = window.open('','_blank',
354                                   'width='+surface.width+',height='+surface.height+
355                                   ',left='+surface.x+',top='+surface.y+',screenX='+surface.x+',screenY='+surface.y+
356                                   ',location=no,menubar=no,scrollbars=no,toolbar=no');
357             win.surface = surface;
358             registerWindow(win);
359             doc = win.document;
360             doc.open();
361             doc.write("<body></body>");
362             setupDocument(doc);
363
364             surface.window = win;
365             xOffset = 0;
366             yOffset = 0;
367         } else {
368             surface.transientToplevel = getTransientToplevel(surface);
369             if (surface.transientToplevel) {
370                 doc = surface.transientToplevel.window.document;
371                 xOffset = surface.x - surface.transientToplevel.x;
372                 yOffset = surface.y - surface.transientToplevel.y;
373             }
374         }
375
376         ensureSurfaceInDocument(surface, doc);
377     }
378
379     surface.canvas.style["position"] = "absolute";
380     surface.canvas.style["left"] = xOffset + "px";
381     surface.canvas.style["top"] = yOffset + "px";
382     surface.canvas.style["display"] = "inline";
383 }
384
385 function cmdHideSurface(id)
386 {
387     var surface = surfaces[id];
388
389     if (!surface.visible)
390         return;
391     surface.visible = false;
392
393     surface.canvas.style["display"] = "none";
394
395     // Import the canvas into the main document
396     ensureSurfaceInDocument(surface, document);
397
398     if (surface.window) {
399         unregisterWindow(surface.window);
400         surface.window.close();
401         surface.window = null;
402     }
403 }
404
405 function cmdSetTransientFor(id, parentId)
406 {
407     var surface = surfaces[id];
408
409     if (surface.transientParent == parentId)
410         return;
411
412     surface.transientParent = parentId;
413     if (surface.visible && surface.isTemp) {
414         alert("TODO: move temps between transient parents when visible");
415     }
416 }
417
418 function cmdDeleteSurface(id)
419 {
420     var canvas = surfaces[id].canvas;
421     delete surfaces[id];
422     canvas.parentNode.removeChild(canvas);
423 }
424
425 function cmdMoveSurface(id, x, y)
426 {
427     var surface = surfaces[id];
428     surface.x = x;
429     surface.y = y;
430
431     if (surface.visible) {
432         if (surface.window) {
433             /* TODO: This moves the outer frame position, we really want the inner position.
434              * However this isn't *strictly* invalid, as any WM could have done whatever it
435              * wanted with the positioning of the window.
436              */
437             //surface.window.moveTo(surface.x, surface.y);
438         } else {
439             var xOffset = surface.x;
440             var yOffset = surface.y;
441
442             var transientToplevel = getTransientToplevel(surface);
443             if (transientToplevel) {
444                 xOffset = surface.x - transientToplevel.x;
445                 yOffset = surface.y - transientToplevel.y;
446             }
447
448             surface.canvas.style["left"] = xOffset + "px";
449             surface.canvas.style["top"] = yOffset + "px";
450         }
451     }
452 }
453
454 function cmdResizeSurface(id, w, h)
455 {
456     var surface = surfaces[id];
457
458     surface.width = w;
459     surface.height = h;
460
461     /* Flush any outstanding draw ops before changing size */
462     flushSurface(surface);
463
464     resizeCanvas(surface.canvas, w, h);
465
466     if (surface.window) {
467         resizeBrowserWindow(surface.window, w, h);
468     }
469 }
470
471 function cmdFlushSurface(id)
472 {
473     flushSurface(surfaces[id]);
474 }
475
476 function cmdGrabPointer(id, ownerEvents)
477 {
478     doGrab(id, ownerEvents, false);
479     sendInput ("g", []);
480 }
481
482 function cmdUngrabPointer()
483 {
484     sendInput ("u", []);
485
486     grab.window = null;
487 }
488
489 function handleCommands(cmdObj)
490 {
491     var cmd = cmdObj.data;
492     var i = cmdObj.pos;
493
494     while (i < cmd.length) {
495         var command = cmd[i++];
496         lastSerial = base64_32(cmd, i);
497         i = i + 6;
498         switch (command) {
499         case 's': // create new surface
500             var id = base64_16(cmd, i);
501             i = i + 3;
502             var x = base64_16(cmd, i);
503             i = i + 3;
504             var y = base64_16(cmd, i);
505             i = i + 3;
506             var w = base64_16(cmd, i);
507             i = i + 3;
508             var h = base64_16(cmd, i);
509             i = i + 3;
510             var isTemp = cmd[i] == '1';
511             i = i + 1;
512             cmdCreateSurface(id, x, y, w, h, isTemp);
513             break;
514
515         case 'S': // Show a surface
516             var id = base64_16(cmd, i);
517             i = i + 3;
518             cmdShowSurface(id);
519             break;
520
521         case 'H': // Hide a surface
522             var id = base64_16(cmd, i);
523             i = i + 3;
524             cmdHideSurface(id);
525             break;
526
527         case 'p': // Set transient parent
528             var id = base64_16(cmd, i);
529             i = i + 3;
530             var parentId = base64_16(cmd, i);
531             i = i + 3;
532             cmdSetTransientFor(id, parentId);
533             break;
534
535         case 'd': // Delete surface
536             var id = base64_16(cmd, i);
537             i = i + 3;
538             cmdDeleteSurface(id);
539             break;
540
541         case 'm': // Move a surface
542             var id = base64_16(cmd, i);
543             i = i + 3;
544             var x = base64_16(cmd, i);
545             i = i + 3;
546             var y = base64_16(cmd, i);
547             i = i + 3;
548             cmdMoveSurface(id, x, y);
549             break;
550
551         case 'r': // Resize a surface
552             var id = base64_16(cmd, i);
553             i = i + 3;
554             var w = base64_16(cmd, i);
555             i = i + 3;
556             var h = base64_16(cmd, i);
557             i = i + 3;
558             cmdResizeSurface(id, w, h);
559             break;
560
561         case 'i': // Put image data surface
562             var q = new Object();
563             q.op = 'i';
564             q.id = base64_16(cmd, i);
565             i = i + 3;
566             q.x = base64_16(cmd, i);
567             i = i + 3;
568             q.y = base64_16(cmd, i);
569             i = i + 3;
570             var size = base64_32(cmd, i);
571             i = i + 6;
572             var url = cmd.slice(i, i + size);
573             i = i + size;
574             q.img = new Image();
575             q.img.src = url;
576             surfaces[q.id].drawQueue.push(q);
577             if (!q.img.complete) {
578                 cmdObj.pos = i;
579                 q.img.onload = function() { handleOutstanding(); };
580                 return false;
581             }
582             break;
583
584         case 'b': // Copy rects
585             var q = new Object();
586             q.op = 'b';
587             q.id = base64_16(cmd, i);
588             i = i + 3;
589
590             var nrects = base64_16(cmd, i);
591             i = i + 3;
592
593             q.rects = [];
594             for (var r = 0; r < nrects; r++) {
595                 var rect = new Object();
596                 rect.x = base64_16(cmd, i);
597                 i = i + 3;
598                 rect.y = base64_16(cmd, i);
599                 i = i + 3;
600                 rect.w = base64_16(cmd, i);
601                 i = i + 3;
602                 rect.h = base64_16(cmd, i);
603                 i = i + 3;
604                 q.rects.push (rect);
605             }
606
607             q.dx = base64_16s(cmd, i);
608             i = i + 3;
609             q.dy = base64_16s(cmd, i);
610             i = i + 3;
611             surfaces[q.id].drawQueue.push(q);
612             break;
613
614         case 'f': // Flush surface
615             var id = base64_16(cmd, i);
616             i = i + 3;
617
618             cmdFlushSurface(id);
619             break;
620
621         case 'g': // Grab
622             var id = base64_16(cmd, i);
623             i = i + 3;
624             var ownerEvents = cmd[i++] == '1';
625
626             cmdGrabPointer(id, ownerEvents);
627             break;
628
629         case 'u': // Ungrab
630             cmdUngrabPointer();
631             break;
632         default:
633             alert("Unknown op " + command);
634         }
635     }
636     return true;
637 }
638
639 function handleOutstanding()
640 {
641     while (outstandingCommands.length > 0) {
642         var cmd = outstandingCommands.shift();
643         if (!handleCommands(cmd)) {
644             outstandingCommands.unshift(cmd);
645             return;
646         }
647     }
648 }
649
650 function handleLoad(event)
651 {
652     var cmdObj = {};
653     cmdObj.data = event.target.responseText;
654     cmdObj.pos = 0;
655
656     outstandingCommands.push(cmdObj);
657     if (outstandingCommands.length == 1) {
658         handleOutstanding();
659     }
660 }
661
662 function getSurfaceId(ev) {
663     var surface = ev.target.surface;
664     if (surface != undefined)
665         return surface.id;
666     return 0;
667 }
668
669 function sendInput(cmd, args)
670 {
671     if (inputSocket != null) {
672         inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
673     }
674 }
675
676 function getDocumentCoordinates(element)
677 {
678     var res = new Object();
679     res.x = element.offsetLeft;
680     res.y = element.offsetTop;
681
682     var offsetParent = element.offsetParent;
683     while (offsetParent != null) {
684         res.x += offsetParent.offsetLeft;
685         res.y += offsetParent.offsetTop;
686         offsetParent = offsetParent.offsetParent;
687     }
688     return res;
689 }
690
691 function getPositionsFromAbsCoord(absX, absY, relativeId) {
692     var res = Object();
693
694     res.rootX = absX;
695     res.rootY = absY;
696     res.winX = absX;
697     res.winY = absY;
698     if (relativeId != 0) {
699         var pos = getDocumentCoordinates(surfaces[relativeId].canvas);
700         res.winX = res.winX - pos.x;
701         res.winY = res.winY - pos.y;
702     }
703
704     return res;
705 }
706
707 function getPositionsFromEvent(ev, relativeId) {
708     var res = getPositionsFromAbsCoord(ev.pageX, ev.pageY, relativeId);
709
710     lastX = res.rootX;
711     lastY = res.rootY;
712
713     return res;
714 }
715
716 function getEffectiveEventTarget (id) {
717     if (grab.window != null) {
718         if (!grab.ownerEvents)
719             return grab.window;
720         if (id == 0)
721             return grab.window;
722     }
723     return id;
724 }
725
726 function updateForEvent(ev) {
727     lastTimeStamp = ev.timeStamp;
728     if (ev.target.surface && ev.target.surface.window) {
729         var win = ev.target.surface.window;
730         if (ev.screenX != undefined && ev.clientX != undefined) {
731             var newFrameSizeX = ev.screenX - ev.clientX - win.screenX;
732             var newFrameSizeY = ev.screenY - ev.clientY - win.screenY;
733             if (newFrameSizeX != frameSizeX || newFrameSizeY != frameSizeY) {
734                 frameSizeX = newFrameSizeX;
735                 frameSizeY = newFrameSizeY;
736                 toplevelWindows.forEach(updateBrowserWindowGeometry);
737             }
738         }
739         updateBrowserWindowGeometry(win);
740     }
741 }
742
743 function onMouseMove (ev) {
744     updateForEvent(ev);
745     var id = getSurfaceId(ev);
746     id = getEffectiveEventTarget (id);
747     var pos = getPositionsFromEvent(ev, id);
748     sendInput ("m", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
749 }
750
751 function onMouseOver (ev) {
752     updateForEvent(ev);
753     var id = getSurfaceId(ev);
754     realWindowWithMouse = id;
755     id = getEffectiveEventTarget (id);
756     var pos = getPositionsFromEvent(ev, id);
757     windowWithMouse = id;
758     if (windowWithMouse != 0) {
759         sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
760     }
761 }
762
763 function onMouseOut (ev) {
764     updateForEvent(ev);
765     var id = getSurfaceId(ev);
766     var origId = id;
767     id = getEffectiveEventTarget (id);
768     var pos = getPositionsFromEvent(ev, id);
769
770     if (id != 0) {
771         sendInput ("l", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
772     }
773     realWindowWithMouse = 0;
774     windowWithMouse = 0;
775 }
776
777 function doGrab(id, ownerEvents, implicit) {
778     var pos;
779
780     if (windowWithMouse != id) {
781         if (windowWithMouse != 0) {
782             pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
783             sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
784         }
785         pos = getPositionsFromAbsCoord(lastX, lastY, id);
786         sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
787         windowWithMouse = id;
788     }
789
790     grab.window = id;
791     grab.ownerEvents = ownerEvents;
792     grab.implicit = implicit;
793 }
794
795 function doUngrab() {
796     var pos;
797     if (realWindowWithMouse != windowWithMouse) {
798         if (windowWithMouse != 0) {
799             pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
800             sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
801         }
802         if (realWindowWithMouse != 0) {
803             pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);
804             sendInput ("e", [realWindowWithMouse, realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
805         }
806         windowWithMouse = realWindowWithMouse;
807     }
808     grab.window = null;
809 }
810
811 function onMouseDown (ev) {
812     updateForEvent(ev);
813     var id = getSurfaceId(ev);
814     id = getEffectiveEventTarget (id);
815     var pos = getPositionsFromEvent(ev, id);
816     if (grab.window != null)
817         doGrab (id, false, true);
818     var button = ev.button + 1;
819     lastState = lastState | getButtonMask (button);
820     sendInput ("b", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
821 }
822
823 function onMouseUp (ev) {
824     updateForEvent(ev);
825     var id = getSurfaceId(ev);
826     id = getEffectiveEventTarget (id);
827     var pos = getPositionsFromEvent(ev, id);
828     var button = ev.button + 1;
829     lastState = lastState & ~getButtonMask (button);
830     sendInput ("B", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
831
832     if (grab.window != null && grab.implicit)
833         doUngrab(ev.timeStamp);
834 }
835
836 var lastKeyDown = 0;
837 function onKeyDown (ev) {
838     updateForEvent(ev);
839     var keyCode = ev.keyCode;
840     if (keyCode != lastKeyDown) {
841         sendInput ("k", [keyCode]);
842         lastKeyDown = keyCode;
843     }
844 }
845
846 function onKeyUp (ev) {
847     updateForEvent(ev);
848     var keyCode = ev.keyCode;
849     sendInput ("K", [keyCode]);
850     lastKeyDown = 0;
851 }
852
853 function cancelEvent(ev)
854 {
855     ev = ev ? ev : window.event;
856     if (ev.stopPropagation)
857         ev.stopPropagation();
858     if (ev.preventDefault)
859         ev.preventDefault();
860     ev.cancelBubble = true;
861     ev.cancel = true;
862     ev.returnValue = false;
863     return false;
864 }
865
866 function onMouseWheel(ev)
867 {
868     updateForEvent(ev);
869     ev = ev ? ev : window.event;
870
871     var id = getSurfaceId(ev);
872     var pos = getPositionsFromEvent(ev, id);
873
874     var offset = ev.detail ? ev.detail : ev.wheelDelta;
875     var dir = 0;
876     if (offset > 0)
877         dir = 1;
878     sendInput ("s", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
879
880     return cancelEvent(ev);
881 }
882
883 function setupDocument(document)
884 {
885     document.oncontextmenu = function () { return false; };
886     document.onmousemove = onMouseMove;
887     document.onmouseover = onMouseOver;
888     document.onmouseout = onMouseOut;
889     document.onmousedown = onMouseDown;
890     document.onmouseup = onMouseUp;
891     document.onkeydown = onKeyDown;
892     document.onkeyup = onKeyUp;
893
894     if (document.addEventListener) {
895       document.addEventListener('DOMMouseScroll', onMouseWheel, false);
896       document.addEventListener('mousewheel', onMouseWheel, false);
897     } else if (document.attachEvent) {
898       element.attachEvent("onmousewheel", onMouseWheel);
899     }
900 }
901
902 function connect()
903 {
904     var xhr = createXHR();
905     if (xhr) {
906         if (typeof xhr.multipart == 'undefined') {
907             alert("Sorry, this example only works in browsers that support multipart.");
908             return;
909         }
910
911         xhr.multipart = true;
912         xhr.open("GET", "/output", true);
913         xhr.onload = handleLoad;
914         xhr.send(null);
915     }
916
917     if ("WebSocket" in window) {
918         var loc = window.location.toString().replace("http:", "ws:");
919         loc = loc.substr(0, loc.lastIndexOf('/')) + "/input";
920         var ws = new WebSocket(loc, "broadway");
921         ws.onopen = function() {
922             inputSocket = ws;
923         };
924         ws.onclose = function() {
925             inputSocket = null;
926         };
927     } else {
928         alert("WebSocket not supported, input will not work!");
929     }
930     setupDocument(document);
931     window.onunload = function (ev) {
932         for (var i = 0; i < toplevelWindows.length; i++)
933             toplevelWindows[i].close();
934     };
935 }