]> Pileus Git - ~andy/gtk/blob - gdk/broadway/broadway.js
[broadway] Make pointer grabs not roundtrip
[~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 var grab = new Object();
80 grab.window = null;
81 grab.ownerEvents = false;
82 grab.time = 0;
83 grab.implicit = false;
84 var lastSerial = 0;
85 var lastX = 0;
86 var lastY = 0;
87 var lastState;
88 var lastTimeStamp = 0;
89 var realWindowWithMouse = 0;
90 var windowWithMouse = 0;
91 var surfaces = {};
92 var outstandingCommands = new Array();
93 var inputSocket = null;
94
95 function initContext(canvas, x, y, id)
96 {
97     canvas.surfaceId = id;
98     canvas.style["position"] = "absolute";
99     canvas.style["left"] = x + "px";
100     canvas.style["top"] = y + "px";
101     canvas.style["display"] = "none";
102     var context = canvas.getContext("2d");
103     context.globalCompositeOperation = "source-over";
104     document.body.appendChild(canvas);
105     context.drawQueue = [];
106
107     return context;
108 }
109
110 var GDK_CROSSING_NORMAL = 0;
111 var GDK_CROSSING_GRAB = 1;
112 var GDK_CROSSING_UNGRAB = 2;
113
114 // GdkModifierType
115 var GDK_SHIFT_MASK = 1 << 0;
116 var GDK_LOCK_MASK     = 1 << 1;
117 var GDK_CONTROL_MASK  = 1 << 2;
118 var GDK_MOD1_MASK     = 1 << 3;
119 var GDK_MOD2_MASK     = 1 << 4;
120 var GDK_MOD3_MASK     = 1 << 5;
121 var GDK_MOD4_MASK     = 1 << 6;
122 var GDK_MOD5_MASK     = 1 << 7;
123 var GDK_BUTTON1_MASK  = 1 << 8;
124 var GDK_BUTTON2_MASK  = 1 << 9;
125 var GDK_BUTTON3_MASK  = 1 << 10;
126 var GDK_BUTTON4_MASK  = 1 << 11;
127 var GDK_BUTTON5_MASK  = 1 << 12;
128 var GDK_SUPER_MASK    = 1 << 26;
129 var GDK_HYPER_MASK    = 1 << 27;
130 var GDK_META_MASK     = 1 << 28;
131 var GDK_RELEASE_MASK  = 1 << 30;
132
133 function getButtonMask (button) {
134     if (button == 1)
135         return GDK_BUTTON1_MASK;
136     if (button == 2)
137         return GDK_BUTTON2_MASK;
138     if (button == 3)
139         return GDK_BUTTON3_MASK;
140     if (button == 4)
141         return GDK_BUTTON4_MASK;
142     if (button == 5)
143         return GDK_BUTTON5_MASK;
144     return 0;
145 }
146
147 function flushSurface(surface)
148 {
149     var commands = surface.drawQueue;
150     surface.queue = [];
151     var i = 0;
152     for (i = 0; i < commands.length; i++) {
153         var cmd = commands[i];
154         var context = surfaces[cmd.id];
155         switch (cmd.op) {
156       /* put image data surface */
157         case 'i':
158             context.globalCompositeOperation = "source-over";
159             context.drawImage(cmd.img, cmd.x, cmd.y);
160             break;
161
162       /* copy rects */
163         case 'b':
164             context.save();
165             context.beginPath();
166
167             var minx;
168             var miny;
169             var maxx;
170             var maxy;
171             for (var j = 0; j < cmd.rects.length; j++) {
172                 var rect = cmd.rects[j];
173                 context.rect(rect.x, rect.y, rect.w, rect.h);
174                 if (j == 0) {
175                     minx = rect.x;
176                     miny = rect.y;
177                     maxx = rect.x + rect.w;
178                     maxy = rect.y + rect.h;
179                 } else {
180                     if (rect.x < minx)
181                         minx = rect.x;
182                     if (rect.y < miny)
183                         miny = rect.y;
184                     if (rect.x + rect.w > maxx)
185                         maxx = rect.x + rect.w;
186                     if (rect.y + rect.h > maxy)
187                         maxy = rect.y + rect.h;
188                 }
189             }
190             context.clip();
191             context.globalCompositeOperation = "copy";
192             context.drawImage(context.canvas,
193                               minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,
194                               minx, miny, maxx - minx, maxy - miny);
195             context.restore();
196             break;
197
198       default:
199             alert("Unknown drawing op " + cmd.op);
200         }
201     }
202 }
203
204 function handleCommands(cmdObj)
205 {
206     var cmd = cmdObj.data;
207     var i = cmdObj.pos;
208
209     while (i < cmd.length) {
210         var command = cmd[i++];
211         lastSerial = base64_32(cmd, i);
212         i = i + 6;
213         switch (command) {
214         case 's': // create new surface
215             var id = base64_16(cmd, i);
216             i = i + 3;
217             var x = base64_16(cmd, i);
218             i = i + 3;
219             var y = base64_16(cmd, i);
220             i = i + 3;
221             var w = base64_16(cmd, i);
222             i = i + 3;
223             var h = base64_16(cmd, i);
224             i = i + 3;
225             var surface = document.createElement("canvas");
226             surface.width = w;
227             surface.height = h;
228             surfaces[id] = initContext(surface, x, y, id);
229             break;
230
231         case 'S': // Show a surface
232             var id = base64_16(cmd, i);
233             i = i + 3;
234             surfaces[id].canvas.style["display"] = "inline";
235             break;
236
237         case 'H': // Hide a surface
238             var id = base64_16(cmd, i);
239             i = i + 3;
240             surfaces[id].canvas.style["display"] = "none";
241             break;
242
243         case 'd': // Delete surface
244             var id = base64_16(cmd, i);
245             i = i + 3;
246             var canvas = surfaces[id].canvas;
247             delete surfaces[id];
248             canvas.parentNode.removeChild(canvas);
249
250             break;
251
252         case 'm': // Move a surface
253             var id = base64_16(cmd, i);
254             i = i + 3;
255             var x = base64_16(cmd, i);
256             i = i + 3;
257             var y = base64_16(cmd, i);
258             i = i + 3;
259             surfaces[id].canvas.style["left"] = x + "px";
260             surfaces[id].canvas.style["top"] = y + "px";
261             break;
262
263         case 'r': // Resize a surface
264             var id = base64_16(cmd, i);
265             i = i + 3;
266             var w = base64_16(cmd, i);
267             i = i + 3;
268             var h = base64_16(cmd, i);
269             i = i + 3;
270             var surface = surfaces[id];
271
272             /* Flush any outstanding draw ops before changing size */
273             flushSurface(surface);
274
275             /* Canvas resize clears the data, so we need to save it first */
276             var tmpCanvas = document.createElement("canvas");
277             tmpCanvas.width = surface.canvas.width;
278             tmpCanvas.height = surface.canvas.height;
279             var tmpContext = tmpCanvas.getContext("2d");
280             tmpContext.globalCompositeOperation = "copy";
281             tmpContext.drawImage(surface.canvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
282
283             surface.canvas.width = w;
284             surface.canvas.height = h;
285
286             surface.globalCompositeOperation = "copy";
287             surface.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
288
289             break;
290
291         case 'i': // Put image data surface
292             var q = new Object();
293             q.op = 'i';
294             q.id = base64_16(cmd, i);
295             i = i + 3;
296             q.x = base64_16(cmd, i);
297             i = i + 3;
298             q.y = base64_16(cmd, i);
299             i = i + 3;
300             var size = base64_32(cmd, i);
301             i = i + 6;
302             var url = cmd.slice(i, i + size);
303             i = i + size;
304             q.img = new Image();
305             q.img.src = url;
306             surfaces[q.id].drawQueue.push(q);
307             if (!q.img.complete) {
308                 cmdObj.pos = i;
309                 q.img.onload = function() { handleOutstanding(); };
310                 return false;
311             }
312             break;
313
314         case 'b': // Copy rects
315             var q = new Object();
316             q.op = 'b';
317             q.id = base64_16(cmd, i);
318             i = i + 3;
319
320             var nrects = base64_16(cmd, i);
321             i = i + 3;
322
323             q.rects = [];
324             for (var r = 0; r < nrects; r++) {
325                 var rect = new Object();
326                 rect.x = base64_16(cmd, i);
327                 i = i + 3;
328                 rect.y = base64_16(cmd, i);
329                 i = i + 3;
330                 rect.w = base64_16(cmd, i);
331                 i = i + 3;
332                 rect.h = base64_16(cmd, i);
333                 i = i + 3;
334                 q.rects.push (rect);
335             }
336
337             q.dx = base64_16s(cmd, i);
338             i = i + 3;
339             q.dy = base64_16s(cmd, i);
340             i = i + 3;
341             surfaces[q.id].drawQueue.push(q);
342             break;
343
344         case 'f': // Flush surface
345             var id = base64_16(cmd, i);
346             i = i + 3;
347
348             flushSurface(surfaces[id]);
349             break;
350
351         case 'q': // Query pointer
352             var id = base64_16(cmd, i);
353             i = i + 3;
354
355             var pos = getPositionsFromAbsCoord(lastX, lastY, id);
356
357             sendInput ("q", [pos.rootX, pos.rootY, pos.winX, pos.winY, windowWithMouse]);
358             break;
359
360         case 'g': // Grab
361             var id = base64_16(cmd, i);
362             i = i + 3;
363             var ownerEvents = cmd[i++] == '1';
364
365             doGrab(id, ownerEvents, time, false);
366
367             sendInput ("g", []);
368             break;
369
370         case 'u': // Ungrab
371             var time = base64_32(cmd, i);
372             i = i + 6;
373             sendInput ("u", []);
374
375             if (grab.window != null) {
376                 if (grab.time == 0 || time == 0 ||
377                     grab.time < time)
378                     grab.window = null;
379             }
380
381             break;
382         default:
383             alert("Unknown op " + command);
384         }
385     }
386     return true;
387 }
388
389 function handleOutstanding()
390 {
391     while (outstandingCommands.length > 0) {
392         var cmd = outstandingCommands.shift();
393         if (!handleCommands(cmd)) {
394             outstandingCommands.unshift(cmd);
395             return;
396         }
397     }
398 }
399
400 function handleLoad(event)
401 {
402     var cmdObj = {};
403     cmdObj.data = event.target.responseText;
404     cmdObj.pos = 0;
405
406     outstandingCommands.push(cmdObj);
407     if (outstandingCommands.length == 1) {
408         handleOutstanding();
409     }
410 }
411
412 function getSurfaceId(ev) {
413     var id = ev.target.surfaceId;
414     if (id != undefined)
415         return id;
416     return 0;
417 }
418
419 function sendInput(cmd, args)
420 {
421     if (inputSocket != null) {
422         inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
423     }
424 }
425
426 function getDocumentCoordinates(element)
427 {
428     var res = new Object();
429     res.x = element.offsetLeft;
430     res.y = element.offsetTop;
431
432     var offsetParent = element.offsetParent;
433     while (offsetParent != null) {
434         res.x += offsetParent.offsetLeft;
435         res.y += offsetParent.offsetTop;
436         offsetParent = offsetParent.offsetParent;
437     }
438     return res;
439 }
440
441 function getPositionsFromAbsCoord(absX, absY, relativeId) {
442     var res = Object();
443
444     res.rootX = absX;
445     res.rootY = absY;
446     res.winX = absX;
447     res.winY = absY;
448     if (relativeId != 0) {
449         var pos = getDocumentCoordinates(surfaces[relativeId].canvas);
450         res.winX = res.winX - pos.x;
451         res.winY = res.winY - pos.y;
452     }
453
454     return res;
455 }
456
457 function getPositionsFromEvent(ev, relativeId) {
458     var res = getPositionsFromAbsCoord(ev.pageX, ev.pageY, relativeId);
459
460     lastX = res.rootX;
461     lastY = res.rootY;
462
463     return res;
464 }
465
466 function getEffectiveEventTarget (id) {
467     if (grab.window != null) {
468         if (!grab.ownerEvents)
469             return grab.window;
470         if (id == 0)
471             return grab.window;
472     }
473     return id;
474 }
475
476 function updateForEvent(ev) {
477     lastTimeStamp = ev.timeStamp;
478 }
479
480 function onMouseMove (ev) {
481     updateForEvent(ev);
482     var id = getSurfaceId(ev);
483     id = getEffectiveEventTarget (id);
484     var pos = getPositionsFromEvent(ev, id);
485     sendInput ("m", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
486 }
487
488 function onMouseOver (ev) {
489     updateForEvent(ev);
490     var id = getSurfaceId(ev);
491     realWindowWithMouse = id;
492     id = getEffectiveEventTarget (id);
493     var pos = getPositionsFromEvent(ev, id);
494     windowWithMouse = id;
495     if (windowWithMouse != 0) {
496         sendInput ("e", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
497     }
498 }
499
500 function onMouseOut (ev) {
501     updateForEvent(ev);
502     var id = getSurfaceId(ev);
503     var origId = id;
504     id = getEffectiveEventTarget (id);
505     var pos = getPositionsFromEvent(ev, id);
506
507     if (id != 0) {
508         sendInput ("l", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
509     }
510     realWindowWithMouse = 0;
511     windowWithMouse = 0;
512 }
513
514 function doGrab(id, ownerEvents, time, implicit) {
515     var pos;
516
517     if (windowWithMouse != id) {
518         if (windowWithMouse != 0) {
519             pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
520             sendInput ("l", [windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
521         }
522         pos = getPositionsFromAbsCoord(lastX, lastY, id);
523         sendInput ("e", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
524         windowWithMouse = id;
525     }
526
527     grab.window = id;
528     grab.ownerEvents = ownerEvents;
529     grab.time = time;
530     grab.implicit = implicit;
531 }
532
533 function doUngrab(time) {
534     var pos;
535     if (realWindowWithMouse != windowWithMouse) {
536         if (windowWithMouse != 0) {
537             pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
538             sendInput ("l", [windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
539         }
540         if (realWindowWithMouse != 0) {
541             pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);
542             sendInput ("e", [realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
543         }
544         windowWithMouse = realWindowWithMouse;
545     }
546     grab.window = null;
547 }
548
549 function onMouseDown (ev) {
550     updateForEvent(ev);
551     var id = getSurfaceId(ev);
552     id = getEffectiveEventTarget (id);
553     var pos = getPositionsFromEvent(ev, id);
554     if (grab.window != null)
555         doGrab (id, false, ev.timeStamp, true);
556     var button = ev.button + 1;
557     lastState = lastState | getButtonMask (button);
558     sendInput ("b", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
559 }
560
561 function onMouseUp (ev) {
562     updateForEvent(ev);
563     var id = getSurfaceId(ev);
564     id = getEffectiveEventTarget (id);
565     var pos = getPositionsFromEvent(ev, id);
566     var button = ev.button + 1;
567     lastState = lastState & ~getButtonMask (button);
568     sendInput ("B", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
569
570     if (grab.window != null && grab.implicit)
571         doUngrab(ev.timeStamp);
572 }
573
574 var lastKeyDown = 0;
575 function onKeyDown (ev) {
576     updateForEvent(ev);
577     var keyCode = ev.keyCode;
578     if (keyCode != lastKeyDown) {
579         sendInput ("k", [keyCode]);
580         lastKeyDown = keyCode;
581     }
582 }
583
584 function onKeyUp (ev) {
585     updateForEvent(ev);
586     var keyCode = ev.keyCode;
587     sendInput ("K", [keyCode]);
588     lastKeyDown = 0;
589 }
590
591 function cancelEvent(ev)
592 {
593     ev = ev ? ev : window.event;
594     if (ev.stopPropagation)
595         ev.stopPropagation();
596     if (ev.preventDefault)
597         ev.preventDefault();
598     ev.cancelBubble = true;
599     ev.cancel = true;
600     ev.returnValue = false;
601     return false;
602 }
603
604 function onMouseWheel(ev)
605 {
606     updateForEvent(ev);
607     ev = ev ? ev : window.event;
608
609     var id = getSurfaceId(ev);
610     var pos = getPositionsFromEvent(ev, id);
611
612     var offset = ev.detail ? ev.detail : ev.wheelDelta;
613     var dir = 0;
614     if (offset > 0)
615         dir = 1;
616     sendInput ("s", [id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
617
618     return cancelEvent(ev);
619 }
620
621 function connect()
622 {
623     var xhr = createXHR();
624     if (xhr) {
625         if (typeof xhr.multipart == 'undefined') {
626             alert("Sorry, this example only works in browsers that support multipart.");
627             return;
628         }
629
630         xhr.multipart = true;
631         xhr.open("GET", "/output", true);
632         xhr.onload = handleLoad;
633         xhr.send(null);
634     }
635
636     if ("WebSocket" in window) {
637         var loc = window.location.toString().replace("http:", "ws:");
638         loc = loc.substr(0, loc.lastIndexOf('/')) + "/input";
639         var ws = new WebSocket(loc, "broadway");
640         ws.onopen = function() {
641             inputSocket = ws;
642         };
643         ws.onclose = function() {
644             inputSocket = null;
645         };
646     } else {
647         alert("WebSocket not supported, input will not work!");
648     }
649     document.oncontextmenu = function () { return false; };
650     document.onmousemove = onMouseMove;
651     document.onmouseover = onMouseOver;
652     document.onmouseout = onMouseOut;
653     document.onmousedown = onMouseDown;
654     document.onmouseup = onMouseUp;
655     document.onkeydown = onKeyDown;
656     document.onkeyup = onKeyUp;
657
658     if (document.addEventListener) {
659         document.addEventListener('DOMMouseScroll', onMouseWheel, false);
660         document.addEventListener('mousewheel', onMouseWheel, false);
661     } else if (document.attachEvent) {
662         element.attachEvent("onmousewheel", onMouseWheel);
663     }
664 }