]> Pileus Git - ~andy/gtk/blobdiff - gdk/broadway/broadway.js
[broadway] Remove unused grab.time on browser side
[~andy/gtk] / gdk / broadway / broadway.js
index de1ac3f0e776c02601f68af456600026872c3631..2a2bb9fc9dc06db615880e3c176d0b68f25846e8 100644 (file)
+/* Helper functions for debugging */
+var logDiv = null;
+function log(str) {
+    if (!logDiv) {
+       logDiv = document.createElement('div');
+       document.body.appendChild(logDiv);
+       logDiv.style["position"] = "absolute";
+       logDiv.style["right"] = "0px";
+    }
+    logDiv.appendChild(document.createTextNode(str));
+    logDiv.appendChild(document.createElement('br'));
+}
 
-var base64_val = [
-  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
-  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
-  255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
-   52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,
-  255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
-   15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
-  255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
-   41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255
+var base64Values = [
+    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+    255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,
+    255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
+    255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255
 ]
 
 function base64_8(str, index) {
-  var v =
-    (base64_val[str.charCodeAt(index)]) +
-    (base64_val[str.charCodeAt(index+1)] << 6);
-  return v;
+    var v =
+       (base64Values[str.charCodeAt(index)]) +
+       (base64Values[str.charCodeAt(index+1)] << 6);
+    return v;
 }
 
 function base64_16(str, index) {
-  var v =
-    (base64_val[str.charCodeAt(index)]) +
-    (base64_val[str.charCodeAt(index+1)] << 6) +
-    (base64_val[str.charCodeAt(index+2)] << 12);
-  return v;
+    var v =
+       (base64Values[str.charCodeAt(index)]) +
+       (base64Values[str.charCodeAt(index+1)] << 6) +
+       (base64Values[str.charCodeAt(index+2)] << 12);
+    return v;
 }
 
 function base64_16s(str, index) {
-  var v = base64_16(str, index);
-  if (v > 32767)
-    return v - 65536;
-  else
-    return v;
+    var v = base64_16(str, index);
+    if (v > 32767)
+       return v - 65536;
+    else
+       return v;
 }
 
 function base64_24(str, index) {
-  var v =
-    (base64_val[str.charCodeAt(index)]) +
-    (base64_val[str.charCodeAt(index+1)] << 6) +
-    (base64_val[str.charCodeAt(index+2)] << 12) +
-    (base64_val[str.charCodeAt(index+3)] << 18);
-  return v;
+    var v =
+       (base64Values[str.charCodeAt(index)]) +
+       (base64Values[str.charCodeAt(index+1)] << 6) +
+       (base64Values[str.charCodeAt(index+2)] << 12) +
+       (base64Values[str.charCodeAt(index+3)] << 18);
+    return v;
 }
 
 function base64_32(str, index) {
-  var v =
-    (base64_val[str.charCodeAt(index)]) +
-    (base64_val[str.charCodeAt(index+1)] << 6) +
-    (base64_val[str.charCodeAt(index+2)] << 12) +
-    (base64_val[str.charCodeAt(index+3)] << 18) +
-    (base64_val[str.charCodeAt(index+4)] << 24) +
-    (base64_val[str.charCodeAt(index+5)] << 30);
-  return v;
+    var v =
+       (base64Values[str.charCodeAt(index)]) +
+       (base64Values[str.charCodeAt(index+1)] << 6) +
+       (base64Values[str.charCodeAt(index+2)] << 12) +
+       (base64Values[str.charCodeAt(index+3)] << 18) +
+       (base64Values[str.charCodeAt(index+4)] << 24) +
+       (base64Values[str.charCodeAt(index+5)] << 30);
+    return v;
 }
 
 function createXHR()
 {
-  try { return new XMLHttpRequest(); } catch(e) {}
-  try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
-  try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
-  try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
-  try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
+    try { return new XMLHttpRequest(); } catch(e) {}
+    try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
+    try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {}
+    try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
+    try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
 
-  return null;
+    return null;
 }
 
+var grab = new Object();
+grab.window = null;
+grab.ownerEvents = false;
+grab.implicit = false;
+var lastSerial = 0;
+var lastX = 0;
+var lastY = 0;
+var lastState;
+var lastTimeStamp = 0;
+var realWindowWithMouse = 0;
+var windowWithMouse = 0;
 var surfaces = {};
-var outstanding_commands = new Array();
+var outstandingCommands = new Array();
+var inputSocket = null;
+
+var GDK_CROSSING_NORMAL = 0;
+var GDK_CROSSING_GRAB = 1;
+var GDK_CROSSING_UNGRAB = 2;
+
+// GdkModifierType
+var GDK_SHIFT_MASK = 1 << 0;
+var GDK_LOCK_MASK     = 1 << 1;
+var GDK_CONTROL_MASK  = 1 << 2;
+var GDK_MOD1_MASK     = 1 << 3;
+var GDK_MOD2_MASK     = 1 << 4;
+var GDK_MOD3_MASK     = 1 << 5;
+var GDK_MOD4_MASK     = 1 << 6;
+var GDK_MOD5_MASK     = 1 << 7;
+var GDK_BUTTON1_MASK  = 1 << 8;
+var GDK_BUTTON2_MASK  = 1 << 9;
+var GDK_BUTTON3_MASK  = 1 << 10;
+var GDK_BUTTON4_MASK  = 1 << 11;
+var GDK_BUTTON5_MASK  = 1 << 12;
+var GDK_SUPER_MASK    = 1 << 26;
+var GDK_HYPER_MASK    = 1 << 27;
+var GDK_META_MASK     = 1 << 28;
+var GDK_RELEASE_MASK  = 1 << 30;
+
+function getButtonMask (button) {
+    if (button == 1)
+       return GDK_BUTTON1_MASK;
+    if (button == 2)
+       return GDK_BUTTON2_MASK;
+    if (button == 3)
+       return GDK_BUTTON3_MASK;
+    if (button == 4)
+       return GDK_BUTTON4_MASK;
+    if (button == 5)
+       return GDK_BUTTON5_MASK;
+    return 0;
+}
 
-function apply_delta(id, img, x, y)
+function flushSurface(surface)
 {
-  var tmp_surface = document.createElement("canvas");
-  var w = img.width;
-  var h = img.height;
-  tmp_surface.width = w;
-  tmp_surface.height = h;
-
-  tmp_context = tmp_surface.getContext("2d");
-  tmp_context.drawImage(img, 0, 0);
-
-  var data = surfaces[id].getImageData(x, y, w, h);
-  var d = data.data
-  var delta = tmp_context.getImageData(0, 0, w, h).data;
-  var imax = w * h * 4;
-  for (var i = 0; i < imax; i += 4) {
-    d[i  ] = (d[i  ] + delta[i  ]) & 0xff;
-    d[i+1] = (d[i+1] + delta[i+1]) & 0xff;
-    d[i+2] = (d[i+2] + delta[i+2]) & 0xff;
-    d[i+3] = 255;
-  }
-  surfaces[id].putImageData(data, x, y);
-  delete tmp_surface
-}
-
-function initContext(canvas, x, y)
+    var commands = surface.drawQueue;
+    surface.queue = [];
+    var context = surface.context;
+    var i = 0;
+    for (i = 0; i < commands.length; i++) {
+       var cmd = commands[i];
+       switch (cmd.op) {
+       case 'i': // put image data surface
+           context.globalCompositeOperation = "source-over";
+           context.drawImage(cmd.img, cmd.x, cmd.y);
+           break;
+
+       case 'b': // copy rects
+           context.save();
+           context.beginPath();
+
+           var minx;
+           var miny;
+           var maxx;
+           var maxy;
+           for (var j = 0; j < cmd.rects.length; j++) {
+               var rect = cmd.rects[j];
+               context.rect(rect.x, rect.y, rect.w, rect.h);
+               if (j == 0) {
+                   minx = rect.x;
+                   miny = rect.y;
+                   maxx = rect.x + rect.w;
+                   maxy = rect.y + rect.h;
+               } else {
+                   if (rect.x < minx)
+                       minx = rect.x;
+                   if (rect.y < miny)
+                       miny = rect.y;
+                   if (rect.x + rect.w > maxx)
+                       maxx = rect.x + rect.w;
+                   if (rect.y + rect.h > maxy)
+                       maxy = rect.y + rect.h;
+               }
+           }
+           context.clip();
+           context.globalCompositeOperation = "copy";
+           context.drawImage(context.canvas,
+                             minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,
+                             minx, miny, maxx - minx, maxy - miny);
+           context.restore();
+           break;
+
+       default:
+           alert("Unknown drawing op " + cmd.op);
+       }
+    }
+}
+
+function cmdCreateSurface(id, x, y, width, height, isTemp)
 {
-  canvas.style["position"] = "absolute"
-  canvas.style["top"] = x + "px"
-  canvas.style["left"] = y + "px"
-  canvas.style["display"] = "none"
-  context = canvas.getContext("2d")
-  context.globalCompositeOperation = "copy"
-  context.fillRect(0, 0, canvas.width, canvas.height);
-  document.body.appendChild(canvas)
-  return context
-}
-
-function handleCommands(cmd_obj)
+    var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };
+    surface.drawQueue = [];
+    surface.transientParent = 0;
+
+    var canvas = document.createElement("canvas");
+    canvas.width = width;
+    canvas.height = height;
+    canvas.surface = surface;
+    canvas.style["position"] = "absolute";
+    canvas.style["left"] = x + "px";
+    canvas.style["top"] = y + "px";
+    canvas.style["display"] = "none";
+    document.body.appendChild(canvas);
+    surface.canvas = canvas;
+
+    var context = canvas.getContext("2d");
+    context.globalCompositeOperation = "source-over";
+    surface.context = context;
+
+    surfaces[id] = surface;
+}
+
+function cmdShowSurface(id)
 {
-  var cmd = cmd_obj.data;
-  var i = cmd_obj.pos;
-
-  while (i < cmd.length) {
-    var command = cmd[i++];
-    switch (command) {
-      /* create new surface */
-      case 's':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-        var x = base64_16(cmd, i);
-        i = i + 3;
-        var y = base64_16(cmd, i);
-        i = i + 3;
-        var w = base64_16(cmd, i);
-        i = i + 3;
-        var h = base64_16(cmd, i);
-        i = i + 3;
-        var surface = document.createElement("canvas");
-       surface.width = w;
-       surface.height = h;
-       surfaces[id] = initContext(surface, x, y);
-        break;
-
-      /* show a surface */
-      case 'S':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-       surfaces[id].canvas.style["display"] = "inline";
-        break;
-
-      /* hide a surface */
-      case 'H':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-       surfaces[id].canvas.style["display"] = "inline";
-        break;
-
-      /* delete surface */
-      case 'd':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-       var canvas = surfaces[id].canvas
-       delete surfaces[id]
-       canvas.parentNode.removeChild(canvas);
-
-        break;
-
-      /* move a surface */
-      case 'm':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-        var x = base64_16(cmd, i);
-        i = i + 3;
-        var y = base64_16(cmd, i);
-        i = i + 3;
-       surfaces[id].canvas.style["left"] = x + "px";
-       surfaces[id].canvas.style["top"] = y + "px";
-        break;
-
-      /* put image data surface */
-      case 'i':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-        var x = base64_16(cmd, i);
-        i = i + 3;
-        var y = base64_16(cmd, i);
-        i = i + 3;
-        var size = base64_32(cmd, i);
-        i = i + 6;
-       var url = cmd.slice(i, i + size);
-       i = i + size;
-        var img = new Image();
-       img.src = url
-       if (img.complete) {
-          surfaces[id].drawImage(img, x, y);
-       } else {
-         cmd_obj.pos = i;
-         img.onload = function() { surfaces[id].drawImage(img, x, y); handleOutstanding(); }
-         return false
-       }
+    surfaces[id].canvas.style["display"] = "inline";
+}
 
-        break;
-
-      /* put delta image data surface */
-      case 'D':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-        var x = base64_16(cmd, i);
-        i = i + 3;
-        var y = base64_16(cmd, i);
-        i = i + 3;
-        var size = base64_32(cmd, i);
-        i = i + 6;
-       var url = cmd.slice(i, i + size);
-       i = i + size;
-        var img = new Image();
-       img.src = url
-       if (img.complete) {
-         apply_delta(id, img, x, y);
-       } else {
-         cmd_obj.pos = i;
-          img.onload = function() { apply_delta(id, img, x, y); handleOutstanding(); }
-         return false
-       }
+function cmdHideSurface(id)
+{
+    surfaces[id].canvas.style["display"] = "none";
+}
 
-        break;
-
-      /* copy rects */
-      case 'b':
-        var id = base64_16(cmd, i);
-        i = i + 3;
-
-       var nrects = base64_16(cmd, i);
-        i = i + 3;
-
-       var context = surfaces[id];
-       context.save();
-
-       for (var r = 0; r < nrects; r++) {
-         var x = base64_16(cmd, i);
-          i = i + 3;
-          var y = base64_16(cmd, i);
-          i = i + 3;
-          var w = base64_16(cmd, i);
-          i = i + 3;
-          var h = base64_16(cmd, i);
-          i = i + 3;
-         context.rect(x, y, w, h);
-       }
+function cmdSetTransientFor(id, parentId)
+{
+    surfaces[id].transientParent = parentId;
+}
+
+function cmdDeleteSurface(id)
+{
+    var canvas = surfaces[id].canvas;
+    delete surfaces[id];
+    canvas.parentNode.removeChild(canvas);
+}
+
+function cmdMoveSurface(id, x, y)
+{
+    surfaces[id].canvas.style["left"] = x + "px";
+    surfaces[id].canvas.style["top"] = y + "px";
+}
+
+function cmdResizeSurface(id, w, h)
+{
+    var surface = surfaces[id];
+
+    /* Flush any outstanding draw ops before changing size */
+    flushSurface(surface);
 
-       context.clip()
+    /* Canvas resize clears the data, so we need to save it first */
+    var tmpCanvas = document.createElement("canvas");
+    tmpCanvas.width = surface.canvas.width;
+    tmpCanvas.height = surface.canvas.height;
+    var tmpContext = tmpCanvas.getContext("2d");
+    tmpContext.globalCompositeOperation = "copy";
+    tmpContext.drawImage(surface.canvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
+
+    surface.canvas.width = w;
+    surface.canvas.height = h;
+
+    surface.context.globalCompositeOperation = "copy";
+    surface.context.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);
+}
 
-        var dx = base64_16s(cmd, i);
-        i = i + 3;
-        var dy = base64_16s(cmd, i);
-        i = i + 3;
+function cmdFlushSurface(id)
+{
+    flushSurface(surfaces[id]);
+}
 
-        context.drawImage(context.canvas, dx, dy, context.canvas.width, context.canvas.height);
+function cmdGrabPointer(id, ownerEvents)
+{
+    doGrab(id, ownerEvents, false);
+    sendInput ("g", []);
+}
 
-       context.restore();
-        break;
+function cmdUngrabPointer()
+{
+    sendInput ("u", []);
 
-      default:
-        alert("Unknown op " + command);
+    if (grab.window != null) {
+       if (grab.time == 0 || time == 0 ||
+           grab.time < time)
+           grab.window = null;
     }
-  }
-  return true;
+}
+
+function handleCommands(cmdObj)
+{
+    var cmd = cmdObj.data;
+    var i = cmdObj.pos;
+
+    while (i < cmd.length) {
+       var command = cmd[i++];
+       lastSerial = base64_32(cmd, i);
+       i = i + 6;
+       switch (command) {
+       case 's': // create new surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           var x = base64_16(cmd, i);
+           i = i + 3;
+           var y = base64_16(cmd, i);
+           i = i + 3;
+           var w = base64_16(cmd, i);
+           i = i + 3;
+           var h = base64_16(cmd, i);
+           i = i + 3;
+           var isTemp = cmd[i] == '1';
+           i = i + 1;
+           cmdCreateSurface(id, x, y, w, h, isTemp);
+           break;
+
+       case 'S': // Show a surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           cmdShowSurface(id);
+           break;
+
+       case 'H': // Hide a surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           cmdHideSurface(id);
+           break;
+
+       case 'p': // Set transient parent
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           var parentId = base64_16(cmd, i);
+           i = i + 3;
+           cmdSetTransientFor(id, parentId);
+           break;
+
+       case 'd': // Delete surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           cmdDeleteSurface(id);
+           break;
+
+       case 'm': // Move a surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           var x = base64_16(cmd, i);
+           i = i + 3;
+           var y = base64_16(cmd, i);
+           i = i + 3;
+           cmdMoveSurface(id, x, y);
+           break;
+
+       case 'r': // Resize a surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           var w = base64_16(cmd, i);
+           i = i + 3;
+           var h = base64_16(cmd, i);
+           i = i + 3;
+           cmdResizeSurface(id, w, h);
+           break;
+
+       case 'i': // Put image data surface
+           var q = new Object();
+           q.op = 'i';
+           q.id = base64_16(cmd, i);
+           i = i + 3;
+           q.x = base64_16(cmd, i);
+           i = i + 3;
+           q.y = base64_16(cmd, i);
+           i = i + 3;
+           var size = base64_32(cmd, i);
+           i = i + 6;
+           var url = cmd.slice(i, i + size);
+           i = i + size;
+           q.img = new Image();
+           q.img.src = url;
+           surfaces[q.id].drawQueue.push(q);
+           if (!q.img.complete) {
+               cmdObj.pos = i;
+               q.img.onload = function() { handleOutstanding(); };
+               return false;
+           }
+           break;
+
+       case 'b': // Copy rects
+           var q = new Object();
+           q.op = 'b';
+           q.id = base64_16(cmd, i);
+           i = i + 3;
+
+           var nrects = base64_16(cmd, i);
+           i = i + 3;
+
+           q.rects = [];
+           for (var r = 0; r < nrects; r++) {
+               var rect = new Object();
+               rect.x = base64_16(cmd, i);
+               i = i + 3;
+               rect.y = base64_16(cmd, i);
+               i = i + 3;
+               rect.w = base64_16(cmd, i);
+               i = i + 3;
+               rect.h = base64_16(cmd, i);
+               i = i + 3;
+               q.rects.push (rect);
+           }
+
+           q.dx = base64_16s(cmd, i);
+           i = i + 3;
+           q.dy = base64_16s(cmd, i);
+           i = i + 3;
+           surfaces[q.id].drawQueue.push(q);
+           break;
+
+       case 'f': // Flush surface
+           var id = base64_16(cmd, i);
+           i = i + 3;
+
+           cmdFlushSurface(id);
+           break;
+
+       case 'g': // Grab
+           var id = base64_16(cmd, i);
+           i = i + 3;
+           var ownerEvents = cmd[i++] == '1';
+
+           cmdGrabPointer(id, ownerEvents);
+           break;
+
+       case 'u': // Ungrab
+           cmdUngrabPointer();
+           break;
+       default:
+           alert("Unknown op " + command);
+       }
+    }
+    return true;
 }
 
 function handleOutstanding()
 {
-  while (outstanding_commands.length > 0) {
-    var cmd = outstanding_commands.shift();
-    if (!handleCommands(cmd)) {
-      outstanding_commands.unshift(cmd);
-      return;
+    while (outstandingCommands.length > 0) {
+       var cmd = outstandingCommands.shift();
+       if (!handleCommands(cmd)) {
+           outstandingCommands.unshift(cmd);
+           return;
+       }
     }
-  }
 }
 
 function handleLoad(event)
 {
-  var cmd_obj = {};
-  cmd_obj.data = event.target.responseText;
-  cmd_obj.pos = 0;
+    var cmdObj = {};
+    cmdObj.data = event.target.responseText;
+    cmdObj.pos = 0;
+
+    outstandingCommands.push(cmdObj);
+    if (outstandingCommands.length == 1) {
+       handleOutstanding();
+    }
+}
 
-  outstanding_commands.push(cmd_obj);
-  if (outstanding_commands.length == 1) {
-    handleOutstanding()
-  }
+function getSurfaceId(ev) {
+    var surface = ev.target.surface;
+    if (surface != undefined)
+       return surface.id;
+    return 0;
 }
 
-function connect(app)
+function sendInput(cmd, args)
 {
-  var xhr = createXHR();
-  if (xhr) {
-    if (typeof xhr.multipart == 'undefined') {
-      alert("Sorry, this example only works in browsers that support multipart.");
-      return;
+    if (inputSocket != null) {
+       inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(","));
+    }
+}
+
+function getDocumentCoordinates(element)
+{
+    var res = new Object();
+    res.x = element.offsetLeft;
+    res.y = element.offsetTop;
+
+    var offsetParent = element.offsetParent;
+    while (offsetParent != null) {
+       res.x += offsetParent.offsetLeft;
+       res.y += offsetParent.offsetTop;
+       offsetParent = offsetParent.offsetParent;
+    }
+    return res;
+}
+
+function getPositionsFromAbsCoord(absX, absY, relativeId) {
+    var res = Object();
+
+    res.rootX = absX;
+    res.rootY = absY;
+    res.winX = absX;
+    res.winY = absY;
+    if (relativeId != 0) {
+       var pos = getDocumentCoordinates(surfaces[relativeId].canvas);
+       res.winX = res.winX - pos.x;
+       res.winY = res.winY - pos.y;
+    }
+
+    return res;
+}
+
+function getPositionsFromEvent(ev, relativeId) {
+    var res = getPositionsFromAbsCoord(ev.pageX, ev.pageY, relativeId);
+
+    lastX = res.rootX;
+    lastY = res.rootY;
+
+    return res;
+}
+
+function getEffectiveEventTarget (id) {
+    if (grab.window != null) {
+       if (!grab.ownerEvents)
+           return grab.window;
+       if (id == 0)
+           return grab.window;
+    }
+    return id;
+}
+
+function updateForEvent(ev) {
+    lastTimeStamp = ev.timeStamp;
+}
+
+function onMouseMove (ev) {
+    updateForEvent(ev);
+    var id = getSurfaceId(ev);
+    id = getEffectiveEventTarget (id);
+    var pos = getPositionsFromEvent(ev, id);
+    sendInput ("m", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);
+}
+
+function onMouseOver (ev) {
+    updateForEvent(ev);
+    var id = getSurfaceId(ev);
+    realWindowWithMouse = id;
+    id = getEffectiveEventTarget (id);
+    var pos = getPositionsFromEvent(ev, id);
+    windowWithMouse = id;
+    if (windowWithMouse != 0) {
+       sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
+    }
+}
+
+function onMouseOut (ev) {
+    updateForEvent(ev);
+    var id = getSurfaceId(ev);
+    var origId = id;
+    id = getEffectiveEventTarget (id);
+    var pos = getPositionsFromEvent(ev, id);
+
+    if (id != 0) {
+       sendInput ("l", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);
+    }
+    realWindowWithMouse = 0;
+    windowWithMouse = 0;
+}
+
+function doGrab(id, ownerEvents, implicit) {
+    var pos;
+
+    if (windowWithMouse != id) {
+       if (windowWithMouse != 0) {
+           pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
+           sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
+       }
+       pos = getPositionsFromAbsCoord(lastX, lastY, id);
+       sendInput ("e", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);
+       windowWithMouse = id;
+    }
+
+    grab.window = id;
+    grab.ownerEvents = ownerEvents;
+    grab.implicit = implicit;
+}
+
+function doUngrab() {
+    var pos;
+    if (realWindowWithMouse != windowWithMouse) {
+       if (windowWithMouse != 0) {
+           pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);
+           sendInput ("l", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
+       }
+       if (realWindowWithMouse != 0) {
+           pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);
+           sendInput ("e", [realWindowWithMouse, realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);
+       }
+       windowWithMouse = realWindowWithMouse;
+    }
+    grab.window = null;
+}
+
+function onMouseDown (ev) {
+    updateForEvent(ev);
+    var id = getSurfaceId(ev);
+    id = getEffectiveEventTarget (id);
+    var pos = getPositionsFromEvent(ev, id);
+    if (grab.window != null)
+       doGrab (id, false, true);
+    var button = ev.button + 1;
+    lastState = lastState | getButtonMask (button);
+    sendInput ("b", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
+}
+
+function onMouseUp (ev) {
+    updateForEvent(ev);
+    var id = getSurfaceId(ev);
+    id = getEffectiveEventTarget (id);
+    var pos = getPositionsFromEvent(ev, id);
+    var button = ev.button + 1;
+    lastState = lastState & ~getButtonMask (button);
+    sendInput ("B", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);
+
+    if (grab.window != null && grab.implicit)
+       doUngrab(ev.timeStamp);
+}
+
+var lastKeyDown = 0;
+function onKeyDown (ev) {
+    updateForEvent(ev);
+    var keyCode = ev.keyCode;
+    if (keyCode != lastKeyDown) {
+       sendInput ("k", [keyCode]);
+       lastKeyDown = keyCode;
     }
+}
 
-    xhr.multipart = true;
-    xhr.open("GET", "/cgi-bin/" + app, true);
-    xhr.onload = handleLoad;
-    xhr.send(null);
-  }
+function onKeyUp (ev) {
+    updateForEvent(ev);
+    var keyCode = ev.keyCode;
+    sendInput ("K", [keyCode]);
+    lastKeyDown = 0;
 }
 
-function startClient(app)
+function cancelEvent(ev)
 {
-  connect(app);
+    ev = ev ? ev : window.event;
+    if (ev.stopPropagation)
+       ev.stopPropagation();
+    if (ev.preventDefault)
+       ev.preventDefault();
+    ev.cancelBubble = true;
+    ev.cancel = true;
+    ev.returnValue = false;
+    return false;
+}
+
+function onMouseWheel(ev)
+{
+    updateForEvent(ev);
+    ev = ev ? ev : window.event;
+
+    var id = getSurfaceId(ev);
+    var pos = getPositionsFromEvent(ev, id);
+
+    var offset = ev.detail ? ev.detail : ev.wheelDelta;
+    var dir = 0;
+    if (offset > 0)
+       dir = 1;
+    sendInput ("s", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);
+
+    return cancelEvent(ev);
+}
+
+function connect()
+{
+    var xhr = createXHR();
+    if (xhr) {
+       if (typeof xhr.multipart == 'undefined') {
+           alert("Sorry, this example only works in browsers that support multipart.");
+           return;
+       }
+
+       xhr.multipart = true;
+       xhr.open("GET", "/output", true);
+       xhr.onload = handleLoad;
+       xhr.send(null);
+    }
+
+    if ("WebSocket" in window) {
+       var loc = window.location.toString().replace("http:", "ws:");
+       loc = loc.substr(0, loc.lastIndexOf('/')) + "/input";
+       var ws = new WebSocket(loc, "broadway");
+       ws.onopen = function() {
+           inputSocket = ws;
+       };
+       ws.onclose = function() {
+           inputSocket = null;
+       };
+    } else {
+       alert("WebSocket not supported, input will not work!");
+    }
+    document.oncontextmenu = function () { return false; };
+    document.onmousemove = onMouseMove;
+    document.onmouseover = onMouseOver;
+    document.onmouseout = onMouseOut;
+    document.onmousedown = onMouseDown;
+    document.onmouseup = onMouseUp;
+    document.onkeydown = onKeyDown;
+    document.onkeyup = onKeyUp;
+
+    if (document.addEventListener) {
+       document.addEventListener('DOMMouseScroll', onMouseWheel, false);
+       document.addEventListener('mousewheel', onMouseWheel, false);
+    } else if (document.attachEvent) {
+       element.attachEvent("onmousewheel", onMouseWheel);
+    }
 }