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'));
14 function getStackTrace()
17 var isCallstackPopulated = false;
21 if (e.stack) { // Firefox
22 var lines = e.stack.split("\n");
23 for (var i=0, len=lines.length; i<len; i++) {
24 if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
25 callstack.push(lines[i]);
28 // Remove call to getStackTrace()
30 isCallstackPopulated = true;
31 } else if (window.opera && e.message) { // Opera
32 var lines = e.message.split("\n");
33 for (var i=0, len=lines.length; i<len; i++) {
34 if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
36 // Append next line also since it has the file info
38 entry += " at " + lines[i+1];
41 callstack.push(entry);
44 // Remove call to getStackTrace()
46 isCallstackPopulated = true;
49 if (!isCallstackPopulated) { //IE and Safari
50 var currentFunction = arguments.callee.caller;
51 while (currentFunction) {
52 var fn = currentFunction.toString();
53 var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
54 callstack.push(fname);
55 currentFunction = currentFunction.caller;
61 function logStackTrace(len) {
62 var callstack = getStackTrace();
63 var end = callstack.length;
65 end = Math.min(len + 1, end);
66 for (var i = 1; i < end; i++)
71 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
72 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
73 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
74 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
75 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
76 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
77 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
78 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255
81 function base64_8(str, index) {
83 (base64Values[str.charCodeAt(index)]) +
84 (base64Values[str.charCodeAt(index+1)] << 6);
88 function base64_16(str, index) {
90 (base64Values[str.charCodeAt(index)]) +
91 (base64Values[str.charCodeAt(index+1)] << 6) +
92 (base64Values[str.charCodeAt(index+2)] << 12);
96 function base64_16s(str, index) {
97 var v = base64_16(str, index);
104 function base64_24(str, index) {
106 (base64Values[str.charCodeAt(index)]) +
107 (base64Values[str.charCodeAt(index+1)] << 6) +
108 (base64Values[str.charCodeAt(index+2)] << 12) +
109 (base64Values[str.charCodeAt(index+3)] << 18);
113 function base64_32(str, index) {
115 (base64Values[str.charCodeAt(index)]) +
116 (base64Values[str.charCodeAt(index+1)] << 6) +
117 (base64Values[str.charCodeAt(index+2)] << 12) +
118 (base64Values[str.charCodeAt(index+3)] << 18) +
119 (base64Values[str.charCodeAt(index+4)] << 24) +
120 (base64Values[str.charCodeAt(index+5)] << 30);
126 try { return new XMLHttpRequest(); } catch(e) {}
127 try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
128 try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
129 try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
130 try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
135 /* This resizes the window so the *inner* size is the specified size */
136 function resizeBrowserWindow(window, w, h) {
137 var innerW = window.innerWidth;
138 var innerH = window.innerHeight;
140 var outerW = window.outerWidth;
141 var outerH = window.outerHeight;
143 window.resizeTo(w + outerW - innerW,
144 h + outerH - innerH);
147 function resizeCanvas(canvas, w, h)
149 /* Canvas resize clears the data, so we need to save it first */
150 var tmpCanvas = canvas.ownerDocument.createElement("canvas");
151 tmpCanvas.width = canvas.width;
152 tmpCanvas.height = canvas.height;
153 var tmpContext = tmpCanvas.getContext("2d");
154 tmpContext.globalCompositeOperation = "copy";
155 tmpContext.drawImage(canvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
160 var context = canvas.getContext("2d");
162 context.globalCompositeOperation = "copy";
163 context.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
166 var useToplevelWindows = false;
167 var toplevelWindows = [];
168 var grab = new Object();
170 grab.ownerEvents = false;
171 grab.implicit = false;
172 var localGrab = null;
173 var keyDownList = [];
178 var lastTimeStamp = 0;
179 var realWindowWithMouse = 0;
180 var windowWithMouse = 0;
182 var stackingOrder = [];
183 var outstandingCommands = new Array();
184 var inputSocket = null;
186 var GDK_CROSSING_NORMAL = 0;
187 var GDK_CROSSING_GRAB = 1;
188 var GDK_CROSSING_UNGRAB = 2;
191 var GDK_SHIFT_MASK = 1 << 0;
192 var GDK_LOCK_MASK = 1 << 1;
193 var GDK_CONTROL_MASK = 1 << 2;
194 var GDK_MOD1_MASK = 1 << 3;
195 var GDK_MOD2_MASK = 1 << 4;
196 var GDK_MOD3_MASK = 1 << 5;
197 var GDK_MOD4_MASK = 1 << 6;
198 var GDK_MOD5_MASK = 1 << 7;
199 var GDK_BUTTON1_MASK = 1 << 8;
200 var GDK_BUTTON2_MASK = 1 << 9;
201 var GDK_BUTTON3_MASK = 1 << 10;
202 var GDK_BUTTON4_MASK = 1 << 11;
203 var GDK_BUTTON5_MASK = 1 << 12;
204 var GDK_SUPER_MASK = 1 << 26;
205 var GDK_HYPER_MASK = 1 << 27;
206 var GDK_META_MASK = 1 << 28;
207 var GDK_RELEASE_MASK = 1 << 30;
209 function getButtonMask (button) {
211 return GDK_BUTTON1_MASK;
213 return GDK_BUTTON2_MASK;
215 return GDK_BUTTON3_MASK;
217 return GDK_BUTTON4_MASK;
219 return GDK_BUTTON5_MASK;
223 function flushSurface(surface)
225 var commands = surface.drawQueue;
227 var context = surface.canvas.getContext("2d");
228 context.globalCompositeOperation = "source-over";
230 for (i = 0; i < commands.length; i++) {
231 var cmd = commands[i];
233 case 'i': // put image data surface
234 context.globalCompositeOperation = "source-over";
235 context.drawImage(cmd.img, cmd.x, cmd.y);
238 case 'b': // copy rects
246 for (var j = 0; j < cmd.rects.length; j++) {
247 var rect = cmd.rects[j];
248 context.rect(rect.x, rect.y, rect.w, rect.h);
252 maxx = rect.x + rect.w;
253 maxy = rect.y + rect.h;
259 if (rect.x + rect.w > maxx)
260 maxx = rect.x + rect.w;
261 if (rect.y + rect.h > maxy)
262 maxy = rect.y + rect.h;
266 context.globalCompositeOperation = "copy";
267 context.drawImage(context.canvas,
268 minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,
269 minx, miny, maxx - minx, maxy - miny);
274 alert("Unknown drawing op " + cmd.op);
279 function ensureSurfaceInDocument(surface, doc)
281 if (surface.document != doc) {
282 var oldCanvas = surface.canvas;
283 var canvas = doc.importNode(oldCanvas, false);
284 doc.body.appendChild(canvas);
285 canvas.surface = surface;
286 oldCanvas.parentNode.removeChild(oldCanvas);
288 surface.canvas = canvas;
289 if (surface.toplevelElement == oldCanvas)
290 surface.toplevelElement = canvas;
291 surface.document = doc;
295 function sendConfigureNotify(surface)
297 sendInput("w", [surface.id, surface.x, surface.y, surface.width, surface.height]);
300 var windowGeometryTimeout = null;
302 function updateBrowserWindowGeometry(win, alwaysSendConfigure) {
306 var surface = win.surface;
308 var innerW = win.innerWidth;
309 var innerH = win.innerHeight;
314 if (win.mozInnerScreenX != undefined) {
315 x = win.mozInnerScreenX;
316 y = win.mozInnerScreenY;
317 } else if (win.screenTop != undefined) {
321 alert("No implementation to get window position");
324 if (alwaysSendConfigure || x != surface.x || y != surface.y ||
325 innerW != surface.width || innerH != surface.height) {
326 var oldX = surface.x;
327 var oldY = surface.y;
330 if (surface.width != innerW || surface.height != innerH)
331 resizeCanvas(surface.canvas, innerW, innerH);
332 surface.width = innerW;
333 surface.height = innerH;
334 sendConfigureNotify(surface);
335 for (id in surfaces) {
336 var childSurface = surfaces[id];
337 var transientToplevel = getTransientToplevel(childSurface);
338 if (transientToplevel != null && transientToplevel == surface) {
339 childSurface.x += surface.x - oldX;
340 childSurface.y += surface.y - oldY;
341 sendConfigureNotify(childSurface);
347 function browserWindowClosed(win) {
348 var surface = win.surface;
350 sendInput ("W", [surface.id]);
351 for (id in surfaces) {
352 var childSurface = surfaces[id];
353 var transientToplevel = getTransientToplevel(childSurface);
354 if (transientToplevel != null && transientToplevel == surface) {
355 sendInput ("W", [childSurface.id]);
360 function registerWindow(win)
362 toplevelWindows.push(win);
363 win.onresize = function(ev) { updateBrowserWindowGeometry(ev.target, false); };
364 if (!windowGeometryTimeout)
365 windowGeometryTimeout = setInterval(function () {
366 for (var i = 0; i < toplevelWindows.length; i++)
367 updateBrowserWindowGeometry(toplevelWindows[i], false);
369 win.onunload = function(ev) { browserWindowClosed(ev.target.defaultView); };
372 function unregisterWindow(win)
374 var i = toplevelWindows.indexOf(win);
376 toplevelWindows.splice(i, 1);
378 if (windowGeometryTimeout && toplevelWindows.length == 0) {
379 clearInterval(windowGeometryTimeout);
380 windowGeometryTimeout = null;
384 function getTransientToplevel(surface)
386 while (surface && surface.transientParent != 0) {
387 surface = surfaces[surface.transientParent];
388 if (surface && surface.window)
394 function getStyle(el, styleProp)
396 if (el.currentStyle) {
397 return el.currentStyle[styleProp];
398 } else if (window.getComputedStyle) {
399 var win = el.ownerDocument.defaultView;
400 return win.getComputedStyle(el, null).getPropertyValue(styleProp);
405 function parseOffset(value)
407 var px = value.indexOf("px");
409 return parseInt(value.slice(0,px));
413 function getFrameOffset(surface) {
416 var el = surface.canvas;
417 while (el != null && el != surface.frame) {
421 /* For some reason the border is not includes in the offsets.. */
422 x += parseOffset(getStyle(el, "border-left-width"));
423 y += parseOffset(getStyle(el, "border-top-width"));
425 el = el.offsetParent;
428 /* Also include frame border as per above */
429 x += parseOffset(getStyle(el, "border-left-width"));
430 y += parseOffset(getStyle(el, "border-top-width"));
435 var positionIndex = 0;
436 function cmdCreateSurface(id, x, y, width, height, isTemp)
438 var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };
439 surface.positioned = isTemp;
440 surface.drawQueue = [];
441 surface.transientParent = 0;
442 surface.visible = false;
443 surface.window = null;
444 surface.document = document;
445 surface.frame = null;
447 var canvas = document.createElement("canvas");
448 canvas.width = width;
449 canvas.height = height;
450 canvas.surface = surface;
451 surface.canvas = canvas;
454 if (useToplevelWindows || isTemp) {
455 toplevelElement = canvas;
456 document.body.appendChild(canvas);
458 var frame = document.createElement("div");
459 frame.frameFor = surface;
460 frame.className = "frame-window";
461 surface.frame = frame;
463 var button = document.createElement("center");
464 var X = document.createTextNode("X");
465 button.appendChild(X);
466 button.className = "frame-close";
467 frame.appendChild(button);
469 var contents = document.createElement("div");
470 contents.className = "frame-contents";
471 frame.appendChild(contents);
473 canvas.style["display"] = "block";
474 contents.appendChild(canvas);
476 toplevelElement = frame;
477 document.body.appendChild(frame);
479 surface.x = 100 + positionIndex * 10;
480 surface.y = 100 + positionIndex * 10;
481 positionIndex = (positionIndex + 1) % 20;
484 surface.toplevelElement = toplevelElement;
485 toplevelElement.style["position"] = "absolute";
486 /* This positioning isn't strictly right for apps in another topwindow,
487 * but that will be fixed up when showing. */
488 toplevelElement.style["left"] = surface.x + "px";
489 toplevelElement.style["top"] = surface.y + "px";
490 toplevelElement.style["display"] = "inline";
492 /* We hide the frame with visibility rather than display none
493 * so getFrameOffset still works with hidden windows. */
494 toplevelElement.style["visibility"] = "hidden";
496 surfaces[id] = surface;
497 stackingOrder.push(surface);
499 sendConfigureNotify(surface);
502 function cmdShowSurface(id)
504 var surface = surfaces[id];
508 surface.visible = true;
510 var xOffset = surface.x;
511 var yOffset = surface.y;
513 if (useToplevelWindows) {
515 if (!surface.isTemp) {
517 'width='+surface.width+',height='+surface.height+
518 ',location=no,menubar=no,scrollbars=no,toolbar=no';
519 if (surface.positioned)
521 ',left='+surface.x+',top='+surface.y+',screenX='+surface.x+',screenY='+surface.y;
522 var win = window.open('','_blank', options);
523 win.surface = surface;
527 doc.write("<body></body>");
530 surface.window = win;
534 var transientToplevel = getTransientToplevel(surface);
535 if (transientToplevel) {
536 doc = transientToplevel.window.document;
537 xOffset = surface.x - transientToplevel.x;
538 yOffset = surface.y - transientToplevel.y;
542 ensureSurfaceInDocument(surface, doc);
545 var offset = getFrameOffset(surface);
551 surface.toplevelElement.style["left"] = xOffset + "px";
552 surface.toplevelElement.style["top"] = yOffset + "px";
553 surface.toplevelElement.style["visibility"] = "visible";
558 updateBrowserWindowGeometry(surface.window, false);
561 function cmdHideSurface(id)
563 var surface = surfaces[id];
565 if (!surface.visible)
567 surface.visible = false;
569 var element = surface.toplevelElement;
571 element.style["visibility"] = "hidden";
573 // Import the canvas into the main document
574 ensureSurfaceInDocument(surface, document);
576 if (surface.window) {
577 unregisterWindow(surface.window);
578 surface.window.close();
579 surface.window = null;
583 function cmdSetTransientFor(id, parentId)
585 var surface = surfaces[id];
587 if (surface.transientParent == parentId)
590 surface.transientParent = parentId;
591 if (parentId != 0 && surfaces[parentId]) {
592 moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1);
595 if (surface.visible) {
600 function restackWindows() {
601 for (var i = 0; i < stackingOrder.length; i++) {
602 var surface = stackingOrder[i];
603 surface.toplevelElement.style.zIndex = i;
607 function moveToHelper(surface, position) {
608 var i = stackingOrder.indexOf(surface);
609 stackingOrder.splice(i, 1);
610 if (position != undefined)
611 stackingOrder.splice(position, 0, surface);
613 stackingOrder.push(surface);
615 for (var cid in surfaces) {
616 var child = surfaces[cid];
617 if (child.transientParent == surface.id)
618 moveToHelper(child, stackingOrder.indexOf(surface) + 1);
622 function moveToTop(surface) {
623 moveToHelper(surface);
628 function cmdDeleteSurface(id)
630 var surface = surfaces[id];
631 var i = stackingOrder.indexOf(surface);
633 stackingOrder.splice(i, 1);
634 var canvas = surface.canvas;
635 canvas.parentNode.removeChild(canvas);
636 var frame = surface.frame;
638 frame.parentNode.removeChild(frame);
642 function cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h)
644 var surface = surfaces[id];
646 surface.positioned = true;
655 /* Flush any outstanding draw ops before (possibly) changing size */
656 flushSurface(surface);
659 resizeCanvas(surface.canvas, w, h);
661 if (surface.visible) {
662 if (surface.window) {
663 /* TODO: This moves the outer frame position, we really want the inner position.
664 * However this isn't *strictly* invalid, as any WM could have done whatever it
665 * wanted with the positioning of the window.
668 surface.window.moveTo(surface.x, surface.y);
670 resizeBrowserWindow(surface.window, w, h);
673 var xOffset = surface.x;
674 var yOffset = surface.y;
676 var transientToplevel = getTransientToplevel(surface);
677 if (transientToplevel) {
678 xOffset = surface.x - transientToplevel.x;
679 yOffset = surface.y - transientToplevel.y;
682 var element = surface.canvas;
684 element = surface.frame;
685 var offset = getFrameOffset(surface);
690 element.style["left"] = xOffset + "px";
691 element.style["top"] = yOffset + "px";
696 if (surface.window) {
697 updateBrowserWindowGeometry(surface.window, true);
699 sendConfigureNotify(surface);
703 function cmdFlushSurface(id)
705 flushSurface(surfaces[id]);
708 function cmdGrabPointer(id, ownerEvents)
710 doGrab(id, ownerEvents, false);
714 function cmdUngrabPointer()
721 function handleCommands(cmdObj)
723 var cmd = cmdObj.data;
726 while (i < cmd.length) {
727 var id, x, y, w, h, q;
728 var command = cmd[i++];
729 lastSerial = base64_32(cmd, i);
732 case 's': // create new surface
733 id = base64_16(cmd, i);
735 x = base64_16s(cmd, i);
737 y = base64_16s(cmd, i);
739 w = base64_16(cmd, i);
741 h = base64_16(cmd, i);
743 var isTemp = cmd[i] == '1';
745 cmdCreateSurface(id, x, y, w, h, isTemp);
748 case 'S': // Show a surface
749 id = base64_16(cmd, i);
754 case 'H': // Hide a surface
755 id = base64_16(cmd, i);
760 case 'p': // Set transient parent
761 id = base64_16(cmd, i);
763 var parentId = base64_16(cmd, i);
765 cmdSetTransientFor(id, parentId);
768 case 'd': // Delete surface
769 id = base64_16(cmd, i);
771 cmdDeleteSurface(id);
774 case 'm': // Move a surface
775 id = base64_16(cmd, i);
777 var ops = cmd.charCodeAt(i++) - 48;
778 var has_pos = ops & 1;
780 x = base64_16s(cmd, i);
782 y = base64_16s(cmd, i);
785 var has_size = ops & 2;
787 w = base64_16(cmd, i);
789 h = base64_16(cmd, i);
792 cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h);
795 case 'i': // Put image data surface
798 q.id = base64_16(cmd, i);
800 q.x = base64_16(cmd, i);
802 q.y = base64_16(cmd, i);
804 var size = base64_32(cmd, i);
806 var url = cmd.slice(i, i + size);
810 surfaces[q.id].drawQueue.push(q);
811 if (!q.img.complete) {
813 q.img.onload = function() { handleOutstanding(); };
818 case 'b': // Copy rects
821 q.id = base64_16(cmd, i);
824 var nrects = base64_16(cmd, i);
828 for (var r = 0; r < nrects; r++) {
829 var rect = new Object();
830 rect.x = base64_16(cmd, i);
832 rect.y = base64_16(cmd, i);
834 rect.w = base64_16(cmd, i);
836 rect.h = base64_16(cmd, i);
841 q.dx = base64_16s(cmd, i);
843 q.dy = base64_16s(cmd, i);
845 surfaces[q.id].drawQueue.push(q);
848 case 'f': // Flush surface
849 id = base64_16(cmd, i);
856 id = base64_16(cmd, i);
858 var ownerEvents = cmd[i++] == '1';
860 cmdGrabPointer(id, ownerEvents);
867 alert("Unknown op " + command);
873 function handleOutstanding()
875 while (outstandingCommands.length > 0) {
876 var cmd = outstandingCommands.shift();
877 if (!handleCommands(cmd)) {
878 outstandingCommands.unshift(cmd);
884 function handleLoad(event)
887 cmdObj.data = event.target.responseText;
890 outstandingCommands.push(cmdObj);
891 if (outstandingCommands.length == 1) {
896 function getSurfaceId(ev) {
897 var surface = ev.target.surface;
898 if (surface != undefined)
903 function sendInput(cmd, args)
905 if (inputSocket != null) {
906 inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
910 function getPositionsFromAbsCoord(absX, absY, relativeId) {
917 if (relativeId != 0) {
918 var surface = surfaces[relativeId];
919 res.winX = res.winX - surface.x;
920 res.winY = res.winY - surface.y;
926 function getPositionsFromEvent(ev, relativeId) {
928 if (useToplevelWindows) {
935 var res = getPositionsFromAbsCoord(absX, absY, relativeId);
943 function getEffectiveEventTarget (id) {
944 if (grab.window != null) {
945 if (!grab.ownerEvents)
953 function updateForEvent(ev) {
954 lastTimeStamp = ev.timeStamp;
955 if (ev.target.surface && ev.target.surface.window) {
956 var win = ev.target.surface.window;
957 updateBrowserWindowGeometry(win, false);
961 function onMouseMove (ev) {
964 var dx = ev.pageX - localGrab.lastX;
965 var dy = ev.pageY - localGrab.lastY;
966 var surface = localGrab.frame.frameFor;
969 var offset = getFrameOffset(surface);
970 localGrab.frame.style["left"] = (surface.x - offset.x) + "px";
971 localGrab.frame.style["top"] = (surface.y - offset.y) + "px";
972 sendConfigureNotify(surface);
973 localGrab.lastX = ev.pageX;
974 localGrab.lastY = ev.pageY;
977 var id = getSurfaceId(ev);
978 id = getEffectiveEventTarget (id);
979 var pos = getPositionsFromEvent(ev, id);
980 sendInput ("m", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
983 function onMouseOver (ev) {
987 var id = getSurfaceId(ev);
988 realWindowWithMouse = id;
989 id = getEffectiveEventTarget (id);
990 var pos = getPositionsFromEvent(ev, id);
991 windowWithMouse = id;
992 if (windowWithMouse != 0) {
993 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
997 function onMouseOut (ev) {
1001 var id = getSurfaceId(ev);
1003 id = getEffectiveEventTarget (id);
1004 var pos = getPositionsFromEvent(ev, id);
1007 sendInput ("l", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
1009 realWindowWithMouse = 0;
1010 windowWithMouse = 0;
1013 function doGrab(id, ownerEvents, implicit) {
1016 if (windowWithMouse != id) {
1017 if (windowWithMouse != 0) {
1018 pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
1019 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
1021 pos = getPositionsFromAbsCoord(lastX, lastY, id);
1022 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
1023 windowWithMouse = id;
1027 grab.ownerEvents = ownerEvents;
1028 grab.implicit = implicit;
1031 function doUngrab() {
1033 if (realWindowWithMouse != windowWithMouse) {
1034 if (windowWithMouse != 0) {
1035 pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
1036 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
1038 if (realWindowWithMouse != 0) {
1039 pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);
1040 sendInput ("e", [realWindowWithMouse, realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
1042 windowWithMouse = realWindowWithMouse;
1047 function onMouseDown (ev) {
1049 var button = ev.button + 1;
1050 lastState = lastState | getButtonMask (button);
1051 var id = getSurfaceId(ev);
1052 id = getEffectiveEventTarget (id);
1054 if (id == 0 && ev.target.frameFor) { /* mouse click on frame */
1055 localGrab = new Object();
1056 localGrab.frame = ev.target;
1057 localGrab.lastX = ev.pageX;
1058 localGrab.lastY = ev.pageY;
1059 moveToTop(localGrab.frame.frameFor);
1063 var pos = getPositionsFromEvent(ev, id);
1064 if (grab.window == null)
1065 doGrab (id, false, true);
1066 sendInput ("b", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
1069 function onMouseUp (ev) {
1071 var button = ev.button + 1;
1072 lastState = lastState & ~getButtonMask (button);
1073 var evId = getSurfaceId(ev);
1074 id = getEffectiveEventTarget (evId);
1075 var pos = getPositionsFromEvent(ev, id);
1079 realWindowWithMouse = evId;
1080 if (windowWithMouse != id) {
1081 if (windowWithMouse != 0) {
1082 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
1084 windowWithMouse = id;
1085 if (windowWithMouse != 0) {
1086 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
1092 sendInput ("B", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
1094 if (grab.window != null && grab.implicit)
1095 doUngrab(ev.timeStamp);
1098 /* Some of the keyboard handling code is from noVNC and
1099 * (c) Joel Martin (github@martintribe.org), used with permission
1101 * https://github.com/kanaka/noVNC/blob/master/include/input.js
1104 var unicodeTable = {
2365 var specialKeyTable = {
2366 8: 0xFF08, // BACKSPACE
2367 13: 0xFF0D, // ENTER
2369 27: 0xFF1B, // ESCAPE
2370 46: 0xFFFF, // DELETE
2373 33: 0xFF55, // PAGE_UP
2374 34: 0xFF56, // PAGE_DOWN
2375 45: 0xFF63, // INSERT
2378 39: 0xFF53, // RIGHT
2380 16: 0xFFE1, // SHIFT
2381 17: 0xFFE3, // CONTROL
2382 18: 0xFFE9, // Left ALT (Mac Command)
2397 function getEventKeySym(ev) {
2398 if (typeof ev.which !== "undefined" && ev.which > 0)
2403 // This is based on the approach from noVNC. We handle
2404 // everything in keydown that we have all info for, and that
2405 // are not safe to pass on to the browser (as it may do something
2406 // with the key. The rest we pass on to keypress so we can get the
2407 // translated keysym.
2408 function getKeysymSpecial(ev) {
2409 // These are simple and risky to pass on to browser, handle directly
2410 if ((ev.keyCode in specialKeyTable))
2411 return specialKeyTable[ev.keyCode];
2413 // If we don't hold alt or ctrl, then we should be safe to pass
2414 // on to keypressed and look at the translated data
2415 if (!ev.ctrlKey && !ev.altKey)
2418 var keysym = getEventKeySym(ev);
2422 case 186 : keysym = 59; break; // ; (IE)
2423 case 187 : keysym = 61; break; // = (IE)
2424 case 188 : keysym = 44; break; // , (Mozilla, IE)
2425 case 109 : // - (Mozilla, Opera)
2426 if (true /* TODO: check if browser is firefox or opera */)
2429 case 189 : keysym = 45; break; // - (IE)
2430 case 190 : keysym = 46; break; // . (Mozilla, IE)
2431 case 191 : keysym = 47; break; // / (Mozilla, IE)
2432 case 192 : keysym = 96; break; // ` (Mozilla, IE)
2433 case 219 : keysym = 91; break; // [ (Mozilla, IE)
2434 case 220 : keysym = 92; break; // \ (Mozilla, IE)
2435 case 221 : keysym = 93; break; // ] (Mozilla, IE)
2436 case 222 : keysym = 39; break; // ' (Mozilla, IE)
2439 /* Remap shifted and unshifted keys */
2440 if (!!ev.shiftKey) {
2442 case 48 : keysym = 41 ; break; // ) (shifted 0)
2443 case 49 : keysym = 33 ; break; // ! (shifted 1)
2444 case 50 : keysym = 64 ; break; // @ (shifted 2)
2445 case 51 : keysym = 35 ; break; // # (shifted 3)
2446 case 52 : keysym = 36 ; break; // $ (shifted 4)
2447 case 53 : keysym = 37 ; break; // % (shifted 5)
2448 case 54 : keysym = 94 ; break; // ^ (shifted 6)
2449 case 55 : keysym = 38 ; break; // & (shifted 7)
2450 case 56 : keysym = 42 ; break; // * (shifted 8)
2451 case 57 : keysym = 40 ; break; // ( (shifted 9)
2452 case 59 : keysym = 58 ; break; // : (shifted `)
2453 case 61 : keysym = 43 ; break; // + (shifted ;)
2454 case 44 : keysym = 60 ; break; // < (shifted ,)
2455 case 45 : keysym = 95 ; break; // _ (shifted -)
2456 case 46 : keysym = 62 ; break; // > (shifted .)
2457 case 47 : keysym = 63 ; break; // ? (shifted /)
2458 case 96 : keysym = 126; break; // ~ (shifted `)
2459 case 91 : keysym = 123; break; // { (shifted [)
2460 case 92 : keysym = 124; break; // | (shifted \)
2461 case 93 : keysym = 125; break; // } (shifted ])
2462 case 39 : keysym = 34 ; break; // " (shifted ')
2464 } else if ((keysym >= 65) && (keysym <=90)) {
2465 /* Remap unshifted A-Z */
2467 } else if (ev.keyLocation === 3) {
2470 case 96 : keysym = 48; break; // 0
2471 case 97 : keysym = 49; break; // 1
2472 case 98 : keysym = 50; break; // 2
2473 case 99 : keysym = 51; break; // 3
2474 case 100: keysym = 52; break; // 4
2475 case 101: keysym = 53; break; // 5
2476 case 102: keysym = 54; break; // 6
2477 case 103: keysym = 55; break; // 7
2478 case 104: keysym = 56; break; // 8
2479 case 105: keysym = 57; break; // 9
2480 case 109: keysym = 45; break; // -
2481 case 110: keysym = 46; break; // .
2482 case 111: keysym = 47; break; // /
2489 /* Translate DOM keyPress event to keysym value */
2490 function getKeysym(ev) {
2493 keysym = getEventKeySym(ev);
2495 if ((keysym > 255) && (keysym < 0xFF00)) {
2496 // Map Unicode outside Latin 1 to gdk keysyms
2497 keysym = unicodeTable[keysym];
2498 if (typeof keysym === 'undefined')
2505 function copyKeyEvent(ev) {
2506 var members = ['type', 'keyCode', 'charCode', 'which',
2507 'altKey', 'ctrlKey', 'shiftKey',
2508 'keyLocation', 'keyIdentifier'], i, obj = {};
2509 for (i = 0; i < members.length; i++) {
2510 if (typeof ev[members[i]] !== "undefined")
2511 obj[members[i]] = ev[members[i]];
2516 function pushKeyEvent(fev) {
2517 keyDownList.push(fev);
2520 function getKeyEvent(keyCode, pop) {
2522 for (i = keyDownList.length-1; i >= 0; i--) {
2523 if (keyDownList[i].keyCode === keyCode) {
2524 if ((typeof pop !== "undefined") && pop)
2525 fev = keyDownList.splice(i, 1)[0];
2527 fev = keyDownList[i];
2534 function ignoreKeyEvent(ev) {
2535 // Blarg. Some keys have a different keyCode on keyDown vs keyUp
2536 if (ev.keyCode === 229) {
2537 // French AZERTY keyboard dead key.
2538 // Lame thing is that the respective keyUp is 219 so we can't
2539 // properly ignore the keyUp event
2545 function handleKeyDown(e) {
2546 var fev = null, ev = (e ? e : window.event), keysym = null, suppress = false;
2548 fev = copyKeyEvent(ev);
2550 keysym = getKeysymSpecial(ev);
2551 // Save keysym decoding for use in keyUp
2552 fev.keysym = keysym;
2554 // If it is a key or key combination that might trigger
2555 // browser behaviors or it has no corresponding keyPress
2556 // event, then send it immediately
2557 if (!ignoreKeyEvent(ev))
2558 sendInput("k", [keysym]);
2562 if (! ignoreKeyEvent(ev)) {
2563 // Add it to the list of depressed keys
2568 // Suppress bubbling/default actions
2569 return cancelEvent(ev);
2572 // Allow the event to bubble and become a keyPress event which
2573 // will have the character code translated
2577 function handleKeyPress(e) {
2578 var ev = (e ? e : window.event), kdlen = keyDownList.length, keysym = null;
2580 if (((ev.which !== "undefined") && (ev.which === 0)) ||
2581 getKeysymSpecial(ev)) {
2582 // Firefox and Opera generate a keyPress event even if keyDown
2583 // is suppressed. But the keys we want to suppress will have
2585 // - the which attribute set to 0
2586 // - getKeysymSpecial() will identify it
2587 return cancelEvent(ev);
2590 keysym = getKeysym(ev);
2592 // Modify the the which attribute in the depressed keys list so
2593 // that the keyUp event will be able to have the character code
2594 // translation available.
2596 keyDownList[kdlen-1].keysym = keysym;
2598 log("keyDownList empty when keyPress triggered");
2601 // Send the translated keysym
2603 sendInput ("k", [keysym]);
2605 // Stop keypress events just in case
2606 return cancelEvent(ev);
2609 function handleKeyUp(e) {
2610 var fev = null, ev = (e ? e : window.event), i, keysym;
2612 fev = getKeyEvent(ev.keyCode, true);
2615 keysym = fev.keysym;
2617 log("Key event (keyCode = " + ev.keyCode + ") not found on keyDownList");
2622 sendInput ("K", [keysym]);
2623 return cancelEvent(ev);
2626 function onKeyDown (ev) {
2629 return cancelEvent(ev);
2630 return handleKeyDown(ev);
2633 function onKeyPress(ev) {
2636 return cancelEvent(ev);
2637 return handleKeyPress(ev);
2640 function onKeyUp (ev) {
2643 return cancelEvent(ev);
2644 return handleKeyUp(ev);
2647 function cancelEvent(ev)
2649 ev = ev ? ev : window.event;
2650 if (ev.stopPropagation)
2651 ev.stopPropagation();
2652 if (ev.preventDefault)
2653 ev.preventDefault();
2654 ev.cancelBubble = true;
2656 ev.returnValue = false;
2660 function onMouseWheel(ev)
2665 ev = ev ? ev : window.event;
2667 var id = getSurfaceId(ev);
2668 var pos = getPositionsFromEvent(ev, id);
2670 var offset = ev.detail ? ev.detail : ev.wheelDelta;
2674 sendInput ("s", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
2676 return cancelEvent(ev);
2679 function setupDocument(document)
2681 document.oncontextmenu = function () { return false; };
2682 document.onmousemove = onMouseMove;
2683 document.onmouseover = onMouseOver;
2684 document.onmouseout = onMouseOut;
2685 document.onmousedown = onMouseDown;
2686 document.onmouseup = onMouseUp;
2687 document.onkeydown = onKeyDown;
2688 document.onkeypress = onKeyPress;
2689 document.onkeyup = onKeyUp;
2691 if (document.addEventListener) {
2692 document.addEventListener('DOMMouseScroll', onMouseWheel, false);
2693 document.addEventListener('mousewheel', onMouseWheel, false);
2694 } else if (document.attachEvent) {
2695 element.attachEvent("onmousewheel", onMouseWheel);
2701 var url = window.location.toString();
2702 var query_string = url.split("?");
2703 if (query_string.length > 1) {
2704 var params = query_string[1].split("&");
2705 if (params[0].indexOf("toplevel") != -1)
2706 useToplevelWindows = true;
2708 var xhr = createXHR();
2710 if (typeof xhr.multipart == 'undefined') {
2711 alert("Sorry, this example only works in browsers that support multipart.");
2715 xhr.multipart = true;
2716 xhr.open("GET", "/output", true);
2717 xhr.onload = handleLoad;
2721 if ("WebSocket" in window) {
2722 var loc = window.location.toString().replace("http:", "ws:");
2723 loc = loc.substr(0, loc.lastIndexOf('/')) + "/input";
2724 var ws = new WebSocket(loc, "broadway");
2725 ws.onopen = function() {
2728 if (useToplevelWindows) {
2729 w = window.screen.width;
2730 h = window.screen.height;
2732 w = window.innerWidth;
2733 h = window.innerHeight;
2734 window.onresize = function(ev) {
2736 w = window.innerWidth;
2737 h = window.innerHeight;
2738 sendInput ("d", [w, h]);
2741 sendInput ("d", [w, h]);
2743 ws.onclose = function() {
2747 alert("WebSocket not supported, input will not work!");
2749 setupDocument(document);
2750 window.onunload = function (ev) {
2751 for (var i = 0; i < toplevelWindows.length; i++)
2752 toplevelWindows[i].close();