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 function resizeCanvas(canvas, w, h)
137 /* Canvas resize clears the data, so we need to save it first */
138 var tmpCanvas = canvas.ownerDocument.createElement("canvas");
139 tmpCanvas.width = canvas.width;
140 tmpCanvas.height = canvas.height;
141 var tmpContext = tmpCanvas.getContext("2d");
142 tmpContext.globalCompositeOperation = "copy";
143 tmpContext.drawImage(canvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
148 var context = canvas.getContext("2d");
150 context.globalCompositeOperation = "copy";
151 context.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
154 var grab = new Object();
156 grab.ownerEvents = false;
157 grab.implicit = false;
158 var localGrab = null;
159 var keyDownList = [];
164 var lastTimeStamp = 0;
165 var realWindowWithMouse = 0;
166 var windowWithMouse = 0;
168 var stackingOrder = [];
169 var outstandingCommands = new Array();
170 var inputSocket = null;
172 var GDK_CROSSING_NORMAL = 0;
173 var GDK_CROSSING_GRAB = 1;
174 var GDK_CROSSING_UNGRAB = 2;
177 var GDK_SHIFT_MASK = 1 << 0;
178 var GDK_LOCK_MASK = 1 << 1;
179 var GDK_CONTROL_MASK = 1 << 2;
180 var GDK_MOD1_MASK = 1 << 3;
181 var GDK_MOD2_MASK = 1 << 4;
182 var GDK_MOD3_MASK = 1 << 5;
183 var GDK_MOD4_MASK = 1 << 6;
184 var GDK_MOD5_MASK = 1 << 7;
185 var GDK_BUTTON1_MASK = 1 << 8;
186 var GDK_BUTTON2_MASK = 1 << 9;
187 var GDK_BUTTON3_MASK = 1 << 10;
188 var GDK_BUTTON4_MASK = 1 << 11;
189 var GDK_BUTTON5_MASK = 1 << 12;
190 var GDK_SUPER_MASK = 1 << 26;
191 var GDK_HYPER_MASK = 1 << 27;
192 var GDK_META_MASK = 1 << 28;
193 var GDK_RELEASE_MASK = 1 << 30;
195 function getButtonMask (button) {
197 return GDK_BUTTON1_MASK;
199 return GDK_BUTTON2_MASK;
201 return GDK_BUTTON3_MASK;
203 return GDK_BUTTON4_MASK;
205 return GDK_BUTTON5_MASK;
209 function flushSurface(surface)
211 var commands = surface.drawQueue;
213 var context = surface.canvas.getContext("2d");
214 context.globalCompositeOperation = "source-over";
216 for (i = 0; i < commands.length; i++) {
217 var cmd = commands[i];
219 case 'i': // put image data surface
220 context.globalCompositeOperation = "source-over";
221 context.drawImage(cmd.img, cmd.x, cmd.y);
224 case 'b': // copy rects
232 for (var j = 0; j < cmd.rects.length; j++) {
233 var rect = cmd.rects[j];
234 context.rect(rect.x, rect.y, rect.w, rect.h);
238 maxx = rect.x + rect.w;
239 maxy = rect.y + rect.h;
245 if (rect.x + rect.w > maxx)
246 maxx = rect.x + rect.w;
247 if (rect.y + rect.h > maxy)
248 maxy = rect.y + rect.h;
252 context.globalCompositeOperation = "copy";
253 context.drawImage(context.canvas,
254 minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,
255 minx, miny, maxx - minx, maxy - miny);
260 alert("Unknown drawing op " + cmd.op);
265 function sendConfigureNotify(surface)
267 sendInput("w", [surface.id, surface.x, surface.y, surface.width, surface.height]);
270 function getStyle(el, styleProp)
272 if (el.currentStyle) {
273 return el.currentStyle[styleProp];
274 } else if (window.getComputedStyle) {
275 var win = el.ownerDocument.defaultView;
276 return win.getComputedStyle(el, null).getPropertyValue(styleProp);
281 function parseOffset(value)
283 var px = value.indexOf("px");
285 return parseInt(value.slice(0,px));
289 function getFrameOffset(surface) {
292 var el = surface.canvas;
293 while (el != null && el != surface.frame) {
297 /* For some reason the border is not includes in the offsets.. */
298 x += parseOffset(getStyle(el, "border-left-width"));
299 y += parseOffset(getStyle(el, "border-top-width"));
301 el = el.offsetParent;
304 /* Also include frame border as per above */
305 x += parseOffset(getStyle(el, "border-left-width"));
306 y += parseOffset(getStyle(el, "border-top-width"));
311 var positionIndex = 0;
312 function cmdCreateSurface(id, x, y, width, height, isTemp)
314 var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };
315 surface.positioned = isTemp;
316 surface.drawQueue = [];
317 surface.transientParent = 0;
318 surface.visible = false;
319 surface.frame = null;
321 var canvas = document.createElement("canvas");
322 canvas.width = width;
323 canvas.height = height;
324 canvas.surface = surface;
325 surface.canvas = canvas;
329 toplevelElement = canvas;
330 document.body.appendChild(canvas);
332 var frame = document.createElement("div");
333 frame.frameFor = surface;
334 frame.className = "frame-window";
335 surface.frame = frame;
337 var button = document.createElement("center");
338 button.closeFor = surface;
339 var X = document.createTextNode("\u00d7");
340 button.appendChild(X);
341 button.className = "frame-close";
342 frame.appendChild(button);
344 var contents = document.createElement("div");
345 contents.className = "frame-contents";
346 frame.appendChild(contents);
348 canvas.style["display"] = "block";
349 contents.appendChild(canvas);
351 toplevelElement = frame;
352 document.body.appendChild(frame);
354 surface.x = 100 + positionIndex * 10;
355 surface.y = 100 + positionIndex * 10;
356 positionIndex = (positionIndex + 1) % 20;
359 surface.toplevelElement = toplevelElement;
360 toplevelElement.style["position"] = "absolute";
361 /* This positioning isn't strictly right for apps in another topwindow,
362 * but that will be fixed up when showing. */
363 toplevelElement.style["left"] = surface.x + "px";
364 toplevelElement.style["top"] = surface.y + "px";
365 toplevelElement.style["display"] = "inline";
367 /* We hide the frame with visibility rather than display none
368 * so getFrameOffset still works with hidden windows. */
369 toplevelElement.style["visibility"] = "hidden";
371 surfaces[id] = surface;
372 stackingOrder.push(surface);
374 sendConfigureNotify(surface);
377 function cmdShowSurface(id)
379 var surface = surfaces[id];
383 surface.visible = true;
385 var xOffset = surface.x;
386 var yOffset = surface.y;
389 var offset = getFrameOffset(surface);
394 surface.toplevelElement.style["left"] = xOffset + "px";
395 surface.toplevelElement.style["top"] = yOffset + "px";
396 surface.toplevelElement.style["visibility"] = "visible";
401 function cmdHideSurface(id)
403 if (grab.window == id)
406 var surface = surfaces[id];
408 if (!surface.visible)
410 surface.visible = false;
412 var element = surface.toplevelElement;
414 element.style["visibility"] = "hidden";
417 function cmdSetTransientFor(id, parentId)
419 var surface = surfaces[id];
421 if (surface.transientParent == parentId)
424 surface.transientParent = parentId;
425 if (parentId != 0 && surfaces[parentId]) {
426 moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1);
429 if (surface.visible) {
434 function restackWindows() {
435 for (var i = 0; i < stackingOrder.length; i++) {
436 var surface = stackingOrder[i];
437 surface.toplevelElement.style.zIndex = i;
441 function moveToHelper(surface, position) {
442 var i = stackingOrder.indexOf(surface);
443 stackingOrder.splice(i, 1);
444 if (position != undefined)
445 stackingOrder.splice(position, 0, surface);
447 stackingOrder.push(surface);
449 for (var cid in surfaces) {
450 var child = surfaces[cid];
451 if (child.transientParent == surface.id)
452 moveToHelper(child, stackingOrder.indexOf(surface) + 1);
456 function moveToTop(surface) {
457 moveToHelper(surface);
462 function cmdDeleteSurface(id)
464 if (grab.window == id)
467 var surface = surfaces[id];
468 var i = stackingOrder.indexOf(surface);
470 stackingOrder.splice(i, 1);
471 var canvas = surface.canvas;
472 canvas.parentNode.removeChild(canvas);
473 var frame = surface.frame;
475 frame.parentNode.removeChild(frame);
479 function cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h)
481 var surface = surfaces[id];
483 surface.positioned = true;
492 /* Flush any outstanding draw ops before (possibly) changing size */
493 flushSurface(surface);
496 resizeCanvas(surface.canvas, w, h);
498 if (surface.visible) {
500 var xOffset = surface.x;
501 var yOffset = surface.y;
503 var transientToplevel = getTransientToplevel(surface);
504 if (transientToplevel) {
505 xOffset = surface.x - transientToplevel.x;
506 yOffset = surface.y - transientToplevel.y;
509 var element = surface.canvas;
511 element = surface.frame;
512 var offset = getFrameOffset(surface);
517 element.style["left"] = xOffset + "px";
518 element.style["top"] = yOffset + "px";
522 sendConfigureNotify(surface);
525 function cmdFlushSurface(id)
527 flushSurface(surfaces[id]);
530 function cmdGrabPointer(id, ownerEvents)
532 doGrab(id, ownerEvents, false);
536 function cmdUngrabPointer()
543 function handleCommands(cmd)
545 while (cmd.pos < cmd.length) {
546 var id, x, y, w, h, q;
547 var command = cmd.get_char();
548 lastSerial = cmd.get_32();
550 case 's': // create new surface
556 var isTemp = cmd.get_bool();
557 cmdCreateSurface(id, x, y, w, h, isTemp);
560 case 'S': // Show a surface
565 case 'H': // Hide a surface
570 case 'p': // Set transient parent
572 var parentId = cmd.get_16();
573 cmdSetTransientFor(id, parentId);
576 case 'd': // Delete surface
578 cmdDeleteSurface(id);
581 case 'm': // Move a surface
583 var ops = cmd.get_flags();
584 var has_pos = ops & 1;
589 var has_size = ops & 2;
594 cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h);
597 case 'i': // Put image data surface
603 var url = cmd.get_image_url ();
606 surfaces[q.id].drawQueue.push(q);
607 if (!q.img.complete) {
608 q.img.onload = function() { cmd.free_image_url (url); handleOutstanding(); };
611 cmd.free_image_url (url);
614 case 'b': // Copy rects
618 var nrects = cmd.get_16();
621 for (var r = 0; r < nrects; r++) {
622 var rect = new Object();
623 rect.x = cmd.get_16();
624 rect.y = cmd.get_16();
625 rect.w = cmd.get_16();
626 rect.h = cmd.get_16();
630 q.dx = cmd.get_16s();
631 q.dy = cmd.get_16s();
632 surfaces[q.id].drawQueue.push(q);
635 case 'f': // Flush surface
643 var ownerEvents = cmd.get_bool ();
645 cmdGrabPointer(id, ownerEvents);
652 alert("Unknown op " + command);
658 function handleOutstanding()
660 while (outstandingCommands.length > 0) {
661 var cmd = outstandingCommands.shift();
662 if (!handleCommands(cmd)) {
663 outstandingCommands.unshift(cmd);
669 function TextCommands(message) {
671 this.length = message.length;
675 TextCommands.prototype.get_char = function() {
676 return this.data[this.pos++];
678 TextCommands.prototype.get_bool = function() {
679 return this.get_char() == '1';
681 TextCommands.prototype.get_flags = function() {
682 return this.get_char() - 48;
684 TextCommands.prototype.get_16 = function() {
685 var n = base64_16(this.data, this.pos);
686 this.pos = this.pos + 3;
689 TextCommands.prototype.get_16s = function() {
690 var n = base64_16s(this.data, this.pos);
691 this.pos = this.pos + 3;
694 TextCommands.prototype.get_32 = function() {
695 var n = base64_32(this.data, this.pos);
696 this.pos = this.pos + 6;
699 TextCommands.prototype.get_image_url = function() {
700 var size = this.get_32();
701 var url = this.data.slice(this.pos, this.pos + size);
702 this.pos = this.pos + size;
705 TextCommands.prototype.free_image_url = function(url) {
708 function BinCommands(message) {
709 this.arraybuffer = message;
710 this.u8 = new Uint8Array(message);
711 this.length = this.u8.length;
715 BinCommands.prototype.get_char = function() {
716 return String.fromCharCode(this.u8[this.pos++]);
718 BinCommands.prototype.get_bool = function() {
719 return this.u8[this.pos++] != 0;
721 BinCommands.prototype.get_flags = function() {
722 return this.u8[this.pos++];
724 BinCommands.prototype.get_16 = function() {
727 (this.u8[this.pos+1] << 8);
728 this.pos = this.pos + 2;
731 BinCommands.prototype.get_16s = function() {
732 var v = this.get_16 ();
738 BinCommands.prototype.get_32 = function() {
741 (this.u8[this.pos+1] << 8) +
742 (this.u8[this.pos+2] << 16) +
743 (this.u8[this.pos+3] << 24);
744 this.pos = this.pos + 4;
747 BinCommands.prototype.get_image_url = function() {
748 var size = this.get_32();
749 var png_blob = new Blob ([this.arraybuffer.slice (this.pos, this.pos + size)], {type:"image/png"});
750 var url = URL.createObjectURL(png_blob, {oneTimeOnly: true});
751 this.pos = this.pos + size;
754 BinCommands.prototype.free_image_url = function(url) {
755 URL.revokeObjectURL(url);
758 function handleMessage(message)
761 if (message instanceof ArrayBuffer)
762 cmd = new BinCommands(message);
764 cmd = new TextCommands(message);
765 outstandingCommands.push(cmd);
766 if (outstandingCommands.length == 1) {
771 function getSurfaceId(ev) {
772 var surface = ev.target.surface;
773 if (surface != undefined)
778 function sendInput(cmd, args)
780 if (inputSocket != null) {
781 inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
785 function getPositionsFromAbsCoord(absX, absY, relativeId) {
792 if (relativeId != 0) {
793 var surface = surfaces[relativeId];
794 res.winX = res.winX - surface.x;
795 res.winY = res.winY - surface.y;
801 function getPositionsFromEvent(ev, relativeId) {
805 var res = getPositionsFromAbsCoord(absX, absY, relativeId);
813 function getEffectiveEventTarget (id) {
814 if (grab.window != null) {
815 if (!grab.ownerEvents)
823 function updateForEvent(ev) {
824 lastState &= ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK|GDK_MOD1_MASK);
826 lastState |= GDK_SHIFT_MASK;
828 lastState |= GDK_CONTROL_MASK;
830 lastState |= GDK_MOD1_MASK;
832 lastTimeStamp = ev.timeStamp;
835 function onMouseMove (ev) {
838 if (localGrab.type == "move") {
839 var dx = ev.pageX - localGrab.lastX;
840 var dy = ev.pageY - localGrab.lastY;
841 var surface = localGrab.surface;
844 var offset = getFrameOffset(surface);
845 localGrab.frame.style["left"] = (surface.x - offset.x) + "px";
846 localGrab.frame.style["top"] = (surface.y - offset.y) + "px";
847 sendConfigureNotify(surface);
848 localGrab.lastX = ev.pageX;
849 localGrab.lastY = ev.pageY;
853 var id = getSurfaceId(ev);
854 id = getEffectiveEventTarget (id);
855 var pos = getPositionsFromEvent(ev, id);
856 sendInput ("m", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
859 function onMouseOver (ev) {
862 if (!grab.window && ev.target.closeFor) {
863 ev.target.className = ev.target.className + " frame-hover";
864 if (ev.target.isDown)
865 ev.target.className = ev.target.className + " frame-active";
870 var id = getSurfaceId(ev);
871 realWindowWithMouse = id;
872 id = getEffectiveEventTarget (id);
873 var pos = getPositionsFromEvent(ev, id);
874 windowWithMouse = id;
875 if (windowWithMouse != 0) {
876 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
880 function onMouseOut (ev) {
882 if (ev.target.closeFor) {
883 ev.target.className = ev.target.className.replace(" frame-hover", "");
884 if (ev.target.isDown)
885 ev.target.className = ev.target.className.replace(" frame-active", "");
889 var id = getSurfaceId(ev);
891 id = getEffectiveEventTarget (id);
892 var pos = getPositionsFromEvent(ev, id);
895 sendInput ("l", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
897 realWindowWithMouse = 0;
901 function doGrab(id, ownerEvents, implicit) {
904 if (windowWithMouse != id) {
905 if (windowWithMouse != 0) {
906 pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
907 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
909 pos = getPositionsFromAbsCoord(lastX, lastY, id);
910 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
911 windowWithMouse = id;
915 grab.ownerEvents = ownerEvents;
916 grab.implicit = implicit;
919 function doUngrab() {
921 if (realWindowWithMouse != windowWithMouse) {
922 if (windowWithMouse != 0) {
923 pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
924 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
926 if (realWindowWithMouse != 0) {
927 pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);
928 sendInput ("e", [realWindowWithMouse, realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
930 windowWithMouse = realWindowWithMouse;
935 function onMouseDown (ev) {
937 var button = ev.button + 1;
938 lastState = lastState | getButtonMask (button);
939 var id = getSurfaceId(ev);
940 id = getEffectiveEventTarget (id);
942 if (id == 0 && ev.target.frameFor) { /* mouse click on frame */
943 localGrab = new Object();
944 localGrab.surface = ev.target.frameFor;
945 localGrab.type = "move";
946 localGrab.frame = ev.target;
947 localGrab.lastX = ev.pageX;
948 localGrab.lastY = ev.pageY;
949 moveToTop(localGrab.frame.frameFor);
953 if (id == 0 && ev.target.closeFor) { /* mouse click on frame */
954 ev.target.isDown = true;
955 ev.target.className = ev.target.className + " frame-active";
956 localGrab = new Object();
957 localGrab.surface = ev.target.closeFor;
958 localGrab.type = "close";
959 localGrab.button = ev.target;
960 localGrab.lastX = ev.pageX;
961 localGrab.lastY = ev.pageY;
965 var pos = getPositionsFromEvent(ev, id);
966 if (grab.window == null)
967 doGrab (id, false, true);
968 sendInput ("b", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
971 function onMouseUp (ev) {
973 var button = ev.button + 1;
974 lastState = lastState & ~getButtonMask (button);
975 var evId = getSurfaceId(ev);
976 id = getEffectiveEventTarget (evId);
977 var pos = getPositionsFromEvent(ev, id);
980 realWindowWithMouse = evId;
981 if (windowWithMouse != id) {
982 if (windowWithMouse != 0) {
983 sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
985 windowWithMouse = id;
986 if (windowWithMouse != 0) {
987 sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
991 if (localGrab.type == "close") {
992 localGrab.button.isDown = false;
993 localGrab.button.className = localGrab.button.className.replace( " frame-active", "");
994 if (ev.target == localGrab.button)
995 sendInput ("W", [localGrab.surface.id]);
1001 sendInput ("B", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
1003 if (grab.window != null && grab.implicit)
1007 /* Some of the keyboard handling code is from noVNC and
1008 * (c) Joel Martin (github@martintribe.org), used with permission
1010 * https://github.com/kanaka/noVNC/blob/master/include/input.js
1013 var unicodeTable = {
2274 var ON_KEYDOWN = 1 << 0; /* Report on keydown, otherwise wait until keypress */
2277 var specialKeyTable = {
2278 // These generate a keyDown and keyPress in Firefox and Opera
2279 8: [0xFF08, ON_KEYDOWN], // BACKSPACE
2280 13: [0xFF0D, ON_KEYDOWN], // ENTER
2282 // This generates a keyDown and keyPress in Opera
2283 9: [0xFF09, ON_KEYDOWN], // TAB
2285 27: 0xFF1B, // ESCAPE
2286 46: 0xFFFF, // DELETE
2289 33: 0xFF55, // PAGE_UP
2290 34: 0xFF56, // PAGE_DOWN
2291 45: 0xFF63, // INSERT
2294 39: 0xFF53, // RIGHT
2296 16: 0xFFE1, // SHIFT
2297 17: 0xFFE3, // CONTROL
2298 18: 0xFFE9, // Left ALT (Mac Command)
2313 function getEventKeySym(ev) {
2314 if (typeof ev.which !== "undefined" && ev.which > 0)
2319 // This is based on the approach from noVNC. We handle
2320 // everything in keydown that we have all info for, and that
2321 // are not safe to pass on to the browser (as it may do something
2322 // with the key. The rest we pass on to keypress so we can get the
2323 // translated keysym.
2324 function getKeysymSpecial(ev) {
2325 if (ev.keyCode in specialKeyTable) {
2326 var r = specialKeyTable[ev.keyCode];
2328 if (typeof r != 'number') {
2332 if (ev.type === 'keydown' || flags & ON_KEYDOWN)
2335 // If we don't hold alt or ctrl, then we should be safe to pass
2336 // on to keypressed and look at the translated data
2337 if (!ev.ctrlKey && !ev.altKey)
2340 var keysym = getEventKeySym(ev);
2344 case 186 : keysym = 59; break; // ; (IE)
2345 case 187 : keysym = 61; break; // = (IE)
2346 case 188 : keysym = 44; break; // , (Mozilla, IE)
2347 case 109 : // - (Mozilla, Opera)
2348 if (true /* TODO: check if browser is firefox or opera */)
2351 case 189 : keysym = 45; break; // - (IE)
2352 case 190 : keysym = 46; break; // . (Mozilla, IE)
2353 case 191 : keysym = 47; break; // / (Mozilla, IE)
2354 case 192 : keysym = 96; break; // ` (Mozilla, IE)
2355 case 219 : keysym = 91; break; // [ (Mozilla, IE)
2356 case 220 : keysym = 92; break; // \ (Mozilla, IE)
2357 case 221 : keysym = 93; break; // ] (Mozilla, IE)
2358 case 222 : keysym = 39; break; // ' (Mozilla, IE)
2361 /* Remap shifted and unshifted keys */
2362 if (!!ev.shiftKey) {
2364 case 48 : keysym = 41 ; break; // ) (shifted 0)
2365 case 49 : keysym = 33 ; break; // ! (shifted 1)
2366 case 50 : keysym = 64 ; break; // @ (shifted 2)
2367 case 51 : keysym = 35 ; break; // # (shifted 3)
2368 case 52 : keysym = 36 ; break; // $ (shifted 4)
2369 case 53 : keysym = 37 ; break; // % (shifted 5)
2370 case 54 : keysym = 94 ; break; // ^ (shifted 6)
2371 case 55 : keysym = 38 ; break; // & (shifted 7)
2372 case 56 : keysym = 42 ; break; // * (shifted 8)
2373 case 57 : keysym = 40 ; break; // ( (shifted 9)
2374 case 59 : keysym = 58 ; break; // : (shifted `)
2375 case 61 : keysym = 43 ; break; // + (shifted ;)
2376 case 44 : keysym = 60 ; break; // < (shifted ,)
2377 case 45 : keysym = 95 ; break; // _ (shifted -)
2378 case 46 : keysym = 62 ; break; // > (shifted .)
2379 case 47 : keysym = 63 ; break; // ? (shifted /)
2380 case 96 : keysym = 126; break; // ~ (shifted `)
2381 case 91 : keysym = 123; break; // { (shifted [)
2382 case 92 : keysym = 124; break; // | (shifted \)
2383 case 93 : keysym = 125; break; // } (shifted ])
2384 case 39 : keysym = 34 ; break; // " (shifted ')
2386 } else if ((keysym >= 65) && (keysym <=90)) {
2387 /* Remap unshifted A-Z */
2389 } else if (ev.keyLocation === 3) {
2392 case 96 : keysym = 48; break; // 0
2393 case 97 : keysym = 49; break; // 1
2394 case 98 : keysym = 50; break; // 2
2395 case 99 : keysym = 51; break; // 3
2396 case 100: keysym = 52; break; // 4
2397 case 101: keysym = 53; break; // 5
2398 case 102: keysym = 54; break; // 6
2399 case 103: keysym = 55; break; // 7
2400 case 104: keysym = 56; break; // 8
2401 case 105: keysym = 57; break; // 9
2402 case 109: keysym = 45; break; // -
2403 case 110: keysym = 46; break; // .
2404 case 111: keysym = 47; break; // /
2411 /* Translate DOM keyPress event to keysym value */
2412 function getKeysym(ev) {
2415 keysym = getEventKeySym(ev);
2417 if ((keysym > 255) && (keysym < 0xFF00)) {
2418 // Map Unicode outside Latin 1 to gdk keysyms
2419 keysym = unicodeTable[keysym];
2420 if (typeof keysym === 'undefined')
2427 function copyKeyEvent(ev) {
2428 var members = ['type', 'keyCode', 'charCode', 'which',
2429 'altKey', 'ctrlKey', 'shiftKey',
2430 'keyLocation', 'keyIdentifier'], i, obj = {};
2431 for (i = 0; i < members.length; i++) {
2432 if (typeof ev[members[i]] !== "undefined")
2433 obj[members[i]] = ev[members[i]];
2438 function pushKeyEvent(fev) {
2439 keyDownList.push(fev);
2442 function getKeyEvent(keyCode, pop) {
2444 for (i = keyDownList.length-1; i >= 0; i--) {
2445 if (keyDownList[i].keyCode === keyCode) {
2446 if ((typeof pop !== "undefined") && pop)
2447 fev = keyDownList.splice(i, 1)[0];
2449 fev = keyDownList[i];
2456 function ignoreKeyEvent(ev) {
2457 // Blarg. Some keys have a different keyCode on keyDown vs keyUp
2458 if (ev.keyCode === 229) {
2459 // French AZERTY keyboard dead key.
2460 // Lame thing is that the respective keyUp is 219 so we can't
2461 // properly ignore the keyUp event
2467 function handleKeyDown(e) {
2468 var fev = null, ev = (e ? e : window.event), keysym = null, suppress = false;
2470 fev = copyKeyEvent(ev);
2472 keysym = getKeysymSpecial(ev);
2473 // Save keysym decoding for use in keyUp
2474 fev.keysym = keysym;
2476 // If it is a key or key combination that might trigger
2477 // browser behaviors or it has no corresponding keyPress
2478 // event, then send it immediately
2479 if (!ignoreKeyEvent(ev))
2480 sendInput("k", [realWindowWithMouse, keysym, lastState]);
2484 if (! ignoreKeyEvent(ev)) {
2485 // Add it to the list of depressed keys
2490 // Suppress bubbling/default actions
2491 return cancelEvent(ev);
2494 // Allow the event to bubble and become a keyPress event which
2495 // will have the character code translated
2499 function handleKeyPress(e) {
2500 var ev = (e ? e : window.event), kdlen = keyDownList.length, keysym = null;
2502 if (((ev.which !== "undefined") && (ev.which === 0)) ||
2503 getKeysymSpecial(ev)) {
2504 // Firefox and Opera generate a keyPress event even if keyDown
2505 // is suppressed. But the keys we want to suppress will have
2507 // - the which attribute set to 0
2508 // - getKeysymSpecial() will identify it
2509 return cancelEvent(ev);
2512 keysym = getKeysym(ev);
2514 // Modify the the which attribute in the depressed keys list so
2515 // that the keyUp event will be able to have the character code
2516 // translation available.
2518 keyDownList[kdlen-1].keysym = keysym;
2520 //log("keyDownList empty when keyPress triggered");
2523 // Send the translated keysym
2525 sendInput ("k", [realWindowWithMouse, keysym, lastState]);
2527 // Stop keypress events just in case
2528 return cancelEvent(ev);
2531 function handleKeyUp(e) {
2532 var fev = null, ev = (e ? e : window.event), i, keysym;
2534 fev = getKeyEvent(ev.keyCode, true);
2537 keysym = fev.keysym;
2539 //log("Key event (keyCode = " + ev.keyCode + ") not found on keyDownList");
2544 sendInput ("K", [realWindowWithMouse, keysym, lastState]);
2545 return cancelEvent(ev);
2548 function onKeyDown (ev) {
2551 return cancelEvent(ev);
2552 return handleKeyDown(ev);
2555 function onKeyPress(ev) {
2558 return cancelEvent(ev);
2559 return handleKeyPress(ev);
2562 function onKeyUp (ev) {
2565 return cancelEvent(ev);
2566 return handleKeyUp(ev);
2569 function cancelEvent(ev)
2571 ev = ev ? ev : window.event;
2572 if (ev.stopPropagation)
2573 ev.stopPropagation();
2574 if (ev.preventDefault)
2575 ev.preventDefault();
2576 ev.cancelBubble = true;
2578 ev.returnValue = false;
2582 function onMouseWheel(ev)
2587 ev = ev ? ev : window.event;
2589 var id = getSurfaceId(ev);
2590 var pos = getPositionsFromEvent(ev, id);
2592 var offset = ev.detail ? ev.detail : ev.wheelDelta;
2596 sendInput ("s", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
2598 return cancelEvent(ev);
2601 function setupDocument(document)
2603 document.oncontextmenu = function () { return false; };
2604 document.onmousemove = onMouseMove;
2605 document.onmouseover = onMouseOver;
2606 document.onmouseout = onMouseOut;
2607 document.onmousedown = onMouseDown;
2608 document.onmouseup = onMouseUp;
2609 document.onkeydown = onKeyDown;
2610 document.onkeypress = onKeyPress;
2611 document.onkeyup = onKeyUp;
2613 if (document.addEventListener) {
2614 document.addEventListener('DOMMouseScroll', onMouseWheel, false);
2615 document.addEventListener('mousewheel', onMouseWheel, false);
2616 } else if (document.attachEvent) {
2617 element.attachEvent("onmousewheel", onMouseWheel);
2621 function newWS(loc) {
2623 if ("WebSocket" in window) {
2624 ws = new WebSocket(loc, "broadway");
2625 } else if ("MozWebSocket" in window) { // Firefox 6
2626 ws = new MozWebSocket(loc);
2628 alert("WebSocket not supported, broadway will not work!");
2635 var url = window.location.toString();
2636 var query_string = url.split("?");
2637 if (query_string.length > 1) {
2638 var params = query_string[1].split("&");
2641 var loc = window.location.toString().replace("http:", "ws:");
2642 loc = loc.substr(0, loc.lastIndexOf('/')) + "/socket";
2644 var supports_binary = newWS (loc + "-test").binaryType == "blob";
2645 if (supports_binary) {
2646 ws = newWS (loc + "-bin");
2647 ws.binaryType = "arraybuffer";
2652 ws.onopen = function() {
2655 w = window.innerWidth;
2656 h = window.innerHeight;
2657 window.onresize = function(ev) {
2659 w = window.innerWidth;
2660 h = window.innerHeight;
2661 sendInput ("d", [w, h]);
2663 sendInput ("d", [w, h]);
2665 ws.onclose = function() {
2668 ws.onmessage = function(event) {
2669 handleMessage(event.data);
2672 setupDocument(document);