1 /* Helper functions for debugging */
5 logDiv = document.createElement('div');
6 document.body.appendChild(logDiv);
7 logDiv.style["position"] = "absolute";
8 logDiv.style["right"] = "0px";
10 logDiv.appendChild(document.createTextNode(str));
11 logDiv.appendChild(document.createElement('br'));
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
25 function base64_8(str, index) {
27 (base64Values[str.charCodeAt(index)]) +
28 (base64Values[str.charCodeAt(index+1)] << 6);
32 function base64_16(str, index) {
34 (base64Values[str.charCodeAt(index)]) +
35 (base64Values[str.charCodeAt(index+1)] << 6) +
36 (base64Values[str.charCodeAt(index+2)] << 12);
40 function base64_16s(str, index) {
41 var v = base64_16(str, index);
48 function base64_24(str, index) {
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);
57 function base64_32(str, index) {
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);
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) {}
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;
84 var outerW = window.outerWidth;
85 var outerH = window.outerHeight;
87 window.resizeTo(w + outerW - innerW,
91 function resizeCanvas(canvas, w, h)
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);
104 var context = canvas.getContext("2d");
106 context.globalCompositeOperation = "copy";
107 context.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
110 var useToplevelWindows = false;
111 var toplevelWindows = [];
112 var grab = new Object();
114 grab.ownerEvents = false;
115 grab.implicit = false;
120 var lastTimeStamp = 0;
121 var realWindowWithMouse = 0;
122 var windowWithMouse = 0;
124 var outstandingCommands = new Array();
125 var inputSocket = null;
130 var GDK_CROSSING_NORMAL = 0;
131 var GDK_CROSSING_GRAB = 1;
132 var GDK_CROSSING_UNGRAB = 2;
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;
153 function getButtonMask (button) {
155 return GDK_BUTTON1_MASK;
157 return GDK_BUTTON2_MASK;
159 return GDK_BUTTON3_MASK;
161 return GDK_BUTTON4_MASK;
163 return GDK_BUTTON5_MASK;
167 function flushSurface(surface)
169 var commands = surface.drawQueue;
171 var context = surface.context;
173 for (i = 0; i < commands.length; i++) {
174 var cmd = commands[i];
176 case 'i': // put image data surface
177 context.globalCompositeOperation = "source-over";
178 context.drawImage(cmd.img, cmd.x, cmd.y);
181 case 'b': // copy rects
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);
195 maxx = rect.x + rect.w;
196 maxy = rect.y + rect.h;
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;
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);
217 alert("Unknown drawing op " + cmd.op);
222 function ensureSurfaceInDocument(surface, doc)
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);
231 surface.canvas = canvas;
232 surface.document = doc;
234 var context = canvas.getContext("2d");
235 context.globalCompositeOperation = "source-over";
236 surface.context = context;
240 var windowGeometryTimeout = null;
242 function updateBrowserWindowGeometry(win) {
246 var surface = win.surface;
248 var innerW = win.innerWidth;
249 var innerH = win.innerHeight;
253 if (frameSizeX > 0) {
254 x = win.screenX + frameSizeX;
255 y = win.screenY + frameSizeY;
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;
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]);
281 function registerWindow(win)
283 toplevelWindows.push(win);
284 win.onresize = function(ev) { updateBrowserWindowGeometry(ev.target); };
285 if (!windowGeometryTimeout)
286 windowGeometryTimeout = setInterval(function () { toplevelWindows.forEach(updateBrowserWindowGeometry); }, 2000);
289 function unregisterWindow(win)
291 var i = toplevelWindows.indexOf(win);
293 toplevelWindows.splice(i, 1);
295 if (windowGeometryTimeout && toplevelWindows.length == 0) {
296 clearInterval(windowGeometryTimeout);
297 windowGeometryTimeout = null;
301 function getTransientToplevel(surface)
303 while (surface.transientParent != 0) {
304 surface = surfaces[surface.transientParent];
311 function cmdCreateSurface(id, x, y, width, height, isTemp)
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;
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;
332 var context = canvas.getContext("2d");
333 context.globalCompositeOperation = "source-over";
334 surface.context = context;
336 surfaces[id] = surface;
339 function cmdShowSurface(id)
341 var surface = surfaces[id];
345 surface.visible = true;
347 var xOffset = surface.x;
348 var yOffset = surface.y;
350 if (useToplevelWindows) {
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;
361 doc.write("<body></body>");
364 surface.window = win;
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;
376 ensureSurfaceInDocument(surface, doc);
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";
385 function cmdHideSurface(id)
387 var surface = surfaces[id];
389 if (!surface.visible)
391 surface.visible = false;
393 surface.canvas.style["display"] = "none";
395 // Import the canvas into the main document
396 ensureSurfaceInDocument(surface, document);
398 if (surface.window) {
399 unregisterWindow(surface.window);
400 surface.window.close();
401 surface.window = null;
405 function cmdSetTransientFor(id, parentId)
407 var surface = surfaces[id];
409 if (surface.transientParent == parentId)
412 surface.transientParent = parentId;
413 if (surface.visible && surface.isTemp) {
414 alert("TODO: move temps between transient parents when visible");
418 function cmdDeleteSurface(id)
420 var canvas = surfaces[id].canvas;
422 canvas.parentNode.removeChild(canvas);
425 function cmdMoveSurface(id, x, y)
427 var surface = surfaces[id];
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.
437 //surface.window.moveTo(surface.x, surface.y);
439 var xOffset = surface.x;
440 var yOffset = surface.y;
442 var transientToplevel = getTransientToplevel(surface);
443 if (transientToplevel) {
444 xOffset = surface.x - transientToplevel.x;
445 yOffset = surface.y - transientToplevel.y;
448 surface.canvas.style["left"] = xOffset + "px";
449 surface.canvas.style["top"] = yOffset + "px";
454 function cmdResizeSurface(id, w, h)
456 var surface = surfaces[id];
461 /* Flush any outstanding draw ops before changing size */
462 flushSurface(surface);
464 resizeCanvas(surface.canvas, w, h);
466 if (surface.window) {
467 resizeBrowserWindow(surface.window, w, h);
471 function cmdFlushSurface(id)
473 flushSurface(surfaces[id]);
476 function cmdGrabPointer(id, ownerEvents)
478 doGrab(id, ownerEvents, false);
482 function cmdUngrabPointer()
489 function handleCommands(cmdObj)
491 var cmd = cmdObj.data;
494 while (i < cmd.length) {
495 var command = cmd[i++];
496 lastSerial = base64_32(cmd, i);
499 case 's': // create new surface
500 var id = base64_16(cmd, i);
502 var x = base64_16(cmd, i);
504 var y = base64_16(cmd, i);
506 var w = base64_16(cmd, i);
508 var h = base64_16(cmd, i);
510 var isTemp = cmd[i] == '1';
512 cmdCreateSurface(id, x, y, w, h, isTemp);
515 case 'S': // Show a surface
516 var id = base64_16(cmd, i);
521 case 'H': // Hide a surface
522 var id = base64_16(cmd, i);
527 case 'p': // Set transient parent
528 var id = base64_16(cmd, i);
530 var parentId = base64_16(cmd, i);
532 cmdSetTransientFor(id, parentId);
535 case 'd': // Delete surface
536 var id = base64_16(cmd, i);
538 cmdDeleteSurface(id);
541 case 'm': // Move a surface
542 var id = base64_16(cmd, i);
544 var x = base64_16(cmd, i);
546 var y = base64_16(cmd, i);
548 cmdMoveSurface(id, x, y);
551 case 'r': // Resize a surface
552 var id = base64_16(cmd, i);
554 var w = base64_16(cmd, i);
556 var h = base64_16(cmd, i);
558 cmdResizeSurface(id, w, h);
561 case 'i': // Put image data surface
562 var q = new Object();
564 q.id = base64_16(cmd, i);
566 q.x = base64_16(cmd, i);
568 q.y = base64_16(cmd, i);
570 var size = base64_32(cmd, i);
572 var url = cmd.slice(i, i + size);
576 surfaces[q.id].drawQueue.push(q);
577 if (!q.img.complete) {
579 q.img.onload = function() { handleOutstanding(); };
584 case 'b': // Copy rects
585 var q = new Object();
587 q.id = base64_16(cmd, i);
590 var nrects = base64_16(cmd, i);
594 for (var r = 0; r < nrects; r++) {
595 var rect = new Object();
596 rect.x = base64_16(cmd, i);
598 rect.y = base64_16(cmd, i);
600 rect.w = base64_16(cmd, i);
602 rect.h = base64_16(cmd, i);
607 q.dx = base64_16s(cmd, i);
609 q.dy = base64_16s(cmd, i);
611 surfaces[q.id].drawQueue.push(q);
614 case 'f': // Flush surface
615 var id = base64_16(cmd, i);
622 var id = base64_16(cmd, i);
624 var ownerEvents = cmd[i++] == '1';
626 cmdGrabPointer(id, ownerEvents);
633 alert("Unknown op " + command);
639 function handleOutstanding()
641 while (outstandingCommands.length > 0) {
642 var cmd = outstandingCommands.shift();
643 if (!handleCommands(cmd)) {
644 outstandingCommands.unshift(cmd);
650 function handleLoad(event)
653 cmdObj.data = event.target.responseText;
656 outstandingCommands.push(cmdObj);
657 if (outstandingCommands.length == 1) {
662 function getSurfaceId(ev) {
663 var surface = ev.target.surface;
664 if (surface != undefined)
669 function sendInput(cmd, args)
671 if (inputSocket != null) {
672 inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
676 function getDocumentCoordinates(element)
678 var res = new Object();
679 res.x = element.offsetLeft;
680 res.y = element.offsetTop;
682 var offsetParent = element.offsetParent;
683 while (offsetParent != null) {
684 res.x += offsetParent.offsetLeft;
685 res.y += offsetParent.offsetTop;
686 offsetParent = offsetParent.offsetParent;
691 function getPositionsFromAbsCoord(absX, absY, relativeId) {
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;
707 function getPositionsFromEvent(ev, relativeId) {
708 var res = getPositionsFromAbsCoord(ev.pageX, ev.pageY, relativeId);
716 function getEffectiveEventTarget (id) {
717 if (grab.window != null) {
718 if (!grab.ownerEvents)
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);
739 updateBrowserWindowGeometry(win);
743 function onMouseMove (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]);
751 function onMouseOver (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]);
763 function onMouseOut (ev) {
765 var id = getSurfaceId(ev);
767 id = getEffectiveEventTarget (id);
768 var pos = getPositionsFromEvent(ev, id);
771 sendInput ("l", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
773 realWindowWithMouse = 0;
777 function doGrab(id, ownerEvents, implicit) {
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]);
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;
791 grab.ownerEvents = ownerEvents;
792 grab.implicit = implicit;
795 function doUngrab() {
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]);
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]);
806 windowWithMouse = realWindowWithMouse;
811 function onMouseDown (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]);
823 function onMouseUp (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]);
832 if (grab.window != null && grab.implicit)
833 doUngrab(ev.timeStamp);
837 function onKeyDown (ev) {
839 var keyCode = ev.keyCode;
840 if (keyCode != lastKeyDown) {
841 sendInput ("k", [keyCode]);
842 lastKeyDown = keyCode;
846 function onKeyUp (ev) {
848 var keyCode = ev.keyCode;
849 sendInput ("K", [keyCode]);
853 function cancelEvent(ev)
855 ev = ev ? ev : window.event;
856 if (ev.stopPropagation)
857 ev.stopPropagation();
858 if (ev.preventDefault)
860 ev.cancelBubble = true;
862 ev.returnValue = false;
866 function onMouseWheel(ev)
869 ev = ev ? ev : window.event;
871 var id = getSurfaceId(ev);
872 var pos = getPositionsFromEvent(ev, id);
874 var offset = ev.detail ? ev.detail : ev.wheelDelta;
878 sendInput ("s", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
880 return cancelEvent(ev);
883 function setupDocument(document)
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;
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);
904 var xhr = createXHR();
906 if (typeof xhr.multipart == 'undefined') {
907 alert("Sorry, this example only works in browsers that support multipart.");
911 xhr.multipart = true;
912 xhr.open("GET", "/output", true);
913 xhr.onload = handleLoad;
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() {
924 ws.onclose = function() {
928 alert("WebSocket not supported, input will not work!");
930 setupDocument(document);
931 window.onunload = function (ev) {
932 for (var i = 0; i < toplevelWindows.length; i++)
933 toplevelWindows[i].close();