static const char broadway_js[] =
  "/* Helper functions for debugging */\n"
  "var logDiv = null;\n"
  "function log(str) {\n"
  "    if (!logDiv) {\n"
  "	logDiv = document.createElement('div');\n"
  "	document.body.appendChild(logDiv);\n"
  "	logDiv.style[\"position\"] = \"absolute\";\n"
  "	logDiv.style[\"right\"] = \"0px\";\n"
  "    }\n"
  "    logDiv.appendChild(document.createTextNode(str));\n"
  "    logDiv.appendChild(document.createElement('br'));\n"
  "}\n"
  "\n"
  "function getStackTrace()\n"
  "{\n"
  "    var callstack = [];\n"
  "    var isCallstackPopulated = false;\n"
  "    try {\n"
  "	i.dont.exist+=0;\n"
  "    } catch(e) {\n"
  "	if (e.stack) { // Firefox\n"
  "	    var lines = e.stack.split(\"\\n\");\n"
  "	    for (var i=0, len=lines.length; i<len; i++) {\n"
  "		if (lines[i].match(/^\\s*[A-Za-z0-9\\-_\\$]+\\(/)) {\n"
  "		    callstack.push(lines[i]);\n"
  "		}\n"
  "	    }\n"
  "	    // Remove call to getStackTrace()\n"
  "	    callstack.shift();\n"
  "	    isCallstackPopulated = true;\n"
  "	} else if (window.opera && e.message) { // Opera\n"
  "	    var lines = e.message.split(\"\\n\");\n"
  "	    for (var i=0, len=lines.length; i<len; i++) {\n"
  "		if (lines[i].match(/^\\s*[A-Za-z0-9\\-_\\$]+\\(/)) {\n"
  "		    var entry = lines[i];\n"
  "		    // Append next line also since it has the file info\n"
  "		    if (lines[i+1]) {\n"
  "			entry += \" at \" + lines[i+1];\n"
  "			i++;\n"
  "		    }\n"
  "		    callstack.push(entry);\n"
  "		}\n"
  "	    }\n"
  "	    // Remove call to getStackTrace()\n"
  "	    callstack.shift();\n"
  "	    isCallstackPopulated = true;\n"
  "	}\n"
  "    }\n"
  "    if (!isCallstackPopulated) { //IE and Safari\n"
  "	var currentFunction = arguments.callee.caller;\n"
  "	while (currentFunction) {\n"
  "	    var fn = currentFunction.toString();\n"
  "	    var fname = fn.substring(fn.indexOf(\"function\") + 8, fn.indexOf(\"(\")) || \"anonymous\";\n"
  "	    callstack.push(fname);\n"
  "	    currentFunction = currentFunction.caller;\n"
  "	}\n"
  "    }\n"
  "    return callstack;\n"
  "}\n"
  "\n"
  "function logStackTrace(len) {\n"
  "    var callstack = getStackTrace();\n"
  "    var end = callstack.length;\n"
  "    if (len > 0)\n"
  "	end = Math.min(len + 1, end);\n"
  "    for (var i = 1; i < end; i++)\n"
  "	log(callstack[i]);\n"
  "}\n"
  "\n"
  "var base64Values = [\n"
  "    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,\n"
  "    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,\n"
  "    255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,\n"
  "    52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255,  0,255,255,\n"
  "    255,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,\n"
  "    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,\n"
  "    255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,\n"
  "    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255\n"
  "];\n"
  "\n"
  "function base64_8(str, index) {\n"
  "    var v =\n"
  "	(base64Values[str.charCodeAt(index)]) +\n"
  "	(base64Values[str.charCodeAt(index+1)] << 6);\n"
  "    return v;\n"
  "}\n"
  "\n"
  "function base64_16(str, index) {\n"
  "    var v =\n"
  "	(base64Values[str.charCodeAt(index)]) +\n"
  "	(base64Values[str.charCodeAt(index+1)] << 6) +\n"
  "	(base64Values[str.charCodeAt(index+2)] << 12);\n"
  "    return v;\n"
  "}\n"
  "\n"
  "function base64_16s(str, index) {\n"
  "    var v = base64_16(str, index);\n"
  "    if (v > 32767)\n"
  "	return v - 65536;\n"
  "    else\n"
  "	return v;\n"
  "}\n"
  "\n"
  "function base64_24(str, index) {\n"
  "    var v =\n"
  "	(base64Values[str.charCodeAt(index)]) +\n"
  "	(base64Values[str.charCodeAt(index+1)] << 6) +\n"
  "	(base64Values[str.charCodeAt(index+2)] << 12) +\n"
  "	(base64Values[str.charCodeAt(index+3)] << 18);\n"
  "    return v;\n"
  "}\n"
  "\n"
  "function base64_32(str, index) {\n"
  "    var v =\n"
  "	(base64Values[str.charCodeAt(index)]) +\n"
  "	(base64Values[str.charCodeAt(index+1)] << 6) +\n"
  "	(base64Values[str.charCodeAt(index+2)] << 12) +\n"
  "	(base64Values[str.charCodeAt(index+3)] << 18) +\n"
  "	(base64Values[str.charCodeAt(index+4)] << 24) +\n"
  "	(base64Values[str.charCodeAt(index+5)] << 30);\n"
  "    return v;\n"
  "}\n"
  "\n"
  "function createXHR()\n"
  "{\n"
  "    try { return new XMLHttpRequest(); } catch(e) {}\n"
  "    try { return new ActiveXObject(\"Msxml2.XMLHTTP.6.0\"); } catch (e) {}\n"
  "    try { return new ActiveXObject(\"Msxml2.XMLHTTP.3.0\"); } catch (e) {}\n"
  "    try { return new ActiveXObject(\"Msxml2.XMLHTTP\"); } catch (e) {}\n"
  "    try { return new ActiveXObject(\"Microsoft.XMLHTTP\"); } catch (e) {}\n"
  "\n"
  "    return null;\n"
  "}\n"
  "\n"
  "/* This resizes the window so the *inner* size is the specified size */\n"
  "function resizeBrowserWindow(window, w, h) {\n"
  "    var innerW = window.innerWidth;\n"
  "    var innerH = window.innerHeight;\n"
  "\n"
  "    var outerW = window.outerWidth;\n"
  "    var outerH = window.outerHeight;\n"
  "\n"
  "    window.resizeTo(w + outerW - innerW,\n"
  "		    h + outerH - innerH);\n"
  "}\n"
  "\n"
  "function resizeCanvas(canvas, w, h)\n"
  "{\n"
  "    /* Canvas resize clears the data, so we need to save it first */\n"
  "    var tmpCanvas = canvas.ownerDocument.createElement(\"canvas\");\n"
  "    tmpCanvas.width = canvas.width;\n"
  "    tmpCanvas.height = canvas.height;\n"
  "    var tmpContext = tmpCanvas.getContext(\"2d\");\n"
  "    tmpContext.globalCompositeOperation = \"copy\";\n"
  "    tmpContext.drawImage(canvas, 0, 0, tmpCanvas.width, tmpCanvas.height);\n"
  "\n"
  "    canvas.width = w;\n"
  "    canvas.height = h;\n"
  "\n"
  "    var context = canvas.getContext(\"2d\");\n"
  "\n"
  "    context.globalCompositeOperation = \"copy\";\n"
  "    context.drawImage(tmpCanvas, 0, 0, tmpCanvas.width, tmpCanvas.height);\n"
  "}\n"
  "\n"
  "var useToplevelWindows = false;\n"
  "var toplevelWindows = [];\n"
  "var grab = new Object();\n"
  "grab.window = null;\n"
  "grab.ownerEvents = false;\n"
  "grab.implicit = false;\n"
  "var localGrab = null;\n"
  "var keyDownList = [];\n"
  "var lastSerial = 0;\n"
  "var lastX = 0;\n"
  "var lastY = 0;\n"
  "var lastState;\n"
  "var lastTimeStamp = 0;\n"
  "var realWindowWithMouse = 0;\n"
  "var windowWithMouse = 0;\n"
  "var surfaces = {};\n"
  "var stackingOrder = [];\n"
  "var outstandingCommands = new Array();\n"
  "var inputSocket = null;\n"
  "\n"
  "var GDK_CROSSING_NORMAL = 0;\n"
  "var GDK_CROSSING_GRAB = 1;\n"
  "var GDK_CROSSING_UNGRAB = 2;\n"
  "\n"
  "// GdkModifierType\n"
  "var GDK_SHIFT_MASK = 1 << 0;\n"
  "var GDK_LOCK_MASK     = 1 << 1;\n"
  "var GDK_CONTROL_MASK  = 1 << 2;\n"
  "var GDK_MOD1_MASK     = 1 << 3;\n"
  "var GDK_MOD2_MASK     = 1 << 4;\n"
  "var GDK_MOD3_MASK     = 1 << 5;\n"
  "var GDK_MOD4_MASK     = 1 << 6;\n"
  "var GDK_MOD5_MASK     = 1 << 7;\n"
  "var GDK_BUTTON1_MASK  = 1 << 8;\n"
  "var GDK_BUTTON2_MASK  = 1 << 9;\n"
  "var GDK_BUTTON3_MASK  = 1 << 10;\n"
  "var GDK_BUTTON4_MASK  = 1 << 11;\n"
  "var GDK_BUTTON5_MASK  = 1 << 12;\n"
  "var GDK_SUPER_MASK    = 1 << 26;\n"
  "var GDK_HYPER_MASK    = 1 << 27;\n"
  "var GDK_META_MASK     = 1 << 28;\n"
  "var GDK_RELEASE_MASK  = 1 << 30;\n"
  "\n"
  "function getButtonMask (button) {\n"
  "    if (button == 1)\n"
  "	return GDK_BUTTON1_MASK;\n"
  "    if (button == 2)\n"
  "	return GDK_BUTTON2_MASK;\n"
  "    if (button == 3)\n"
  "	return GDK_BUTTON3_MASK;\n"
  "    if (button == 4)\n"
  "	return GDK_BUTTON4_MASK;\n"
  "    if (button == 5)\n"
  "	return GDK_BUTTON5_MASK;\n"
  "    return 0;\n"
  "}\n"
  "\n"
  "function flushSurface(surface)\n"
  "{\n"
  "    var commands = surface.drawQueue;\n"
  "    surface.queue = [];\n"
  "    var context = surface.canvas.getContext(\"2d\");\n"
  "    context.globalCompositeOperation = \"source-over\";\n"
  "    var i = 0;\n"
  "    for (i = 0; i < commands.length; i++) {\n"
  "	var cmd = commands[i];\n"
  "	switch (cmd.op) {\n"
  "	case 'i': // put image data surface\n"
  "	    context.globalCompositeOperation = \"source-over\";\n"
  "	    context.drawImage(cmd.img, cmd.x, cmd.y);\n"
  "	    break;\n"
  "\n"
  "	case 'b': // copy rects\n"
  "	    context.save();\n"
  "	    context.beginPath();\n"
  "\n"
  "	    var minx;\n"
  "	    var miny;\n"
  "	    var maxx;\n"
  "	    var maxy;\n"
  "	    for (var j = 0; j < cmd.rects.length; j++) {\n"
  "		var rect = cmd.rects[j];\n"
  "		context.rect(rect.x, rect.y, rect.w, rect.h);\n"
  "		if (j == 0) {\n"
  "		    minx = rect.x;\n"
  "		    miny = rect.y;\n"
  "		    maxx = rect.x + rect.w;\n"
  "		    maxy = rect.y + rect.h;\n"
  "		} else {\n"
  "		    if (rect.x < minx)\n"
  "			minx = rect.x;\n"
  "		    if (rect.y < miny)\n"
  "			miny = rect.y;\n"
  "		    if (rect.x + rect.w > maxx)\n"
  "			maxx = rect.x + rect.w;\n"
  "		    if (rect.y + rect.h > maxy)\n"
  "			maxy = rect.y + rect.h;\n"
  "		}\n"
  "	    }\n"
  "	    context.clip();\n"
  "	    context.globalCompositeOperation = \"copy\";\n"
  "	    context.drawImage(context.canvas,\n"
  "			      minx - cmd.dx, miny - cmd.dy, maxx - minx, maxy - miny,\n"
  "			      minx, miny, maxx - minx, maxy - miny);\n"
  "	    context.restore();\n"
  "	    break;\n"
  "\n"
  "	default:\n"
  "	    alert(\"Unknown drawing op \" + cmd.op);\n"
  "	}\n"
  "    }\n"
  "}\n"
  "\n"
  "function ensureSurfaceInDocument(surface, doc)\n"
  "{\n"
  "    if (surface.document != doc) {\n"
  "	var oldCanvas = surface.canvas;\n"
  "	var canvas = doc.importNode(oldCanvas, false);\n"
  "	doc.body.appendChild(canvas);\n"
  "	canvas.surface = surface;\n"
  "	oldCanvas.parentNode.removeChild(oldCanvas);\n"
  "\n"
  "	surface.canvas = canvas;\n"
  "	if (surface.toplevelElement == oldCanvas)\n"
  "	    surface.toplevelElement = canvas;\n"
  "	surface.document = doc;\n"
  "    }\n"
  "}\n"
  "\n"
  "function sendConfigureNotify(surface)\n"
  "{\n"
  "    sendInput(\"w\", [surface.id, surface.x, surface.y, surface.width, surface.height]);\n"
  "}\n"
  "\n"
  "var windowGeometryTimeout = null;\n"
  "\n"
  "function updateBrowserWindowGeometry(win, alwaysSendConfigure) {\n"
  "    if (win.closed)\n"
  "	return;\n"
  "\n"
  "    var surface = win.surface;\n"
  "\n"
  "    var innerW = win.innerWidth;\n"
  "    var innerH = win.innerHeight;\n"
  "\n"
  "    var x = surface.x;\n"
  "    var y = surface.y;\n"
  "\n"
  "    if (win.mozInnerScreenX != undefined) {\n"
  "	x = win.mozInnerScreenX;\n"
  "	y = win.mozInnerScreenY;\n"
  "    } else if (win.screenTop != undefined) {\n"
  "	x = win.screenTop;\n"
  "	y = win.screenLeft;\n"
  "    } else {\n"
  "	alert(\"No implementation to get window position\");\n"
  "    }\n"
  "\n"
  "    if (alwaysSendConfigure || x != surface.x || y != surface.y ||\n"
  "       innerW != surface.width || innerH != surface.height) {\n"
  "	var oldX = surface.x;\n"
  "	var oldY = surface.y;\n"
  "	surface.x = x;\n"
  "	surface.y = y;\n"
  "	if (surface.width != innerW || surface.height != innerH)\n"
  "	    resizeCanvas(surface.canvas, innerW, innerH);\n"
  "	surface.width = innerW;\n"
  "	surface.height = innerH;\n"
  "	sendConfigureNotify(surface);\n"
  "	for (id in surfaces) {\n"
  "	    var childSurface = surfaces[id];\n"
  "	    var transientToplevel = getTransientToplevel(childSurface);\n"
  "	    if (transientToplevel != null && transientToplevel == surface) {\n"
  "		childSurface.x += surface.x - oldX;\n"
  "		childSurface.y += surface.y - oldY;\n"
  "		sendConfigureNotify(childSurface);\n"
  "	    }\n"
  "	}\n"
  "    }\n"
  "}\n"
  "\n"
  "function browserWindowClosed(win) {\n"
  "    var surface = win.surface;\n"
  "\n"
  "    sendInput (\"W\", [surface.id]);\n"
  "    for (id in surfaces) {\n"
  "	var childSurface = surfaces[id];\n"
  "	var transientToplevel = getTransientToplevel(childSurface);\n"
  "	if (transientToplevel != null && transientToplevel == surface) {\n"
  "	    sendInput (\"W\", [childSurface.id]);\n"
  "	}\n"
  "    }\n"
  "}\n"
  "\n"
  "function registerWindow(win)\n"
  "{\n"
  "    toplevelWindows.push(win);\n"
  "    win.onresize = function(ev) { updateBrowserWindowGeometry(ev.target, false); };\n"
  "    if (!windowGeometryTimeout)\n"
  "	windowGeometryTimeout = setInterval(function () {\n"
  "						for (var i = 0; i < toplevelWindows.length; i++)\n"
  "						    updateBrowserWindowGeometry(toplevelWindows[i], false);\n"
  "					    }, 2000);\n"
  "    win.onunload = function(ev) { browserWindowClosed(ev.target.defaultView); };\n"
  "}\n"
  "\n"
  "function unregisterWindow(win)\n"
  "{\n"
  "    var i = toplevelWindows.indexOf(win);\n"
  "    if (i >= 0)\n"
  "	toplevelWindows.splice(i, 1);\n"
  "\n"
  "    if (windowGeometryTimeout && toplevelWindows.length == 0) {\n"
  "	clearInterval(windowGeometryTimeout);\n"
  "	windowGeometryTimeout = null;\n"
  "    }\n"
  "}\n"
  "\n"
  "function getTransientToplevel(surface)\n"
  "{\n"
  "    while (surface && surface.transientParent != 0) {\n"
  "	surface = surfaces[surface.transientParent];\n"
  "	if (surface && surface.window)\n"
  "	    return surface;\n"
  "    }\n"
  "    return null;\n"
  "}\n"
  "\n"
  "function getStyle(el, styleProp)\n"
  "{\n"
  "    if (el.currentStyle) {\n"
  "	return el.currentStyle[styleProp];\n"
  "    }  else if (window.getComputedStyle) {\n"
  "	var win = el.ownerDocument.defaultView;\n"
  "	return win.getComputedStyle(el, null).getPropertyValue(styleProp);\n"
  "    }\n"
  "    return undefined;\n"
  "}\n"
  "\n"
  "function parseOffset(value)\n"
  "{\n"
  "    var px = value.indexOf(\"px\");\n"
  "    if (px > 0)\n"
  "	return parseInt(value.slice(0,px));\n"
  "    return 0;\n"
  "}\n"
  "\n"
  "function getFrameOffset(surface) {\n"
  "    var x = 0;\n"
  "    var y = 0;\n"
  "    var el = surface.canvas;\n"
  "    while (el != null && el != surface.frame) {\n"
  "	x += el.offsetLeft;\n"
  "	y += el.offsetTop;\n"
  "\n"
  "	/* For some reason the border is not includes in the offsets.. */\n"
  "	x += parseOffset(getStyle(el, \"border-left-width\"));\n"
  "	y += parseOffset(getStyle(el, \"border-top-width\"));\n"
  "\n"
  "	el = el.offsetParent;\n"
  "    }\n"
  "\n"
  "    /* Also include frame border as per above */\n"
  "    x += parseOffset(getStyle(el, \"border-left-width\"));\n"
  "    y += parseOffset(getStyle(el, \"border-top-width\"));\n"
  "\n"
  "    return {x: x, y: y};\n"
  "}\n"
  "\n"
  "var positionIndex = 0;\n"
  "function cmdCreateSurface(id, x, y, width, height, isTemp)\n"
  "{\n"
  "    var surface = { id: id, x: x, y:y, width: width, height: height, isTemp: isTemp };\n"
  "    surface.positioned = isTemp;\n"
  "    surface.drawQueue = [];\n"
  "    surface.transientParent = 0;\n"
  "    surface.visible = false;\n"
  "    surface.window = null;\n"
  "    surface.document = document;\n"
  "    surface.frame = null;\n"
  "\n"
  "    var canvas = document.createElement(\"canvas\");\n"
  "    canvas.width = width;\n"
  "    canvas.height = height;\n"
  "    canvas.surface = surface;\n"
  "    surface.canvas = canvas;\n"
  "    var toplevelElement;\n"
  "\n"
  "    if (useToplevelWindows || isTemp) {\n"
  "	toplevelElement = canvas;\n"
  "	document.body.appendChild(canvas);\n"
  "    } else {\n"
  "	var frame = document.createElement(\"div\");\n"
  "	frame.frameFor = surface;\n"
  "	frame.className = \"frame-window\";\n"
  "	surface.frame = frame;\n"
  "\n"
  "	var button = document.createElement(\"center\");\n"
  "	button.closeFor = surface;\n"
  "	var X = document.createTextNode(\"\\u00d7\");\n"
  "	button.appendChild(X);\n"
  "	button.className = \"frame-close\";\n"
  "	frame.appendChild(button);\n"
  "\n"
  "	var contents = document.createElement(\"div\");\n"
  "	contents.className = \"frame-contents\";\n"
  "	frame.appendChild(contents);\n"
  "\n"
  "	canvas.style[\"display\"] = \"block\";\n"
  "	contents.appendChild(canvas);\n"
  "\n"
  "	toplevelElement = frame;\n"
  "	document.body.appendChild(frame);\n"
  "\n"
  "	surface.x = 100 + positionIndex * 10;\n"
  "	surface.y = 100 + positionIndex * 10;\n"
  "	positionIndex = (positionIndex + 1) % 20;\n"
  "    }\n"
  "\n"
  "    surface.toplevelElement = toplevelElement;\n"
  "    toplevelElement.style[\"position\"] = \"absolute\";\n"
  "    /* This positioning isn't strictly right for apps in another topwindow,\n"
  "     * but that will be fixed up when showing. */\n"
  "    toplevelElement.style[\"left\"] = surface.x + \"px\";\n"
  "    toplevelElement.style[\"top\"] = surface.y + \"px\";\n"
  "    toplevelElement.style[\"display\"] = \"inline\";\n"
  "\n"
  "    /* We hide the frame with visibility rather than display none\n"
  "     * so getFrameOffset still works with hidden windows. */\n"
  "    toplevelElement.style[\"visibility\"] = \"hidden\";\n"
  "\n"
  "    surfaces[id] = surface;\n"
  "    stackingOrder.push(surface);\n"
  "\n"
  "    sendConfigureNotify(surface);\n"
  "}\n"
  "\n"
  "function cmdShowSurface(id)\n"
  "{\n"
  "    var surface = surfaces[id];\n"
  "\n"
  "    if (surface.visible)\n"
  "	return;\n"
  "    surface.visible = true;\n"
  "\n"
  "    var xOffset = surface.x;\n"
  "    var yOffset = surface.y;\n"
  "\n"
  "    if (useToplevelWindows) {\n"
  "	var doc = document;\n"
  "	if (!surface.isTemp) {\n"
  "	    var options =\n"
  "		'width='+surface.width+',height='+surface.height+\n"
  "		',location=no,menubar=no,scrollbars=no,toolbar=no';\n"
  "	    if (surface.positioned)\n"
  "		options = options +\n"
  "		',left='+surface.x+',top='+surface.y+',screenX='+surface.x+',screenY='+surface.y;\n"
  "	    var win = window.open('','_blank', options);\n"
  "	    win.surface = surface;\n"
  "	    registerWindow(win);\n"
  "	    doc = win.document;\n"
  "	    doc.open();\n"
  "	    doc.write(\"<body></body>\");\n"
  "	    setupDocument(doc);\n"
  "\n"
  "	    surface.window = win;\n"
  "	    xOffset = 0;\n"
  "	    yOffset = 0;\n"
  "	} else {\n"
  "	    var transientToplevel = getTransientToplevel(surface);\n"
  "	    if (transientToplevel) {\n"
  "		doc = transientToplevel.window.document;\n"
  "		xOffset = surface.x - transientToplevel.x;\n"
  "		yOffset = surface.y - transientToplevel.y;\n"
  "	    }\n"
  "	}\n"
  "\n"
  "	ensureSurfaceInDocument(surface, doc);\n"
  "    } else {\n"
  "	if (surface.frame) {\n"
  "	    var offset = getFrameOffset(surface);\n"
  "	    xOffset -= offset.x;\n"
  "	    yOffset -= offset.y;\n"
  "	}\n"
  "    }\n"
  "\n"
  "    surface.toplevelElement.style[\"left\"] = xOffset + \"px\";\n"
  "    surface.toplevelElement.style[\"top\"] = yOffset + \"px\";\n"
  "    surface.toplevelElement.style[\"visibility\"] = \"visible\";\n"
  "\n"
  "    restackWindows();\n"
  "\n"
  "    if (surface.window)\n"
  "	updateBrowserWindowGeometry(surface.window, false);\n"
  "}\n"
  "\n"
  "function cmdHideSurface(id)\n"
  "{\n"
  "    if (grab.window == id)\n"
  "	doUngrab();\n"
  "\n"
  "    var surface = surfaces[id];\n"
  "\n"
  "    if (!surface.visible)\n"
  "	return;\n"
  "    surface.visible = false;\n"
  "\n"
  "    var element = surface.toplevelElement;\n"
  "\n"
  "    element.style[\"visibility\"] = \"hidden\";\n"
  "\n"
  "    // Import the canvas into the main document\n"
  "    ensureSurfaceInDocument(surface, document);\n"
  "\n"
  "    if (surface.window) {\n"
  "	unregisterWindow(surface.window);\n"
  "	surface.window.close();\n"
  "	surface.window = null;\n"
  "    }\n"
  "}\n"
  "\n"
  "function cmdSetTransientFor(id, parentId)\n"
  "{\n"
  "    var surface = surfaces[id];\n"
  "\n"
  "    if (surface.transientParent == parentId)\n"
  "	return;\n"
  "\n"
  "    surface.transientParent = parentId;\n"
  "    if (parentId != 0 && surfaces[parentId]) {\n"
  "	moveToHelper(surface, stackingOrder.indexOf(surfaces[parentId])+1);\n"
  "    }\n"
  "\n"
  "    if (surface.visible) {\n"
  "	restackWindows();\n"
  "    }\n"
  "}\n"
  "\n"
  "function restackWindows() {\n"
  "    for (var i = 0; i < stackingOrder.length; i++) {\n"
  "	var surface = stackingOrder[i];\n"
  "	surface.toplevelElement.style.zIndex = i;\n"
  "    }\n"
  "}\n"
  "\n"
  "function moveToHelper(surface, position) {\n"
  "    var i = stackingOrder.indexOf(surface);\n"
  "    stackingOrder.splice(i, 1);\n"
  "    if (position != undefined)\n"
  "	stackingOrder.splice(position, 0, surface);\n"
  "    else\n"
  "	stackingOrder.push(surface);\n"
  "\n"
  "    for (var cid in surfaces) {\n"
  "	var child = surfaces[cid];\n"
  "	if (child.transientParent == surface.id)\n"
  "	    moveToHelper(child, stackingOrder.indexOf(surface) + 1);\n"
  "    }\n"
  "}\n"
  "\n"
  "function moveToTop(surface) {\n"
  "    moveToHelper(surface);\n"
  "    restackWindows();\n"
  "}\n"
  "\n"
  "\n"
  "function cmdDeleteSurface(id)\n"
  "{\n"
  "    if (grab.window == id)\n"
  "	doUngrab();\n"
  "\n"
  "    var surface = surfaces[id];\n"
  "    var i = stackingOrder.indexOf(surface);\n"
  "    if (i >= 0)\n"
  "	stackingOrder.splice(i, 1);\n"
  "    var canvas = surface.canvas;\n"
  "    canvas.parentNode.removeChild(canvas);\n"
  "    var frame = surface.frame;\n"
  "    if (frame)\n"
  "	frame.parentNode.removeChild(frame);\n"
  "    delete surfaces[id];\n"
  "}\n"
  "\n"
  "function cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h)\n"
  "{\n"
  "    var surface = surfaces[id];\n"
  "    if (has_pos) {\n"
  "	surface.positioned = true;\n"
  "	surface.x = x;\n"
  "	surface.y = y;\n"
  "    }\n"
  "    if (has_size) {\n"
  "	surface.width = w;\n"
  "	surface.height = h;\n"
  "    }\n"
  "\n"
  "    /* Flush any outstanding draw ops before (possibly) changing size */\n"
  "    flushSurface(surface);\n"
  "\n"
  "    if (has_size)\n"
  "	resizeCanvas(surface.canvas, w, h);\n"
  "\n"
  "    if (surface.visible) {\n"
  "	if (surface.window) {\n"
  "	    /* TODO: This moves the outer frame position, we really want the inner position.\n"
  "	     * However this isn't *strictly* invalid, as any WM could have done whatever it\n"
  "	     * wanted with the positioning of the window.\n"
  "	     */\n"
  "	    if (has_pos)\n"
  "		surface.window.moveTo(surface.x, surface.y);\n"
  "	    if (has_size)\n"
  "		resizeBrowserWindow(surface.window, w, h);\n"
  "	} else {\n"
  "	    if (has_pos) {\n"
  "		var xOffset = surface.x;\n"
  "		var yOffset = surface.y;\n"
  "\n"
  "		var transientToplevel = getTransientToplevel(surface);\n"
  "		if (transientToplevel) {\n"
  "		    xOffset = surface.x - transientToplevel.x;\n"
  "		    yOffset = surface.y - transientToplevel.y;\n"
  "		}\n"
  "\n"
  "		var element = surface.canvas;\n"
  "		if (surface.frame) {\n"
  "		    element = surface.frame;\n"
  "		    var offset = getFrameOffset(surface);\n"
  "		    xOffset -= offset.x;\n"
  "		    yOffset -= offset.y;\n"
  "		}\n"
  "\n"
  "		element.style[\"left\"] = xOffset + \"px\";\n"
  "		element.style[\"top\"] = yOffset + \"px\";\n"
  "	    }\n"
  "	}\n"
  "    }\n"
  "\n"
  "    if (surface.window) {\n"
  "	updateBrowserWindowGeometry(surface.window, true);\n"
  "    } else {\n"
  "	sendConfigureNotify(surface);\n"
  "    }\n"
  "}\n"
  "\n"
  "function cmdFlushSurface(id)\n"
  "{\n"
  "    flushSurface(surfaces[id]);\n"
  "}\n"
  "\n"
  "function cmdGrabPointer(id, ownerEvents)\n"
  "{\n"
  "    doGrab(id, ownerEvents, false);\n"
  "    sendInput (\"g\", []);\n"
  "}\n"
  "\n"
  "function cmdUngrabPointer()\n"
  "{\n"
  "    sendInput (\"u\", []);\n"
  "    if (grab.window)\n"
  "	doUngrab();\n"
  "}\n"
  "\n"
  "function handleCommands(cmdObj)\n"
  "{\n"
  "    var cmd = cmdObj.data;\n"
  "    var i = cmdObj.pos;\n"
  "\n"
  "    while (i < cmd.length) {\n"
  "	var id, x, y, w, h, q;\n"
  "	var command = cmd[i++];\n"
  "	lastSerial = base64_32(cmd, i);\n"
  "	i = i + 6;\n"
  "	switch (command) {\n"
  "	case 's': // create new surface\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    x = base64_16s(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    y = base64_16s(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    w = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    h = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    var isTemp = cmd[i] == '1';\n"
  "	    i = i + 1;\n"
  "	    cmdCreateSurface(id, x, y, w, h, isTemp);\n"
  "	    break;\n"
  "\n"
  "	case 'S': // Show a surface\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    cmdShowSurface(id);\n"
  "	    break;\n"
  "\n"
  "	case 'H': // Hide a surface\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    cmdHideSurface(id);\n"
  "	    break;\n"
  "\n"
  "	case 'p': // Set transient parent\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    var parentId = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    cmdSetTransientFor(id, parentId);\n"
  "	    break;\n"
  "\n"
  "	case 'd': // Delete surface\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    cmdDeleteSurface(id);\n"
  "	    break;\n"
  "\n"
  "	case 'm': // Move a surface\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    var ops = cmd.charCodeAt(i++) - 48;\n"
  "	    var has_pos = ops & 1;\n"
  "	    if (has_pos) {\n"
  "		x = base64_16s(cmd, i);\n"
  "		i = i + 3;\n"
  "		y = base64_16s(cmd, i);\n"
  "		i = i + 3;\n"
  "	    }\n"
  "	    var has_size = ops & 2;\n"
  "	    if (has_size) {\n"
  "		w = base64_16(cmd, i);\n"
  "		i = i + 3;\n"
  "		h = base64_16(cmd, i);\n"
  "		i = i + 3;\n"
  "	    }\n"
  "	    cmdMoveResizeSurface(id, has_pos, x, y, has_size, w, h);\n"
  "	    break;\n"
  "\n"
  "	case 'i': // Put image data surface\n"
  "	    q = new Object();\n"
  "	    q.op = 'i';\n"
  "	    q.id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    q.x = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    q.y = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    var size = base64_32(cmd, i);\n"
  "	    i = i + 6;\n"
  "	    var url = cmd.slice(i, i + size);\n"
  "	    i = i + size;\n"
  "	    q.img = new Image();\n"
  "	    q.img.src = url;\n"
  "	    surfaces[q.id].drawQueue.push(q);\n"
  "	    if (!q.img.complete) {\n"
  "		cmdObj.pos = i;\n"
  "		q.img.onload = function() { handleOutstanding(); };\n"
  "		return false;\n"
  "	    }\n"
  "	    break;\n"
  "\n"
  "	case 'b': // Copy rects\n"
  "	    q = new Object();\n"
  "	    q.op = 'b';\n"
  "	    q.id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "\n"
  "	    var nrects = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "\n"
  "	    q.rects = [];\n"
  "	    for (var r = 0; r < nrects; r++) {\n"
  "		var rect = new Object();\n"
  "		rect.x = base64_16(cmd, i);\n"
  "		i = i + 3;\n"
  "		rect.y = base64_16(cmd, i);\n"
  "		i = i + 3;\n"
  "		rect.w = base64_16(cmd, i);\n"
  "		i = i + 3;\n"
  "		rect.h = base64_16(cmd, i);\n"
  "		i = i + 3;\n"
  "		q.rects.push (rect);\n"
  "	    }\n"
  "\n"
  "	    q.dx = base64_16s(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    q.dy = base64_16s(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    surfaces[q.id].drawQueue.push(q);\n"
  "	    break;\n"
  "\n"
  "	case 'f': // Flush surface\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "\n"
  "	    cmdFlushSurface(id);\n"
  "	    break;\n"
  "\n"
  "	case 'g': // Grab\n"
  "	    id = base64_16(cmd, i);\n"
  "	    i = i + 3;\n"
  "	    var ownerEvents = cmd[i++] == '1';\n"
  "\n"
  "	    cmdGrabPointer(id, ownerEvents);\n"
  "	    break;\n"
  "\n"
  "	case 'u': // Ungrab\n"
  "	    cmdUngrabPointer();\n"
  "	    break;\n"
  "	default:\n"
  "	    alert(\"Unknown op \" + command);\n"
  "	}\n"
  "    }\n"
  "    return true;\n"
  "}\n"
  "\n"
  "function handleOutstanding()\n"
  "{\n"
  "    while (outstandingCommands.length > 0) {\n"
  "	var cmd = outstandingCommands.shift();\n"
  "	if (!handleCommands(cmd)) {\n"
  "	    outstandingCommands.unshift(cmd);\n"
  "	    return;\n"
  "	}\n"
  "    }\n"
  "}\n"
  "\n"
  "function handleMessage(message)\n"
  "{\n"
  "    var cmdObj = {};\n"
  "    cmdObj.data = message;\n"
  "    cmdObj.pos = 0;\n"
  "\n"
  "    outstandingCommands.push(cmdObj);\n"
  "    if (outstandingCommands.length == 1) {\n"
  "	handleOutstanding();\n"
  "    }\n"
  "}\n"
  "\n"
  "function getSurfaceId(ev) {\n"
  "    var surface = ev.target.surface;\n"
  "    if (surface != undefined)\n"
  "	return surface.id;\n"
  "    return 0;\n"
  "}\n"
  "\n"
  "function sendInput(cmd, args)\n"
  "{\n"
  "    if (inputSocket != null) {\n"
  "	inputSocket.send(cmd + ([lastSerial, lastTimeStamp].concat(args)).join(\",\"));\n"
  "    }\n"
  "}\n"
  "\n"
  "function getPositionsFromAbsCoord(absX, absY, relativeId) {\n"
  "    var res = Object();\n"
  "\n"
  "    res.rootX = absX;\n"
  "    res.rootY = absY;\n"
  "    res.winX = absX;\n"
  "    res.winY = absY;\n"
  "    if (relativeId != 0) {\n"
  "	var surface = surfaces[relativeId];\n"
  "	res.winX = res.winX - surface.x;\n"
  "	res.winY = res.winY - surface.y;\n"
  "    }\n"
  "\n"
  "    return res;\n"
  "}\n"
  "\n"
  "function getPositionsFromEvent(ev, relativeId) {\n"
  "    var absX, absY;\n"
  "    if (useToplevelWindows) {\n"
  "	absX = ev.screenX;\n"
  "	absY = ev.screenY;\n"
  "    } else {\n"
  "	absX = ev.pageX;\n"
  "	absY = ev.pageY;\n"
  "    }\n"
  "    var res = getPositionsFromAbsCoord(absX, absY, relativeId);\n"
  "\n"
  "    lastX = res.rootX;\n"
  "    lastY = res.rootY;\n"
  "\n"
  "    return res;\n"
  "}\n"
  "\n"
  "function getEffectiveEventTarget (id) {\n"
  "    if (grab.window != null) {\n"
  "	if (!grab.ownerEvents)\n"
  "	    return grab.window;\n"
  "	if (id == 0)\n"
  "	    return grab.window;\n"
  "    }\n"
  "    return id;\n"
  "}\n"
  "\n"
  "function updateForEvent(ev) {\n"
  "    lastState &= ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK|GDK_MOD1_MASK);\n"
  "    if (ev.shiftKey)\n"
  "	lastState |= GDK_SHIFT_MASK;\n"
  "    if (ev.ctrlKey)\n"
  "	lastState |= GDK_CONTROL_MASK;\n"
  "    if (ev.altKey)\n"
  "	lastState |= GDK_MOD1_MASK;\n"
  "\n"
  "    lastTimeStamp = ev.timeStamp;\n"
  "    if (ev.target.surface && ev.target.surface.window) {\n"
  "	var win = ev.target.surface.window;\n"
  "	updateBrowserWindowGeometry(win, false);\n"
  "    }\n"
  "}\n"
  "\n"
  "function onMouseMove (ev) {\n"
  "    updateForEvent(ev);\n"
  "    if (localGrab) {\n"
  "	if (localGrab.type == \"move\") {\n"
  "	    var dx = ev.pageX - localGrab.lastX;\n"
  "	    var dy = ev.pageY - localGrab.lastY;\n"
  "	    var surface = localGrab.surface;\n"
  "	    surface.x += dx;\n"
  "	    surface.y += dy;\n"
  "	    var offset = getFrameOffset(surface);\n"
  "	    localGrab.frame.style[\"left\"] = (surface.x - offset.x) + \"px\";\n"
  "	    localGrab.frame.style[\"top\"] = (surface.y - offset.y) + \"px\";\n"
  "	    sendConfigureNotify(surface);\n"
  "	    localGrab.lastX = ev.pageX;\n"
  "	    localGrab.lastY = ev.pageY;\n"
  "	}\n"
  "	return;\n"
  "    }\n"
  "    var id = getSurfaceId(ev);\n"
  "    id = getEffectiveEventTarget (id);\n"
  "    var pos = getPositionsFromEvent(ev, id);\n"
  "    sendInput (\"m\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState]);\n"
  "}\n"
  "\n"
  "function onMouseOver (ev) {\n"
  "    updateForEvent(ev);\n"
  "\n"
  "    if (!grab.window && ev.target.closeFor) {\n"
  "	ev.target.className = ev.target.className + \" frame-hover\";\n"
  "	if (ev.target.isDown)\n"
  "	    ev.target.className = ev.target.className + \" frame-active\";\n"
  "    }\n"
  "\n"
  "    if (localGrab)\n"
  "	return;\n"
  "    var id = getSurfaceId(ev);\n"
  "    realWindowWithMouse = id;\n"
  "    id = getEffectiveEventTarget (id);\n"
  "    var pos = getPositionsFromEvent(ev, id);\n"
  "    windowWithMouse = id;\n"
  "    if (windowWithMouse != 0) {\n"
  "	sendInput (\"e\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);\n"
  "    }\n"
  "}\n"
  "\n"
  "function onMouseOut (ev) {\n"
  "    updateForEvent(ev);\n"
  "    if (ev.target.closeFor) {\n"
  "	ev.target.className = ev.target.className.replace(\" frame-hover\", \"\");\n"
  "	if (ev.target.isDown)\n"
  "	    ev.target.className = ev.target.className.replace(\" frame-active\", \"\");\n"
  "    }\n"
  "    if (localGrab)\n"
  "	return;\n"
  "    var id = getSurfaceId(ev);\n"
  "    var origId = id;\n"
  "    id = getEffectiveEventTarget (id);\n"
  "    var pos = getPositionsFromEvent(ev, id);\n"
  "\n"
  "    if (id != 0) {\n"
  "	sendInput (\"l\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);\n"
  "    }\n"
  "    realWindowWithMouse = 0;\n"
  "    windowWithMouse = 0;\n"
  "}\n"
  "\n"
  "function doGrab(id, ownerEvents, implicit) {\n"
  "    var pos;\n"
  "\n"
  "    if (windowWithMouse != id) {\n"
  "	if (windowWithMouse != 0) {\n"
  "	    pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);\n"
  "	    sendInput (\"l\", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);\n"
  "	}\n"
  "	pos = getPositionsFromAbsCoord(lastX, lastY, id);\n"
  "	sendInput (\"e\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_GRAB]);\n"
  "	windowWithMouse = id;\n"
  "    }\n"
  "\n"
  "    grab.window = id;\n"
  "    grab.ownerEvents = ownerEvents;\n"
  "    grab.implicit = implicit;\n"
  "}\n"
  "\n"
  "function doUngrab() {\n"
  "    var pos;\n"
  "    if (realWindowWithMouse != windowWithMouse) {\n"
  "	if (windowWithMouse != 0) {\n"
  "	    pos = getPositionsFromAbsCoord(lastX, lastY, windowWithMouse);\n"
  "	    sendInput (\"l\", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);\n"
  "	}\n"
  "	if (realWindowWithMouse != 0) {\n"
  "	    pos = getPositionsFromAbsCoord(lastX, lastY, realWindowWithMouse);\n"
  "	    sendInput (\"e\", [realWindowWithMouse, realWindowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_UNGRAB]);\n"
  "	}\n"
  "	windowWithMouse = realWindowWithMouse;\n"
  "    }\n"
  "    grab.window = null;\n"
  "}\n"
  "\n"
  "function onMouseDown (ev) {\n"
  "    updateForEvent(ev);\n"
  "    var button = ev.button + 1;\n"
  "    lastState = lastState | getButtonMask (button);\n"
  "    var id = getSurfaceId(ev);\n"
  "    id = getEffectiveEventTarget (id);\n"
  "\n"
  "    if (id == 0 && ev.target.frameFor) { /* mouse click on frame */\n"
  "	localGrab = new Object();\n"
  "	localGrab.surface = ev.target.frameFor;\n"
  "	localGrab.type = \"move\";\n"
  "	localGrab.frame = ev.target;\n"
  "	localGrab.lastX = ev.pageX;\n"
  "	localGrab.lastY = ev.pageY;\n"
  "	moveToTop(localGrab.frame.frameFor);\n"
  "	return;\n"
  "    }\n"
  "\n"
  "    if (id == 0 && ev.target.closeFor) { /* mouse click on frame */\n"
  "	ev.target.isDown = true;\n"
  "	ev.target.className = ev.target.className + \" frame-active\";\n"
  "	localGrab = new Object();\n"
  "	localGrab.surface = ev.target.closeFor;\n"
  "	localGrab.type = \"close\";\n"
  "	localGrab.button = ev.target;\n"
  "	localGrab.lastX = ev.pageX;\n"
  "	localGrab.lastY = ev.pageY;\n"
  "	return;\n"
  "    }\n"
  "\n"
  "    var pos = getPositionsFromEvent(ev, id);\n"
  "    if (grab.window == null)\n"
  "	doGrab (id, false, true);\n"
  "    sendInput (\"b\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);\n"
  "}\n"
  "\n"
  "function onMouseUp (ev) {\n"
  "    updateForEvent(ev);\n"
  "    var button = ev.button + 1;\n"
  "    lastState = lastState & ~getButtonMask (button);\n"
  "    var evId = getSurfaceId(ev);\n"
  "    id = getEffectiveEventTarget (evId);\n"
  "    var pos = getPositionsFromEvent(ev, id);\n"
  "\n"
  "    if (localGrab) {\n"
  "	realWindowWithMouse = evId;\n"
  "	if (windowWithMouse != id) {\n"
  "	    if (windowWithMouse != 0) {\n"
  "		sendInput (\"l\", [realWindowWithMouse, windowWithMouse, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);\n"
  "	    }\n"
  "	    windowWithMouse = id;\n"
  "	    if (windowWithMouse != 0) {\n"
  "		sendInput (\"e\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, GDK_CROSSING_NORMAL]);\n"
  "	    }\n"
  "	}\n"
  "\n"
  "	if (localGrab.type == \"close\") {\n"
  "	    localGrab.button.isDown = false;\n"
  "	    localGrab.button.className = localGrab.button.className.replace( \" frame-active\", \"\");\n"
  "	    if (ev.target == localGrab.button)\n"
  "		sendInput (\"W\", [localGrab.surface.id]);\n"
  "	}\n"
  "	localGrab = null;\n"
  "	return;\n"
  "    }\n"
  "\n"
  "    sendInput (\"B\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, button]);\n"
  "\n"
  "    if (grab.window != null && grab.implicit)\n"
  "	doUngrab();\n"
  "}\n"
  "\n"
  "/* Some of the keyboard handling code is from noVNC and\n"
  " * (c) Joel Martin (github@martintribe.org), used with permission\n"
  " *  Original code at:\n"
  " * https://github.com/kanaka/noVNC/blob/master/include/input.js\n"
  " */\n"
  "\n"
  "var unicodeTable = {\n"
  "    0x0104: 0x01a1,\n"
  "    0x02D8: 0x01a2,\n"
  "    0x0141: 0x01a3,\n"
  "    0x013D: 0x01a5,\n"
  "    0x015A: 0x01a6,\n"
  "    0x0160: 0x01a9,\n"
  "    0x015E: 0x01aa,\n"
  "    0x0164: 0x01ab,\n"
  "    0x0179: 0x01ac,\n"
  "    0x017D: 0x01ae,\n"
  "    0x017B: 0x01af,\n"
  "    0x0105: 0x01b1,\n"
  "    0x02DB: 0x01b2,\n"
  "    0x0142: 0x01b3,\n"
  "    0x013E: 0x01b5,\n"
  "    0x015B: 0x01b6,\n"
  "    0x02C7: 0x01b7,\n"
  "    0x0161: 0x01b9,\n"
  "    0x015F: 0x01ba,\n"
  "    0x0165: 0x01bb,\n"
  "    0x017A: 0x01bc,\n"
  "    0x02DD: 0x01bd,\n"
  "    0x017E: 0x01be,\n"
  "    0x017C: 0x01bf,\n"
  "    0x0154: 0x01c0,\n"
  "    0x0102: 0x01c3,\n"
  "    0x0139: 0x01c5,\n"
  "    0x0106: 0x01c6,\n"
  "    0x010C: 0x01c8,\n"
  "    0x0118: 0x01ca,\n"
  "    0x011A: 0x01cc,\n"
  "    0x010E: 0x01cf,\n"
  "    0x0110: 0x01d0,\n"
  "    0x0143: 0x01d1,\n"
  "    0x0147: 0x01d2,\n"
  "    0x0150: 0x01d5,\n"
  "    0x0158: 0x01d8,\n"
  "    0x016E: 0x01d9,\n"
  "    0x0170: 0x01db,\n"
  "    0x0162: 0x01de,\n"
  "    0x0155: 0x01e0,\n"
  "    0x0103: 0x01e3,\n"
  "    0x013A: 0x01e5,\n"
  "    0x0107: 0x01e6,\n"
  "    0x010D: 0x01e8,\n"
  "    0x0119: 0x01ea,\n"
  "    0x011B: 0x01ec,\n"
  "    0x010F: 0x01ef,\n"
  "    0x0111: 0x01f0,\n"
  "    0x0144: 0x01f1,\n"
  "    0x0148: 0x01f2,\n"
  "    0x0151: 0x01f5,\n"
  "    0x0171: 0x01fb,\n"
  "    0x0159: 0x01f8,\n"
  "    0x016F: 0x01f9,\n"
  "    0x0163: 0x01fe,\n"
  "    0x02D9: 0x01ff,\n"
  "    0x0126: 0x02a1,\n"
  "    0x0124: 0x02a6,\n"
  "    0x0130: 0x02a9,\n"
  "    0x011E: 0x02ab,\n"
  "    0x0134: 0x02ac,\n"
  "    0x0127: 0x02b1,\n"
  "    0x0125: 0x02b6,\n"
  "    0x0131: 0x02b9,\n"
  "    0x011F: 0x02bb,\n"
  "    0x0135: 0x02bc,\n"
  "    0x010A: 0x02c5,\n"
  "    0x0108: 0x02c6,\n"
  "    0x0120: 0x02d5,\n"
  "    0x011C: 0x02d8,\n"
  "    0x016C: 0x02dd,\n"
  "    0x015C: 0x02de,\n"
  "    0x010B: 0x02e5,\n"
  "    0x0109: 0x02e6,\n"
  "    0x0121: 0x02f5,\n"
  "    0x011D: 0x02f8,\n"
  "    0x016D: 0x02fd,\n"
  "    0x015D: 0x02fe,\n"
  "    0x0138: 0x03a2,\n"
  "    0x0156: 0x03a3,\n"
  "    0x0128: 0x03a5,\n"
  "    0x013B: 0x03a6,\n"
  "    0x0112: 0x03aa,\n"
  "    0x0122: 0x03ab,\n"
  "    0x0166: 0x03ac,\n"
  "    0x0157: 0x03b3,\n"
  "    0x0129: 0x03b5,\n"
  "    0x013C: 0x03b6,\n"
  "    0x0113: 0x03ba,\n"
  "    0x0123: 0x03bb,\n"
  "    0x0167: 0x03bc,\n"
  "    0x014A: 0x03bd,\n"
  "    0x014B: 0x03bf,\n"
  "    0x0100: 0x03c0,\n"
  "    0x012E: 0x03c7,\n"
  "    0x0116: 0x03cc,\n"
  "    0x012A: 0x03cf,\n"
  "    0x0145: 0x03d1,\n"
  "    0x014C: 0x03d2,\n"
  "    0x0136: 0x03d3,\n"
  "    0x0172: 0x03d9,\n"
  "    0x0168: 0x03dd,\n"
  "    0x016A: 0x03de,\n"
  "    0x0101: 0x03e0,\n"
  "    0x012F: 0x03e7,\n"
  "    0x0117: 0x03ec,\n"
  "    0x012B: 0x03ef,\n"
  "    0x0146: 0x03f1,\n"
  "    0x014D: 0x03f2,\n"
  "    0x0137: 0x03f3,\n"
  "    0x0173: 0x03f9,\n"
  "    0x0169: 0x03fd,\n"
  "    0x016B: 0x03fe,\n"
  "    0x1E02: 0x1001e02,\n"
  "    0x1E03: 0x1001e03,\n"
  "    0x1E0A: 0x1001e0a,\n"
  "    0x1E80: 0x1001e80,\n"
  "    0x1E82: 0x1001e82,\n"
  "    0x1E0B: 0x1001e0b,\n"
  "    0x1EF2: 0x1001ef2,\n"
  "    0x1E1E: 0x1001e1e,\n"
  "    0x1E1F: 0x1001e1f,\n"
  "    0x1E40: 0x1001e40,\n"
  "    0x1E41: 0x1001e41,\n"
  "    0x1E56: 0x1001e56,\n"
  "    0x1E81: 0x1001e81,\n"
  "    0x1E57: 0x1001e57,\n"
  "    0x1E83: 0x1001e83,\n"
  "    0x1E60: 0x1001e60,\n"
  "    0x1EF3: 0x1001ef3,\n"
  "    0x1E84: 0x1001e84,\n"
  "    0x1E85: 0x1001e85,\n"
  "    0x1E61: 0x1001e61,\n"
  "    0x0174: 0x1000174,\n"
  "    0x1E6A: 0x1001e6a,\n"
  "    0x0176: 0x1000176,\n"
  "    0x0175: 0x1000175,\n"
  "    0x1E6B: 0x1001e6b,\n"
  "    0x0177: 0x1000177,\n"
  "    0x0152: 0x13bc,\n"
  "    0x0153: 0x13bd,\n"
  "    0x0178: 0x13be,\n"
  "    0x203E: 0x047e,\n"
  "    0x3002: 0x04a1,\n"
  "    0x300C: 0x04a2,\n"
  "    0x300D: 0x04a3,\n"
  "    0x3001: 0x04a4,\n"
  "    0x30FB: 0x04a5,\n"
  "    0x30F2: 0x04a6,\n"
  "    0x30A1: 0x04a7,\n"
  "    0x30A3: 0x04a8,\n"
  "    0x30A5: 0x04a9,\n"
  "    0x30A7: 0x04aa,\n"
  "    0x30A9: 0x04ab,\n"
  "    0x30E3: 0x04ac,\n"
  "    0x30E5: 0x04ad,\n"
  "    0x30E7: 0x04ae,\n"
  "    0x30C3: 0x04af,\n"
  "    0x30FC: 0x04b0,\n"
  "    0x30A2: 0x04b1,\n"
  "    0x30A4: 0x04b2,\n"
  "    0x30A6: 0x04b3,\n"
  "    0x30A8: 0x04b4,\n"
  "    0x30AA: 0x04b5,\n"
  "    0x30AB: 0x04b6,\n"
  "    0x30AD: 0x04b7,\n"
  "    0x30AF: 0x04b8,\n"
  "    0x30B1: 0x04b9,\n"
  "    0x30B3: 0x04ba,\n"
  "    0x30B5: 0x04bb,\n"
  "    0x30B7: 0x04bc,\n"
  "    0x30B9: 0x04bd,\n"
  "    0x30BB: 0x04be,\n"
  "    0x30BD: 0x04bf,\n"
  "    0x30BF: 0x04c0,\n"
  "    0x30C1: 0x04c1,\n"
  "    0x30C4: 0x04c2,\n"
  "    0x30C6: 0x04c3,\n"
  "    0x30C8: 0x04c4,\n"
  "    0x30CA: 0x04c5,\n"
  "    0x30CB: 0x04c6,\n"
  "    0x30CC: 0x04c7,\n"
  "    0x30CD: 0x04c8,\n"
  "    0x30CE: 0x04c9,\n"
  "    0x30CF: 0x04ca,\n"
  "    0x30D2: 0x04cb,\n"
  "    0x30D5: 0x04cc,\n"
  "    0x30D8: 0x04cd,\n"
  "    0x30DB: 0x04ce,\n"
  "    0x30DE: 0x04cf,\n"
  "    0x30DF: 0x04d0,\n"
  "    0x30E0: 0x04d1,\n"
  "    0x30E1: 0x04d2,\n"
  "    0x30E2: 0x04d3,\n"
  "    0x30E4: 0x04d4,\n"
  "    0x30E6: 0x04d5,\n"
  "    0x30E8: 0x04d6,\n"
  "    0x30E9: 0x04d7,\n"
  "    0x30EA: 0x04d8,\n"
  "    0x30EB: 0x04d9,\n"
  "    0x30EC: 0x04da,\n"
  "    0x30ED: 0x04db,\n"
  "    0x30EF: 0x04dc,\n"
  "    0x30F3: 0x04dd,\n"
  "    0x309B: 0x04de,\n"
  "    0x309C: 0x04df,\n"
  "    0x06F0: 0x10006f0,\n"
  "    0x06F1: 0x10006f1,\n"
  "    0x06F2: 0x10006f2,\n"
  "    0x06F3: 0x10006f3,\n"
  "    0x06F4: 0x10006f4,\n"
  "    0x06F5: 0x10006f5,\n"
  "    0x06F6: 0x10006f6,\n"
  "    0x06F7: 0x10006f7,\n"
  "    0x06F8: 0x10006f8,\n"
  "    0x06F9: 0x10006f9,\n"
  "    0x066A: 0x100066a,\n"
  "    0x0670: 0x1000670,\n"
  "    0x0679: 0x1000679,\n"
  "    0x067E: 0x100067e,\n"
  "    0x0686: 0x1000686,\n"
  "    0x0688: 0x1000688,\n"
  "    0x0691: 0x1000691,\n"
  "    0x060C: 0x05ac,\n"
  "    0x06D4: 0x10006d4,\n"
  "    0x0660: 0x1000660,\n"
  "    0x0661: 0x1000661,\n"
  "    0x0662: 0x1000662,\n"
  "    0x0663: 0x1000663,\n"
  "    0x0664: 0x1000664,\n"
  "    0x0665: 0x1000665,\n"
  "    0x0666: 0x1000666,\n"
  "    0x0667: 0x1000667,\n"
  "    0x0668: 0x1000668,\n"
  "    0x0669: 0x1000669,\n"
  "    0x061B: 0x05bb,\n"
  "    0x061F: 0x05bf,\n"
  "    0x0621: 0x05c1,\n"
  "    0x0622: 0x05c2,\n"
  "    0x0623: 0x05c3,\n"
  "    0x0624: 0x05c4,\n"
  "    0x0625: 0x05c5,\n"
  "    0x0626: 0x05c6,\n"
  "    0x0627: 0x05c7,\n"
  "    0x0628: 0x05c8,\n"
  "    0x0629: 0x05c9,\n"
  "    0x062A: 0x05ca,\n"
  "    0x062B: 0x05cb,\n"
  "    0x062C: 0x05cc,\n"
  "    0x062D: 0x05cd,\n"
  "    0x062E: 0x05ce,\n"
  "    0x062F: 0x05cf,\n"
  "    0x0630: 0x05d0,\n"
  "    0x0631: 0x05d1,\n"
  "    0x0632: 0x05d2,\n"
  "    0x0633: 0x05d3,\n"
  "    0x0634: 0x05d4,\n"
  "    0x0635: 0x05d5,\n"
  "    0x0636: 0x05d6,\n"
  "    0x0637: 0x05d7,\n"
  "    0x0638: 0x05d8,\n"
  "    0x0639: 0x05d9,\n"
  "    0x063A: 0x05da,\n"
  "    0x0640: 0x05e0,\n"
  "    0x0641: 0x05e1,\n"
  "    0x0642: 0x05e2,\n"
  "    0x0643: 0x05e3,\n"
  "    0x0644: 0x05e4,\n"
  "    0x0645: 0x05e5,\n"
  "    0x0646: 0x05e6,\n"
  "    0x0647: 0x05e7,\n"
  "    0x0648: 0x05e8,\n"
  "    0x0649: 0x05e9,\n"
  "    0x064A: 0x05ea,\n"
  "    0x064B: 0x05eb,\n"
  "    0x064C: 0x05ec,\n"
  "    0x064D: 0x05ed,\n"
  "    0x064E: 0x05ee,\n"
  "    0x064F: 0x05ef,\n"
  "    0x0650: 0x05f0,\n"
  "    0x0651: 0x05f1,\n"
  "    0x0652: 0x05f2,\n"
  "    0x0653: 0x1000653,\n"
  "    0x0654: 0x1000654,\n"
  "    0x0655: 0x1000655,\n"
  "    0x0698: 0x1000698,\n"
  "    0x06A4: 0x10006a4,\n"
  "    0x06A9: 0x10006a9,\n"
  "    0x06AF: 0x10006af,\n"
  "    0x06BA: 0x10006ba,\n"
  "    0x06BE: 0x10006be,\n"
  "    0x06CC: 0x10006cc,\n"
  "    0x06D2: 0x10006d2,\n"
  "    0x06C1: 0x10006c1,\n"
  "    0x0492: 0x1000492,\n"
  "    0x0493: 0x1000493,\n"
  "    0x0496: 0x1000496,\n"
  "    0x0497: 0x1000497,\n"
  "    0x049A: 0x100049a,\n"
  "    0x049B: 0x100049b,\n"
  "    0x049C: 0x100049c,\n"
  "    0x049D: 0x100049d,\n"
  "    0x04A2: 0x10004a2,\n"
  "    0x04A3: 0x10004a3,\n"
  "    0x04AE: 0x10004ae,\n"
  "    0x04AF: 0x10004af,\n"
  "    0x04B0: 0x10004b0,\n"
  "    0x04B1: 0x10004b1,\n"
  "    0x04B2: 0x10004b2,\n"
  "    0x04B3: 0x10004b3,\n"
  "    0x04B6: 0x10004b6,\n"
  "    0x04B7: 0x10004b7,\n"
  "    0x04B8: 0x10004b8,\n"
  "    0x04B9: 0x10004b9,\n"
  "    0x04BA: 0x10004ba,\n"
  "    0x04BB: 0x10004bb,\n"
  "    0x04D8: 0x10004d8,\n"
  "    0x04D9: 0x10004d9,\n"
  "    0x04E2: 0x10004e2,\n"
  "    0x04E3: 0x10004e3,\n"
  "    0x04E8: 0x10004e8,\n"
  "    0x04E9: 0x10004e9,\n"
  "    0x04EE: 0x10004ee,\n"
  "    0x04EF: 0x10004ef,\n"
  "    0x0452: 0x06a1,\n"
  "    0x0453: 0x06a2,\n"
  "    0x0451: 0x06a3,\n"
  "    0x0454: 0x06a4,\n"
  "    0x0455: 0x06a5,\n"
  "    0x0456: 0x06a6,\n"
  "    0x0457: 0x06a7,\n"
  "    0x0458: 0x06a8,\n"
  "    0x0459: 0x06a9,\n"
  "    0x045A: 0x06aa,\n"
  "    0x045B: 0x06ab,\n"
  "    0x045C: 0x06ac,\n"
  "    0x0491: 0x06ad,\n"
  "    0x045E: 0x06ae,\n"
  "    0x045F: 0x06af,\n"
  "    0x2116: 0x06b0,\n"
  "    0x0402: 0x06b1,\n"
  "    0x0403: 0x06b2,\n"
  "    0x0401: 0x06b3,\n"
  "    0x0404: 0x06b4,\n"
  "    0x0405: 0x06b5,\n"
  "    0x0406: 0x06b6,\n"
  "    0x0407: 0x06b7,\n"
  "    0x0408: 0x06b8,\n"
  "    0x0409: 0x06b9,\n"
  "    0x040A: 0x06ba,\n"
  "    0x040B: 0x06bb,\n"
  "    0x040C: 0x06bc,\n"
  "    0x0490: 0x06bd,\n"
  "    0x040E: 0x06be,\n"
  "    0x040F: 0x06bf,\n"
  "    0x044E: 0x06c0,\n"
  "    0x0430: 0x06c1,\n"
  "    0x0431: 0x06c2,\n"
  "    0x0446: 0x06c3,\n"
  "    0x0434: 0x06c4,\n"
  "    0x0435: 0x06c5,\n"
  "    0x0444: 0x06c6,\n"
  "    0x0433: 0x06c7,\n"
  "    0x0445: 0x06c8,\n"
  "    0x0438: 0x06c9,\n"
  "    0x0439: 0x06ca,\n"
  "    0x043A: 0x06cb,\n"
  "    0x043B: 0x06cc,\n"
  "    0x043C: 0x06cd,\n"
  "    0x043D: 0x06ce,\n"
  "    0x043E: 0x06cf,\n"
  "    0x043F: 0x06d0,\n"
  "    0x044F: 0x06d1,\n"
  "    0x0440: 0x06d2,\n"
  "    0x0441: 0x06d3,\n"
  "    0x0442: 0x06d4,\n"
  "    0x0443: 0x06d5,\n"
  "    0x0436: 0x06d6,\n"
  "    0x0432: 0x06d7,\n"
  "    0x044C: 0x06d8,\n"
  "    0x044B: 0x06d9,\n"
  "    0x0437: 0x06da,\n"
  "    0x0448: 0x06db,\n"
  "    0x044D: 0x06dc,\n"
  "    0x0449: 0x06dd,\n"
  "    0x0447: 0x06de,\n"
  "    0x044A: 0x06df,\n"
  "    0x042E: 0x06e0,\n"
  "    0x0410: 0x06e1,\n"
  "    0x0411: 0x06e2,\n"
  "    0x0426: 0x06e3,\n"
  "    0x0414: 0x06e4,\n"
  "    0x0415: 0x06e5,\n"
  "    0x0424: 0x06e6,\n"
  "    0x0413: 0x06e7,\n"
  "    0x0425: 0x06e8,\n"
  "    0x0418: 0x06e9,\n"
  "    0x0419: 0x06ea,\n"
  "    0x041A: 0x06eb,\n"
  "    0x041B: 0x06ec,\n"
  "    0x041C: 0x06ed,\n"
  "    0x041D: 0x06ee,\n"
  "    0x041E: 0x06ef,\n"
  "    0x041F: 0x06f0,\n"
  "    0x042F: 0x06f1,\n"
  "    0x0420: 0x06f2,\n"
  "    0x0421: 0x06f3,\n"
  "    0x0422: 0x06f4,\n"
  "    0x0423: 0x06f5,\n"
  "    0x0416: 0x06f6,\n"
  "    0x0412: 0x06f7,\n"
  "    0x042C: 0x06f8,\n"
  "    0x042B: 0x06f9,\n"
  "    0x0417: 0x06fa,\n"
  "    0x0428: 0x06fb,\n"
  "    0x042D: 0x06fc,\n"
  "    0x0429: 0x06fd,\n"
  "    0x0427: 0x06fe,\n"
  "    0x042A: 0x06ff,\n"
  "    0x0386: 0x07a1,\n"
  "    0x0388: 0x07a2,\n"
  "    0x0389: 0x07a3,\n"
  "    0x038A: 0x07a4,\n"
  "    0x03AA: 0x07a5,\n"
  "    0x038C: 0x07a7,\n"
  "    0x038E: 0x07a8,\n"
  "    0x03AB: 0x07a9,\n"
  "    0x038F: 0x07ab,\n"
  "    0x0385: 0x07ae,\n"
  "    0x2015: 0x07af,\n"
  "    0x03AC: 0x07b1,\n"
  "    0x03AD: 0x07b2,\n"
  "    0x03AE: 0x07b3,\n"
  "    0x03AF: 0x07b4,\n"
  "    0x03CA: 0x07b5,\n"
  "    0x0390: 0x07b6,\n"
  "    0x03CC: 0x07b7,\n"
  "    0x03CD: 0x07b8,\n"
  "    0x03CB: 0x07b9,\n"
  "    0x03B0: 0x07ba,\n"
  "    0x03CE: 0x07bb,\n"
  "    0x0391: 0x07c1,\n"
  "    0x0392: 0x07c2,\n"
  "    0x0393: 0x07c3,\n"
  "    0x0394: 0x07c4,\n"
  "    0x0395: 0x07c5,\n"
  "    0x0396: 0x07c6,\n"
  "    0x0397: 0x07c7,\n"
  "    0x0398: 0x07c8,\n"
  "    0x0399: 0x07c9,\n"
  "    0x039A: 0x07ca,\n"
  "    0x039B: 0x07cb,\n"
  "    0x039C: 0x07cc,\n"
  "    0x039D: 0x07cd,\n"
  "    0x039E: 0x07ce,\n"
  "    0x039F: 0x07cf,\n"
  "    0x03A0: 0x07d0,\n"
  "    0x03A1: 0x07d1,\n"
  "    0x03A3: 0x07d2,\n"
  "    0x03A4: 0x07d4,\n"
  "    0x03A5: 0x07d5,\n"
  "    0x03A6: 0x07d6,\n"
  "    0x03A7: 0x07d7,\n"
  "    0x03A8: 0x07d8,\n"
  "    0x03A9: 0x07d9,\n"
  "    0x03B1: 0x07e1,\n"
  "    0x03B2: 0x07e2,\n"
  "    0x03B3: 0x07e3,\n"
  "    0x03B4: 0x07e4,\n"
  "    0x03B5: 0x07e5,\n"
  "    0x03B6: 0x07e6,\n"
  "    0x03B7: 0x07e7,\n"
  "    0x03B8: 0x07e8,\n"
  "    0x03B9: 0x07e9,\n"
  "    0x03BA: 0x07ea,\n"
  "    0x03BB: 0x07eb,\n"
  "    0x03BC: 0x07ec,\n"
  "    0x03BD: 0x07ed,\n"
  "    0x03BE: 0x07ee,\n"
  "    0x03BF: 0x07ef,\n"
  "    0x03C0: 0x07f0,\n"
  "    0x03C1: 0x07f1,\n"
  "    0x03C3: 0x07f2,\n"
  "    0x03C2: 0x07f3,\n"
  "    0x03C4: 0x07f4,\n"
  "    0x03C5: 0x07f5,\n"
  "    0x03C6: 0x07f6,\n"
  "    0x03C7: 0x07f7,\n"
  "    0x03C8: 0x07f8,\n"
  "    0x03C9: 0x07f9,\n"
  "    0x23B7: 0x08a1,\n"
  "    0x2320: 0x08a4,\n"
  "    0x2321: 0x08a5,\n"
  "    0x23A1: 0x08a7,\n"
  "    0x23A3: 0x08a8,\n"
  "    0x23A4: 0x08a9,\n"
  "    0x23A6: 0x08aa,\n"
  "    0x239B: 0x08ab,\n"
  "    0x239D: 0x08ac,\n"
  "    0x239E: 0x08ad,\n"
  "    0x23A0: 0x08ae,\n"
  "    0x23A8: 0x08af,\n"
  "    0x23AC: 0x08b0,\n"
  "    0x2264: 0x08bc,\n"
  "    0x2260: 0x08bd,\n"
  "    0x2265: 0x08be,\n"
  "    0x222B: 0x08bf,\n"
  "    0x2234: 0x08c0,\n"
  "    0x221D: 0x08c1,\n"
  "    0x221E: 0x08c2,\n"
  "    0x2207: 0x08c5,\n"
  "    0x223C: 0x08c8,\n"
  "    0x2243: 0x08c9,\n"
  "    0x21D4: 0x08cd,\n"
  "    0x21D2: 0x08ce,\n"
  "    0x2261: 0x08cf,\n"
  "    0x221A: 0x08d6,\n"
  "    0x2282: 0x08da,\n"
  "    0x2283: 0x08db,\n"
  "    0x2229: 0x08dc,\n"
  "    0x222A: 0x08dd,\n"
  "    0x2227: 0x08de,\n"
  "    0x2228: 0x08df,\n"
  "    0x2202: 0x08ef,\n"
  "    0x0192: 0x08f6,\n"
  "    0x2190: 0x08fb,\n"
  "    0x2191: 0x08fc,\n"
  "    0x2192: 0x08fd,\n"
  "    0x2193: 0x08fe,\n"
  "    0x25C6: 0x09e0,\n"
  "    0x2592: 0x09e1,\n"
  "    0x2409: 0x09e2,\n"
  "    0x240C: 0x09e3,\n"
  "    0x240D: 0x09e4,\n"
  "    0x240A: 0x09e5,\n"
  "    0x2424: 0x09e8,\n"
  "    0x240B: 0x09e9,\n"
  "    0x2518: 0x09ea,\n"
  "    0x2510: 0x09eb,\n"
  "    0x250C: 0x09ec,\n"
  "    0x2514: 0x09ed,\n"
  "    0x253C: 0x09ee,\n"
  "    0x23BA: 0x09ef,\n"
  "    0x23BB: 0x09f0,\n"
  "    0x2500: 0x09f1,\n"
  "    0x23BC: 0x09f2,\n"
  "    0x23BD: 0x09f3,\n"
  "    0x251C: 0x09f4,\n"
  "    0x2524: 0x09f5,\n"
  "    0x2534: 0x09f6,\n"
  "    0x252C: 0x09f7,\n"
  "    0x2502: 0x09f8,\n"
  "    0x2003: 0x0aa1,\n"
  "    0x2002: 0x0aa2,\n"
  "    0x2004: 0x0aa3,\n"
  "    0x2005: 0x0aa4,\n"
  "    0x2007: 0x0aa5,\n"
  "    0x2008: 0x0aa6,\n"
  "    0x2009: 0x0aa7,\n"
  "    0x200A: 0x0aa8,\n"
  "    0x2014: 0x0aa9,\n"
  "    0x2013: 0x0aaa,\n"
  "    0x2026: 0x0aae,\n"
  "    0x2025: 0x0aaf,\n"
  "    0x2153: 0x0ab0,\n"
  "    0x2154: 0x0ab1,\n"
  "    0x2155: 0x0ab2,\n"
  "    0x2156: 0x0ab3,\n"
  "    0x2157: 0x0ab4,\n"
  "    0x2158: 0x0ab5,\n"
  "    0x2159: 0x0ab6,\n"
  "    0x215A: 0x0ab7,\n"
  "    0x2105: 0x0ab8,\n"
  "    0x2012: 0x0abb,\n"
  "    0x215B: 0x0ac3,\n"
  "    0x215C: 0x0ac4,\n"
  "    0x215D: 0x0ac5,\n"
  "    0x215E: 0x0ac6,\n"
  "    0x2122: 0x0ac9,\n"
  "    0x2018: 0x0ad0,\n"
  "    0x2019: 0x0ad1,\n"
  "    0x201C: 0x0ad2,\n"
  "    0x201D: 0x0ad3,\n"
  "    0x211E: 0x0ad4,\n"
  "    0x2032: 0x0ad6,\n"
  "    0x2033: 0x0ad7,\n"
  "    0x271D: 0x0ad9,\n"
  "    0x2663: 0x0aec,\n"
  "    0x2666: 0x0aed,\n"
  "    0x2665: 0x0aee,\n"
  "    0x2720: 0x0af0,\n"
  "    0x2020: 0x0af1,\n"
  "    0x2021: 0x0af2,\n"
  "    0x2713: 0x0af3,\n"
  "    0x2717: 0x0af4,\n"
  "    0x266F: 0x0af5,\n"
  "    0x266D: 0x0af6,\n"
  "    0x2642: 0x0af7,\n"
  "    0x2640: 0x0af8,\n"
  "    0x260E: 0x0af9,\n"
  "    0x2315: 0x0afa,\n"
  "    0x2117: 0x0afb,\n"
  "    0x2038: 0x0afc,\n"
  "    0x201A: 0x0afd,\n"
  "    0x201E: 0x0afe,\n"
  "    0x22A4: 0x0bc2,\n"
  "    0x230A: 0x0bc4,\n"
  "    0x2218: 0x0bca,\n"
  "    0x2395: 0x0bcc,\n"
  "    0x22A5: 0x0bce,\n"
  "    0x25CB: 0x0bcf,\n"
  "    0x2308: 0x0bd3,\n"
  "    0x22A3: 0x0bdc,\n"
  "    0x22A2: 0x0bfc,\n"
  "    0x2017: 0x0cdf,\n"
  "    0x05D0: 0x0ce0,\n"
  "    0x05D1: 0x0ce1,\n"
  "    0x05D2: 0x0ce2,\n"
  "    0x05D3: 0x0ce3,\n"
  "    0x05D4: 0x0ce4,\n"
  "    0x05D5: 0x0ce5,\n"
  "    0x05D6: 0x0ce6,\n"
  "    0x05D7: 0x0ce7,\n"
  "    0x05D8: 0x0ce8,\n"
  "    0x05D9: 0x0ce9,\n"
  "    0x05DA: 0x0cea,\n"
  "    0x05DB: 0x0ceb,\n"
  "    0x05DC: 0x0cec,\n"
  "    0x05DD: 0x0ced,\n"
  "    0x05DE: 0x0cee,\n"
  "    0x05DF: 0x0cef,\n"
  "    0x05E0: 0x0cf0,\n"
  "    0x05E1: 0x0cf1,\n"
  "    0x05E2: 0x0cf2,\n"
  "    0x05E3: 0x0cf3,\n"
  "    0x05E4: 0x0cf4,\n"
  "    0x05E5: 0x0cf5,\n"
  "    0x05E6: 0x0cf6,\n"
  "    0x05E7: 0x0cf7,\n"
  "    0x05E8: 0x0cf8,\n"
  "    0x05E9: 0x0cf9,\n"
  "    0x05EA: 0x0cfa,\n"
  "    0x0E01: 0x0da1,\n"
  "    0x0E02: 0x0da2,\n"
  "    0x0E03: 0x0da3,\n"
  "    0x0E04: 0x0da4,\n"
  "    0x0E05: 0x0da5,\n"
  "    0x0E06: 0x0da6,\n"
  "    0x0E07: 0x0da7,\n"
  "    0x0E08: 0x0da8,\n"
  "    0x0E09: 0x0da9,\n"
  "    0x0E0A: 0x0daa,\n"
  "    0x0E0B: 0x0dab,\n"
  "    0x0E0C: 0x0dac,\n"
  "    0x0E0D: 0x0dad,\n"
  "    0x0E0E: 0x0dae,\n"
  "    0x0E0F: 0x0daf,\n"
  "    0x0E10: 0x0db0,\n"
  "    0x0E11: 0x0db1,\n"
  "    0x0E12: 0x0db2,\n"
  "    0x0E13: 0x0db3,\n"
  "    0x0E14: 0x0db4,\n"
  "    0x0E15: 0x0db5,\n"
  "    0x0E16: 0x0db6,\n"
  "    0x0E17: 0x0db7,\n"
  "    0x0E18: 0x0db8,\n"
  "    0x0E19: 0x0db9,\n"
  "    0x0E1A: 0x0dba,\n"
  "    0x0E1B: 0x0dbb,\n"
  "    0x0E1C: 0x0dbc,\n"
  "    0x0E1D: 0x0dbd,\n"
  "    0x0E1E: 0x0dbe,\n"
  "    0x0E1F: 0x0dbf,\n"
  "    0x0E20: 0x0dc0,\n"
  "    0x0E21: 0x0dc1,\n"
  "    0x0E22: 0x0dc2,\n"
  "    0x0E23: 0x0dc3,\n"
  "    0x0E24: 0x0dc4,\n"
  "    0x0E25: 0x0dc5,\n"
  "    0x0E26: 0x0dc6,\n"
  "    0x0E27: 0x0dc7,\n"
  "    0x0E28: 0x0dc8,\n"
  "    0x0E29: 0x0dc9,\n"
  "    0x0E2A: 0x0dca,\n"
  "    0x0E2B: 0x0dcb,\n"
  "    0x0E2C: 0x0dcc,\n"
  "    0x0E2D: 0x0dcd,\n"
  "    0x0E2E: 0x0dce,\n"
  "    0x0E2F: 0x0dcf,\n"
  "    0x0E30: 0x0dd0,\n"
  "    0x0E31: 0x0dd1,\n"
  "    0x0E32: 0x0dd2,\n"
  "    0x0E33: 0x0dd3,\n"
  "    0x0E34: 0x0dd4,\n"
  "    0x0E35: 0x0dd5,\n"
  "    0x0E36: 0x0dd6,\n"
  "    0x0E37: 0x0dd7,\n"
  "    0x0E38: 0x0dd8,\n"
  "    0x0E39: 0x0dd9,\n"
  "    0x0E3A: 0x0dda,\n"
  "    0x0E3F: 0x0ddf,\n"
  "    0x0E40: 0x0de0,\n"
  "    0x0E41: 0x0de1,\n"
  "    0x0E42: 0x0de2,\n"
  "    0x0E43: 0x0de3,\n"
  "    0x0E44: 0x0de4,\n"
  "    0x0E45: 0x0de5,\n"
  "    0x0E46: 0x0de6,\n"
  "    0x0E47: 0x0de7,\n"
  "    0x0E48: 0x0de8,\n"
  "    0x0E49: 0x0de9,\n"
  "    0x0E4A: 0x0dea,\n"
  "    0x0E4B: 0x0deb,\n"
  "    0x0E4C: 0x0dec,\n"
  "    0x0E4D: 0x0ded,\n"
  "    0x0E50: 0x0df0,\n"
  "    0x0E51: 0x0df1,\n"
  "    0x0E52: 0x0df2,\n"
  "    0x0E53: 0x0df3,\n"
  "    0x0E54: 0x0df4,\n"
  "    0x0E55: 0x0df5,\n"
  "    0x0E56: 0x0df6,\n"
  "    0x0E57: 0x0df7,\n"
  "    0x0E58: 0x0df8,\n"
  "    0x0E59: 0x0df9,\n"
  "    0x0587: 0x1000587,\n"
  "    0x0589: 0x1000589,\n"
  "    0x055D: 0x100055d,\n"
  "    0x058A: 0x100058a,\n"
  "    0x055C: 0x100055c,\n"
  "    0x055B: 0x100055b,\n"
  "    0x055E: 0x100055e,\n"
  "    0x0531: 0x1000531,\n"
  "    0x0561: 0x1000561,\n"
  "    0x0532: 0x1000532,\n"
  "    0x0562: 0x1000562,\n"
  "    0x0533: 0x1000533,\n"
  "    0x0563: 0x1000563,\n"
  "    0x0534: 0x1000534,\n"
  "    0x0564: 0x1000564,\n"
  "    0x0535: 0x1000535,\n"
  "    0x0565: 0x1000565,\n"
  "    0x0536: 0x1000536,\n"
  "    0x0566: 0x1000566,\n"
  "    0x0537: 0x1000537,\n"
  "    0x0567: 0x1000567,\n"
  "    0x0538: 0x1000538,\n"
  "    0x0568: 0x1000568,\n"
  "    0x0539: 0x1000539,\n"
  "    0x0569: 0x1000569,\n"
  "    0x053A: 0x100053a,\n"
  "    0x056A: 0x100056a,\n"
  "    0x053B: 0x100053b,\n"
  "    0x056B: 0x100056b,\n"
  "    0x053C: 0x100053c,\n"
  "    0x056C: 0x100056c,\n"
  "    0x053D: 0x100053d,\n"
  "    0x056D: 0x100056d,\n"
  "    0x053E: 0x100053e,\n"
  "    0x056E: 0x100056e,\n"
  "    0x053F: 0x100053f,\n"
  "    0x056F: 0x100056f,\n"
  "    0x0540: 0x1000540,\n"
  "    0x0570: 0x1000570,\n"
  "    0x0541: 0x1000541,\n"
  "    0x0571: 0x1000571,\n"
  "    0x0542: 0x1000542,\n"
  "    0x0572: 0x1000572,\n"
  "    0x0543: 0x1000543,\n"
  "    0x0573: 0x1000573,\n"
  "    0x0544: 0x1000544,\n"
  "    0x0574: 0x1000574,\n"
  "    0x0545: 0x1000545,\n"
  "    0x0575: 0x1000575,\n"
  "    0x0546: 0x1000546,\n"
  "    0x0576: 0x1000576,\n"
  "    0x0547: 0x1000547,\n"
  "    0x0577: 0x1000577,\n"
  "    0x0548: 0x1000548,\n"
  "    0x0578: 0x1000578,\n"
  "    0x0549: 0x1000549,\n"
  "    0x0579: 0x1000579,\n"
  "    0x054A: 0x100054a,\n"
  "    0x057A: 0x100057a,\n"
  "    0x054B: 0x100054b,\n"
  "    0x057B: 0x100057b,\n"
  "    0x054C: 0x100054c,\n"
  "    0x057C: 0x100057c,\n"
  "    0x054D: 0x100054d,\n"
  "    0x057D: 0x100057d,\n"
  "    0x054E: 0x100054e,\n"
  "    0x057E: 0x100057e,\n"
  "    0x054F: 0x100054f,\n"
  "    0x057F: 0x100057f,\n"
  "    0x0550: 0x1000550,\n"
  "    0x0580: 0x1000580,\n"
  "    0x0551: 0x1000551,\n"
  "    0x0581: 0x1000581,\n"
  "    0x0552: 0x1000552,\n"
  "    0x0582: 0x1000582,\n"
  "    0x0553: 0x1000553,\n"
  "    0x0583: 0x1000583,\n"
  "    0x0554: 0x1000554,\n"
  "    0x0584: 0x1000584,\n"
  "    0x0555: 0x1000555,\n"
  "    0x0585: 0x1000585,\n"
  "    0x0556: 0x1000556,\n"
  "    0x0586: 0x1000586,\n"
  "    0x055A: 0x100055a,\n"
  "    0x10D0: 0x10010d0,\n"
  "    0x10D1: 0x10010d1,\n"
  "    0x10D2: 0x10010d2,\n"
  "    0x10D3: 0x10010d3,\n"
  "    0x10D4: 0x10010d4,\n"
  "    0x10D5: 0x10010d5,\n"
  "    0x10D6: 0x10010d6,\n"
  "    0x10D7: 0x10010d7,\n"
  "    0x10D8: 0x10010d8,\n"
  "    0x10D9: 0x10010d9,\n"
  "    0x10DA: 0x10010da,\n"
  "    0x10DB: 0x10010db,\n"
  "    0x10DC: 0x10010dc,\n"
  "    0x10DD: 0x10010dd,\n"
  "    0x10DE: 0x10010de,\n"
  "    0x10DF: 0x10010df,\n"
  "    0x10E0: 0x10010e0,\n"
  "    0x10E1: 0x10010e1,\n"
  "    0x10E2: 0x10010e2,\n"
  "    0x10E3: 0x10010e3,\n"
  "    0x10E4: 0x10010e4,\n"
  "    0x10E5: 0x10010e5,\n"
  "    0x10E6: 0x10010e6,\n"
  "    0x10E7: 0x10010e7,\n"
  "    0x10E8: 0x10010e8,\n"
  "    0x10E9: 0x10010e9,\n"
  "    0x10EA: 0x10010ea,\n"
  "    0x10EB: 0x10010eb,\n"
  "    0x10EC: 0x10010ec,\n"
  "    0x10ED: 0x10010ed,\n"
  "    0x10EE: 0x10010ee,\n"
  "    0x10EF: 0x10010ef,\n"
  "    0x10F0: 0x10010f0,\n"
  "    0x10F1: 0x10010f1,\n"
  "    0x10F2: 0x10010f2,\n"
  "    0x10F3: 0x10010f3,\n"
  "    0x10F4: 0x10010f4,\n"
  "    0x10F5: 0x10010f5,\n"
  "    0x10F6: 0x10010f6,\n"
  "    0x1E8A: 0x1001e8a,\n"
  "    0x012C: 0x100012c,\n"
  "    0x01B5: 0x10001b5,\n"
  "    0x01E6: 0x10001e6,\n"
  "    0x01D2: 0x10001d1,\n"
  "    0x019F: 0x100019f,\n"
  "    0x1E8B: 0x1001e8b,\n"
  "    0x012D: 0x100012d,\n"
  "    0x01B6: 0x10001b6,\n"
  "    0x01E7: 0x10001e7,\n"
  "    0x01D2: 0x10001d2,\n"
  "    0x0275: 0x1000275,\n"
  "    0x018F: 0x100018f,\n"
  "    0x0259: 0x1000259,\n"
  "    0x1E36: 0x1001e36,\n"
  "    0x1E37: 0x1001e37,\n"
  "    0x1EA0: 0x1001ea0,\n"
  "    0x1EA1: 0x1001ea1,\n"
  "    0x1EA2: 0x1001ea2,\n"
  "    0x1EA3: 0x1001ea3,\n"
  "    0x1EA4: 0x1001ea4,\n"
  "    0x1EA5: 0x1001ea5,\n"
  "    0x1EA6: 0x1001ea6,\n"
  "    0x1EA7: 0x1001ea7,\n"
  "    0x1EA8: 0x1001ea8,\n"
  "    0x1EA9: 0x1001ea9,\n"
  "    0x1EAA: 0x1001eaa,\n"
  "    0x1EAB: 0x1001eab,\n"
  "    0x1EAC: 0x1001eac,\n"
  "    0x1EAD: 0x1001ead,\n"
  "    0x1EAE: 0x1001eae,\n"
  "    0x1EAF: 0x1001eaf,\n"
  "    0x1EB0: 0x1001eb0,\n"
  "    0x1EB1: 0x1001eb1,\n"
  "    0x1EB2: 0x1001eb2,\n"
  "    0x1EB3: 0x1001eb3,\n"
  "    0x1EB4: 0x1001eb4,\n"
  "    0x1EB5: 0x1001eb5,\n"
  "    0x1EB6: 0x1001eb6,\n"
  "    0x1EB7: 0x1001eb7,\n"
  "    0x1EB8: 0x1001eb8,\n"
  "    0x1EB9: 0x1001eb9,\n"
  "    0x1EBA: 0x1001eba,\n"
  "    0x1EBB: 0x1001ebb,\n"
  "    0x1EBC: 0x1001ebc,\n"
  "    0x1EBD: 0x1001ebd,\n"
  "    0x1EBE: 0x1001ebe,\n"
  "    0x1EBF: 0x1001ebf,\n"
  "    0x1EC0: 0x1001ec0,\n"
  "    0x1EC1: 0x1001ec1,\n"
  "    0x1EC2: 0x1001ec2,\n"
  "    0x1EC3: 0x1001ec3,\n"
  "    0x1EC4: 0x1001ec4,\n"
  "    0x1EC5: 0x1001ec5,\n"
  "    0x1EC6: 0x1001ec6,\n"
  "    0x1EC7: 0x1001ec7,\n"
  "    0x1EC8: 0x1001ec8,\n"
  "    0x1EC9: 0x1001ec9,\n"
  "    0x1ECA: 0x1001eca,\n"
  "    0x1ECB: 0x1001ecb,\n"
  "    0x1ECC: 0x1001ecc,\n"
  "    0x1ECD: 0x1001ecd,\n"
  "    0x1ECE: 0x1001ece,\n"
  "    0x1ECF: 0x1001ecf,\n"
  "    0x1ED0: 0x1001ed0,\n"
  "    0x1ED1: 0x1001ed1,\n"
  "    0x1ED2: 0x1001ed2,\n"
  "    0x1ED3: 0x1001ed3,\n"
  "    0x1ED4: 0x1001ed4,\n"
  "    0x1ED5: 0x1001ed5,\n"
  "    0x1ED6: 0x1001ed6,\n"
  "    0x1ED7: 0x1001ed7,\n"
  "    0x1ED8: 0x1001ed8,\n"
  "    0x1ED9: 0x1001ed9,\n"
  "    0x1EDA: 0x1001eda,\n"
  "    0x1EDB: 0x1001edb,\n"
  "    0x1EDC: 0x1001edc,\n"
  "    0x1EDD: 0x1001edd,\n"
  "    0x1EDE: 0x1001ede,\n"
  "    0x1EDF: 0x1001edf,\n"
  "    0x1EE0: 0x1001ee0,\n"
  "    0x1EE1: 0x1001ee1,\n"
  "    0x1EE2: 0x1001ee2,\n"
  "    0x1EE3: 0x1001ee3,\n"
  "    0x1EE4: 0x1001ee4,\n"
  "    0x1EE5: 0x1001ee5,\n"
  "    0x1EE6: 0x1001ee6,\n"
  "    0x1EE7: 0x1001ee7,\n"
  "    0x1EE8: 0x1001ee8,\n"
  "    0x1EE9: 0x1001ee9,\n"
  "    0x1EEA: 0x1001eea,\n"
  "    0x1EEB: 0x1001eeb,\n"
  "    0x1EEC: 0x1001eec,\n"
  "    0x1EED: 0x1001eed,\n"
  "    0x1EEE: 0x1001eee,\n"
  "    0x1EEF: 0x1001eef,\n"
  "    0x1EF0: 0x1001ef0,\n"
  "    0x1EF1: 0x1001ef1,\n"
  "    0x1EF4: 0x1001ef4,\n"
  "    0x1EF5: 0x1001ef5,\n"
  "    0x1EF6: 0x1001ef6,\n"
  "    0x1EF7: 0x1001ef7,\n"
  "    0x1EF8: 0x1001ef8,\n"
  "    0x1EF9: 0x1001ef9,\n"
  "    0x01A0: 0x10001a0,\n"
  "    0x01A1: 0x10001a1,\n"
  "    0x01AF: 0x10001af,\n"
  "    0x01B0: 0x10001b0,\n"
  "    0x20A0: 0x10020a0,\n"
  "    0x20A1: 0x10020a1,\n"
  "    0x20A2: 0x10020a2,\n"
  "    0x20A3: 0x10020a3,\n"
  "    0x20A4: 0x10020a4,\n"
  "    0x20A5: 0x10020a5,\n"
  "    0x20A6: 0x10020a6,\n"
  "    0x20A7: 0x10020a7,\n"
  "    0x20A8: 0x10020a8,\n"
  "    0x20A9: 0x10020a9,\n"
  "    0x20AA: 0x10020aa,\n"
  "    0x20AB: 0x10020ab,\n"
  "    0x20AC: 0x20ac,\n"
  "    0x2070: 0x1002070,\n"
  "    0x2074: 0x1002074,\n"
  "    0x2075: 0x1002075,\n"
  "    0x2076: 0x1002076,\n"
  "    0x2077: 0x1002077,\n"
  "    0x2078: 0x1002078,\n"
  "    0x2079: 0x1002079,\n"
  "    0x2080: 0x1002080,\n"
  "    0x2081: 0x1002081,\n"
  "    0x2082: 0x1002082,\n"
  "    0x2083: 0x1002083,\n"
  "    0x2084: 0x1002084,\n"
  "    0x2085: 0x1002085,\n"
  "    0x2086: 0x1002086,\n"
  "    0x2087: 0x1002087,\n"
  "    0x2088: 0x1002088,\n"
  "    0x2089: 0x1002089,\n"
  "    0x2202: 0x1002202,\n"
  "    0x2205: 0x1002205,\n"
  "    0x2208: 0x1002208,\n"
  "    0x2209: 0x1002209,\n"
  "    0x220B: 0x100220B,\n"
  "    0x221A: 0x100221A,\n"
  "    0x221B: 0x100221B,\n"
  "    0x221C: 0x100221C,\n"
  "    0x222C: 0x100222C,\n"
  "    0x222D: 0x100222D,\n"
  "    0x2235: 0x1002235,\n"
  "    0x2245: 0x1002248,\n"
  "    0x2247: 0x1002247,\n"
  "    0x2262: 0x1002262,\n"
  "    0x2263: 0x1002263,\n"
  "    0x2800: 0x1002800,\n"
  "    0x2801: 0x1002801,\n"
  "    0x2802: 0x1002802,\n"
  "    0x2803: 0x1002803,\n"
  "    0x2804: 0x1002804,\n"
  "    0x2805: 0x1002805,\n"
  "    0x2806: 0x1002806,\n"
  "    0x2807: 0x1002807,\n"
  "    0x2808: 0x1002808,\n"
  "    0x2809: 0x1002809,\n"
  "    0x280a: 0x100280a,\n"
  "    0x280b: 0x100280b,\n"
  "    0x280c: 0x100280c,\n"
  "    0x280d: 0x100280d,\n"
  "    0x280e: 0x100280e,\n"
  "    0x280f: 0x100280f,\n"
  "    0x2810: 0x1002810,\n"
  "    0x2811: 0x1002811,\n"
  "    0x2812: 0x1002812,\n"
  "    0x2813: 0x1002813,\n"
  "    0x2814: 0x1002814,\n"
  "    0x2815: 0x1002815,\n"
  "    0x2816: 0x1002816,\n"
  "    0x2817: 0x1002817,\n"
  "    0x2818: 0x1002818,\n"
  "    0x2819: 0x1002819,\n"
  "    0x281a: 0x100281a,\n"
  "    0x281b: 0x100281b,\n"
  "    0x281c: 0x100281c,\n"
  "    0x281d: 0x100281d,\n"
  "    0x281e: 0x100281e,\n"
  "    0x281f: 0x100281f,\n"
  "    0x2820: 0x1002820,\n"
  "    0x2821: 0x1002821,\n"
  "    0x2822: 0x1002822,\n"
  "    0x2823: 0x1002823,\n"
  "    0x2824: 0x1002824,\n"
  "    0x2825: 0x1002825,\n"
  "    0x2826: 0x1002826,\n"
  "    0x2827: 0x1002827,\n"
  "    0x2828: 0x1002828,\n"
  "    0x2829: 0x1002829,\n"
  "    0x282a: 0x100282a,\n"
  "    0x282b: 0x100282b,\n"
  "    0x282c: 0x100282c,\n"
  "    0x282d: 0x100282d,\n"
  "    0x282e: 0x100282e,\n"
  "    0x282f: 0x100282f,\n"
  "    0x2830: 0x1002830,\n"
  "    0x2831: 0x1002831,\n"
  "    0x2832: 0x1002832,\n"
  "    0x2833: 0x1002833,\n"
  "    0x2834: 0x1002834,\n"
  "    0x2835: 0x1002835,\n"
  "    0x2836: 0x1002836,\n"
  "    0x2837: 0x1002837,\n"
  "    0x2838: 0x1002838,\n"
  "    0x2839: 0x1002839,\n"
  "    0x283a: 0x100283a,\n"
  "    0x283b: 0x100283b,\n"
  "    0x283c: 0x100283c,\n"
  "    0x283d: 0x100283d,\n"
  "    0x283e: 0x100283e,\n"
  "    0x283f: 0x100283f,\n"
  "    0x2840: 0x1002840,\n"
  "    0x2841: 0x1002841,\n"
  "    0x2842: 0x1002842,\n"
  "    0x2843: 0x1002843,\n"
  "    0x2844: 0x1002844,\n"
  "    0x2845: 0x1002845,\n"
  "    0x2846: 0x1002846,\n"
  "    0x2847: 0x1002847,\n"
  "    0x2848: 0x1002848,\n"
  "    0x2849: 0x1002849,\n"
  "    0x284a: 0x100284a,\n"
  "    0x284b: 0x100284b,\n"
  "    0x284c: 0x100284c,\n"
  "    0x284d: 0x100284d,\n"
  "    0x284e: 0x100284e,\n"
  "    0x284f: 0x100284f,\n"
  "    0x2850: 0x1002850,\n"
  "    0x2851: 0x1002851,\n"
  "    0x2852: 0x1002852,\n"
  "    0x2853: 0x1002853,\n"
  "    0x2854: 0x1002854,\n"
  "    0x2855: 0x1002855,\n"
  "    0x2856: 0x1002856,\n"
  "    0x2857: 0x1002857,\n"
  "    0x2858: 0x1002858,\n"
  "    0x2859: 0x1002859,\n"
  "    0x285a: 0x100285a,\n"
  "    0x285b: 0x100285b,\n"
  "    0x285c: 0x100285c,\n"
  "    0x285d: 0x100285d,\n"
  "    0x285e: 0x100285e,\n"
  "    0x285f: 0x100285f,\n"
  "    0x2860: 0x1002860,\n"
  "    0x2861: 0x1002861,\n"
  "    0x2862: 0x1002862,\n"
  "    0x2863: 0x1002863,\n"
  "    0x2864: 0x1002864,\n"
  "    0x2865: 0x1002865,\n"
  "    0x2866: 0x1002866,\n"
  "    0x2867: 0x1002867,\n"
  "    0x2868: 0x1002868,\n"
  "    0x2869: 0x1002869,\n"
  "    0x286a: 0x100286a,\n"
  "    0x286b: 0x100286b,\n"
  "    0x286c: 0x100286c,\n"
  "    0x286d: 0x100286d,\n"
  "    0x286e: 0x100286e,\n"
  "    0x286f: 0x100286f,\n"
  "    0x2870: 0x1002870,\n"
  "    0x2871: 0x1002871,\n"
  "    0x2872: 0x1002872,\n"
  "    0x2873: 0x1002873,\n"
  "    0x2874: 0x1002874,\n"
  "    0x2875: 0x1002875,\n"
  "    0x2876: 0x1002876,\n"
  "    0x2877: 0x1002877,\n"
  "    0x2878: 0x1002878,\n"
  "    0x2879: 0x1002879,\n"
  "    0x287a: 0x100287a,\n"
  "    0x287b: 0x100287b,\n"
  "    0x287c: 0x100287c,\n"
  "    0x287d: 0x100287d,\n"
  "    0x287e: 0x100287e,\n"
  "    0x287f: 0x100287f,\n"
  "    0x2880: 0x1002880,\n"
  "    0x2881: 0x1002881,\n"
  "    0x2882: 0x1002882,\n"
  "    0x2883: 0x1002883,\n"
  "    0x2884: 0x1002884,\n"
  "    0x2885: 0x1002885,\n"
  "    0x2886: 0x1002886,\n"
  "    0x2887: 0x1002887,\n"
  "    0x2888: 0x1002888,\n"
  "    0x2889: 0x1002889,\n"
  "    0x288a: 0x100288a,\n"
  "    0x288b: 0x100288b,\n"
  "    0x288c: 0x100288c,\n"
  "    0x288d: 0x100288d,\n"
  "    0x288e: 0x100288e,\n"
  "    0x288f: 0x100288f,\n"
  "    0x2890: 0x1002890,\n"
  "    0x2891: 0x1002891,\n"
  "    0x2892: 0x1002892,\n"
  "    0x2893: 0x1002893,\n"
  "    0x2894: 0x1002894,\n"
  "    0x2895: 0x1002895,\n"
  "    0x2896: 0x1002896,\n"
  "    0x2897: 0x1002897,\n"
  "    0x2898: 0x1002898,\n"
  "    0x2899: 0x1002899,\n"
  "    0x289a: 0x100289a,\n"
  "    0x289b: 0x100289b,\n"
  "    0x289c: 0x100289c,\n"
  "    0x289d: 0x100289d,\n"
  "    0x289e: 0x100289e,\n"
  "    0x289f: 0x100289f,\n"
  "    0x28a0: 0x10028a0,\n"
  "    0x28a1: 0x10028a1,\n"
  "    0x28a2: 0x10028a2,\n"
  "    0x28a3: 0x10028a3,\n"
  "    0x28a4: 0x10028a4,\n"
  "    0x28a5: 0x10028a5,\n"
  "    0x28a6: 0x10028a6,\n"
  "    0x28a7: 0x10028a7,\n"
  "    0x28a8: 0x10028a8,\n"
  "    0x28a9: 0x10028a9,\n"
  "    0x28aa: 0x10028aa,\n"
  "    0x28ab: 0x10028ab,\n"
  "    0x28ac: 0x10028ac,\n"
  "    0x28ad: 0x10028ad,\n"
  "    0x28ae: 0x10028ae,\n"
  "    0x28af: 0x10028af,\n"
  "    0x28b0: 0x10028b0,\n"
  "    0x28b1: 0x10028b1,\n"
  "    0x28b2: 0x10028b2,\n"
  "    0x28b3: 0x10028b3,\n"
  "    0x28b4: 0x10028b4,\n"
  "    0x28b5: 0x10028b5,\n"
  "    0x28b6: 0x10028b6,\n"
  "    0x28b7: 0x10028b7,\n"
  "    0x28b8: 0x10028b8,\n"
  "    0x28b9: 0x10028b9,\n"
  "    0x28ba: 0x10028ba,\n"
  "    0x28bb: 0x10028bb,\n"
  "    0x28bc: 0x10028bc,\n"
  "    0x28bd: 0x10028bd,\n"
  "    0x28be: 0x10028be,\n"
  "    0x28bf: 0x10028bf,\n"
  "    0x28c0: 0x10028c0,\n"
  "    0x28c1: 0x10028c1,\n"
  "    0x28c2: 0x10028c2,\n"
  "    0x28c3: 0x10028c3,\n"
  "    0x28c4: 0x10028c4,\n"
  "    0x28c5: 0x10028c5,\n"
  "    0x28c6: 0x10028c6,\n"
  "    0x28c7: 0x10028c7,\n"
  "    0x28c8: 0x10028c8,\n"
  "    0x28c9: 0x10028c9,\n"
  "    0x28ca: 0x10028ca,\n"
  "    0x28cb: 0x10028cb,\n"
  "    0x28cc: 0x10028cc,\n"
  "    0x28cd: 0x10028cd,\n"
  "    0x28ce: 0x10028ce,\n"
  "    0x28cf: 0x10028cf,\n"
  "    0x28d0: 0x10028d0,\n"
  "    0x28d1: 0x10028d1,\n"
  "    0x28d2: 0x10028d2,\n"
  "    0x28d3: 0x10028d3,\n"
  "    0x28d4: 0x10028d4,\n"
  "    0x28d5: 0x10028d5,\n"
  "    0x28d6: 0x10028d6,\n"
  "    0x28d7: 0x10028d7,\n"
  "    0x28d8: 0x10028d8,\n"
  "    0x28d9: 0x10028d9,\n"
  "    0x28da: 0x10028da,\n"
  "    0x28db: 0x10028db,\n"
  "    0x28dc: 0x10028dc,\n"
  "    0x28dd: 0x10028dd,\n"
  "    0x28de: 0x10028de,\n"
  "    0x28df: 0x10028df,\n"
  "    0x28e0: 0x10028e0,\n"
  "    0x28e1: 0x10028e1,\n"
  "    0x28e2: 0x10028e2,\n"
  "    0x28e3: 0x10028e3,\n"
  "    0x28e4: 0x10028e4,\n"
  "    0x28e5: 0x10028e5,\n"
  "    0x28e6: 0x10028e6,\n"
  "    0x28e7: 0x10028e7,\n"
  "    0x28e8: 0x10028e8,\n"
  "    0x28e9: 0x10028e9,\n"
  "    0x28ea: 0x10028ea,\n"
  "    0x28eb: 0x10028eb,\n"
  "    0x28ec: 0x10028ec,\n"
  "    0x28ed: 0x10028ed,\n"
  "    0x28ee: 0x10028ee,\n"
  "    0x28ef: 0x10028ef,\n"
  "    0x28f0: 0x10028f0,\n"
  "    0x28f1: 0x10028f1,\n"
  "    0x28f2: 0x10028f2,\n"
  "    0x28f3: 0x10028f3,\n"
  "    0x28f4: 0x10028f4,\n"
  "    0x28f5: 0x10028f5,\n"
  "    0x28f6: 0x10028f6,\n"
  "    0x28f7: 0x10028f7,\n"
  "    0x28f8: 0x10028f8,\n"
  "    0x28f9: 0x10028f9,\n"
  "    0x28fa: 0x10028fa,\n"
  "    0x28fb: 0x10028fb,\n"
  "    0x28fc: 0x10028fc,\n"
  "    0x28fd: 0x10028fd,\n"
  "    0x28fe: 0x10028fe,\n"
  "    0x28ff: 0x10028ff\n"
  "};\n"
  "\n"
  "var ON_KEYDOWN = 1 << 0; /* Report on keydown, otherwise wait until keypress  */\n"
  "\n"
  "\n"
  "var specialKeyTable = {\n"
  "    // These generate a keyDown and keyPress in Firefox and Opera\n"
  "    8: [0xFF08, ON_KEYDOWN], // BACKSPACE\n"
  "    13: [0xFF0D, ON_KEYDOWN], // ENTER\n"
  "\n"
  "    // This generates a keyDown and keyPress in Opera\n"
  "    9: [0xFF09, ON_KEYDOWN], // TAB\n"
  "\n"
  "    27: 0xFF1B, // ESCAPE\n"
  "    46: 0xFFFF, // DELETE\n"
  "    36: 0xFF50, // HOME\n"
  "    35: 0xFF57, // END\n"
  "    33: 0xFF55, // PAGE_UP\n"
  "    34: 0xFF56, // PAGE_DOWN\n"
  "    45: 0xFF63, // INSERT\n"
  "    37: 0xFF51, // LEFT\n"
  "    38: 0xFF52, // UP\n"
  "    39: 0xFF53, // RIGHT\n"
  "    40: 0xFF54, // DOWN\n"
  "    16: 0xFFE1, // SHIFT\n"
  "    17: 0xFFE3, // CONTROL\n"
  "    18: 0xFFE9, // Left ALT (Mac Command)\n"
  "    112: 0xFFBE, // F1\n"
  "    113: 0xFFBF, // F2\n"
  "    114: 0xFFC0, // F3\n"
  "    115: 0xFFC1, // F4\n"
  "    116: 0xFFC2, // F5\n"
  "    117: 0xFFC3, // F6\n"
  "    118: 0xFFC4, // F7\n"
  "    119: 0xFFC5, // F8\n"
  "    120: 0xFFC6, // F9\n"
  "    121: 0xFFC7, // F10\n"
  "    122: 0xFFC8, // F11\n"
  "    123: 0xFFC9  // F12\n"
  "};\n"
  "\n"
  "function getEventKeySym(ev) {\n"
  "    if (typeof ev.which !== \"undefined\" && ev.which > 0)\n"
  "	return ev.which;\n"
  "    return ev.keyCode;\n"
  "}\n"
  "\n"
  "// This is based on the approach from noVNC. We handle\n"
  "// everything in keydown that we have all info for, and that\n"
  "// are not safe to pass on to the browser (as it may do something\n"
  "// with the key. The rest we pass on to keypress so we can get the\n"
  "// translated keysym.\n"
  "function getKeysymSpecial(ev) {\n"
  "    if (ev.keyCode in specialKeyTable) {\n"
  "	var r = specialKeyTable[ev.keyCode];\n"
  "	var flags = 0;\n"
  "	if (typeof r != 'number') {\n"
  "	    flags = r[1];\n"
  "	    r = r[0];\n"
  "	}\n"
  "	if (ev.type === 'keydown' || flags & ON_KEYDOWN)\n"
  "	    return r;\n"
  "    }\n"
  "    // If we don't hold alt or ctrl, then we should be safe to pass\n"
  "    // on to keypressed and look at the translated data\n"
  "    if (!ev.ctrlKey && !ev.altKey)\n"
  "	return null;\n"
  "\n"
  "    var keysym = getEventKeySym(ev);\n"
  "\n"
  "    /* Remap symbols */\n"
  "    switch (keysym) {\n"
  "    case 186 : keysym = 59; break; // ; (IE)\n"
  "    case 187 : keysym = 61; break; // = (IE)\n"
  "    case 188 : keysym = 44; break; // , (Mozilla, IE)\n"
  "    case 109 : // - (Mozilla, Opera)\n"
  "	if (true /* TODO: check if browser is firefox or opera */)\n"
  "	    keysym = 45;\n"
  "	break;\n"
  "    case 189 : keysym = 45; break; // - (IE)\n"
  "    case 190 : keysym = 46; break; // . (Mozilla, IE)\n"
  "    case 191 : keysym = 47; break; // / (Mozilla, IE)\n"
  "    case 192 : keysym = 96; break; // ` (Mozilla, IE)\n"
  "    case 219 : keysym = 91; break; // [ (Mozilla, IE)\n"
  "    case 220 : keysym = 92; break; // \\ (Mozilla, IE)\n"
  "    case 221 : keysym = 93; break; // ] (Mozilla, IE)\n"
  "    case 222 : keysym = 39; break; // ' (Mozilla, IE)\n"
  "    }\n"
  "\n"
  "    /* Remap shifted and unshifted keys */\n"
  "    if (!!ev.shiftKey) {\n"
  "	switch (keysym) {\n"
  "	case 48 : keysym = 41 ; break; // ) (shifted 0)\n"
  "	case 49 : keysym = 33 ; break; // ! (shifted 1)\n"
  "	case 50 : keysym = 64 ; break; // @ (shifted 2)\n"
  "	case 51 : keysym = 35 ; break; // # (shifted 3)\n"
  "	case 52 : keysym = 36 ; break; // $ (shifted 4)\n"
  "	case 53 : keysym = 37 ; break; // % (shifted 5)\n"
  "	case 54 : keysym = 94 ; break; // ^ (shifted 6)\n"
  "	case 55 : keysym = 38 ; break; // & (shifted 7)\n"
  "	case 56 : keysym = 42 ; break; // * (shifted 8)\n"
  "	case 57 : keysym = 40 ; break; // ( (shifted 9)\n"
  "	case 59 : keysym = 58 ; break; // : (shifted `)\n"
  "	case 61 : keysym = 43 ; break; // + (shifted ;)\n"
  "	case 44 : keysym = 60 ; break; // < (shifted ,)\n"
  "	case 45 : keysym = 95 ; break; // _ (shifted -)\n"
  "	case 46 : keysym = 62 ; break; // > (shifted .)\n"
  "	case 47 : keysym = 63 ; break; // ? (shifted /)\n"
  "	case 96 : keysym = 126; break; // ~ (shifted `)\n"
  "	case 91 : keysym = 123; break; // { (shifted [)\n"
  "	case 92 : keysym = 124; break; // | (shifted \\)\n"
  "	case 93 : keysym = 125; break; // } (shifted ])\n"
  "	case 39 : keysym = 34 ; break; // \" (shifted ')\n"
  "	}\n"
  "    } else if ((keysym >= 65) && (keysym <=90)) {\n"
  "	/* Remap unshifted A-Z */\n"
  "	keysym += 32;\n"
  "    } else if (ev.keyLocation === 3) {\n"
  "	// numpad keys\n"
  "	switch (keysym) {\n"
  "	case 96 : keysym = 48; break; // 0\n"
  "	case 97 : keysym = 49; break; // 1\n"
  "	case 98 : keysym = 50; break; // 2\n"
  "	case 99 : keysym = 51; break; // 3\n"
  "	case 100: keysym = 52; break; // 4\n"
  "	case 101: keysym = 53; break; // 5\n"
  "	case 102: keysym = 54; break; // 6\n"
  "	case 103: keysym = 55; break; // 7\n"
  "	case 104: keysym = 56; break; // 8\n"
  "	case 105: keysym = 57; break; // 9\n"
  "	case 109: keysym = 45; break; // -\n"
  "	case 110: keysym = 46; break; // .\n"
  "	case 111: keysym = 47; break; // /\n"
  "	}\n"
  "    }\n"
  "\n"
  "    return keysym;\n"
  "}\n"
  "\n"
  "/* Translate DOM keyPress event to keysym value */\n"
  "function getKeysym(ev) {\n"
  "    var keysym, msg;\n"
  "\n"
  "    keysym = getEventKeySym(ev);\n"
  "\n"
  "    if ((keysym > 255) && (keysym < 0xFF00)) {\n"
  "	// Map Unicode outside Latin 1 to gdk keysyms\n"
  "	keysym = unicodeTable[keysym];\n"
  "	if (typeof keysym === 'undefined')\n"
  "	    keysym = 0;\n"
  "    }\n"
  "\n"
  "    return keysym;\n"
  "}\n"
  "\n"
  "function copyKeyEvent(ev) {\n"
  "    var members = ['type', 'keyCode', 'charCode', 'which',\n"
  "		   'altKey', 'ctrlKey', 'shiftKey',\n"
  "		   'keyLocation', 'keyIdentifier'], i, obj = {};\n"
  "    for (i = 0; i < members.length; i++) {\n"
  "	if (typeof ev[members[i]] !== \"undefined\")\n"
  "	    obj[members[i]] = ev[members[i]];\n"
  "    }\n"
  "    return obj;\n"
  "}\n"
  "\n"
  "function pushKeyEvent(fev) {\n"
  "    keyDownList.push(fev);\n"
  "}\n"
  "\n"
  "function getKeyEvent(keyCode, pop) {\n"
  "    var i, fev = null;\n"
  "    for (i = keyDownList.length-1; i >= 0; i--) {\n"
  "	if (keyDownList[i].keyCode === keyCode) {\n"
  "	    if ((typeof pop !== \"undefined\") && pop)\n"
  "		fev = keyDownList.splice(i, 1)[0];\n"
  "	    else\n"
  "		fev = keyDownList[i];\n"
  "	    break;\n"
  "	}\n"
  "    }\n"
  "    return fev;\n"
  "}\n"
  "\n"
  "function ignoreKeyEvent(ev) {\n"
  "    // Blarg. Some keys have a different keyCode on keyDown vs keyUp\n"
  "    if (ev.keyCode === 229) {\n"
  "	// French AZERTY keyboard dead key.\n"
  "	// Lame thing is that the respective keyUp is 219 so we can't\n"
  "	// properly ignore the keyUp event\n"
  "	return true;\n"
  "    }\n"
  "    return false;\n"
  "}\n"
  "\n"
  "function handleKeyDown(e) {\n"
  "    var fev = null, ev = (e ? e : window.event), keysym = null, suppress = false;\n"
  "\n"
  "    fev = copyKeyEvent(ev);\n"
  "\n"
  "    keysym = getKeysymSpecial(ev);\n"
  "    // Save keysym decoding for use in keyUp\n"
  "    fev.keysym = keysym;\n"
  "    if (keysym) {\n"
  "	// If it is a key or key combination that might trigger\n"
  "	// browser behaviors or it has no corresponding keyPress\n"
  "	// event, then send it immediately\n"
  "	if (!ignoreKeyEvent(ev))\n"
  "	    sendInput(\"k\", [keysym, lastState]);\n"
  "	suppress = true;\n"
  "    }\n"
  "\n"
  "    if (! ignoreKeyEvent(ev)) {\n"
  "	// Add it to the list of depressed keys\n"
  "	pushKeyEvent(fev);\n"
  "    }\n"
  "\n"
  "    if (suppress) {\n"
  "	// Suppress bubbling/default actions\n"
  "	return cancelEvent(ev);\n"
  "    }\n"
  "\n"
  "    // Allow the event to bubble and become a keyPress event which\n"
  "    // will have the character code translated\n"
  "    return true;\n"
  "}\n"
  "\n"
  "function handleKeyPress(e) {\n"
  "    var ev = (e ? e : window.event), kdlen = keyDownList.length, keysym = null;\n"
  "\n"
  "    if (((ev.which !== \"undefined\") && (ev.which === 0)) ||\n"
  "	getKeysymSpecial(ev)) {\n"
  "	// Firefox and Opera generate a keyPress event even if keyDown\n"
  "	// is suppressed. But the keys we want to suppress will have\n"
  "	// either:\n"
  "	// - the which attribute set to 0\n"
  "	// - getKeysymSpecial() will identify it\n"
  "	return cancelEvent(ev);\n"
  "    }\n"
  "\n"
  "    keysym = getKeysym(ev);\n"
  "\n"
  "    // Modify the the which attribute in the depressed keys list so\n"
  "    // that the keyUp event will be able to have the character code\n"
  "    // translation available.\n"
  "    if (kdlen > 0) {\n"
  "	keyDownList[kdlen-1].keysym = keysym;\n"
  "    } else {\n"
  "	//log(\"keyDownList empty when keyPress triggered\");\n"
  "    }\n"
  "\n"
  "    // Send the translated keysym\n"
  "    if (keysym > 0)\n"
  "	sendInput (\"k\", [keysym, lastState]);\n"
  "\n"
  "    // Stop keypress events just in case\n"
  "    return cancelEvent(ev);\n"
  "}\n"
  "\n"
  "function handleKeyUp(e) {\n"
  "    var fev = null, ev = (e ? e : window.event), i, keysym;\n"
  "\n"
  "    fev = getKeyEvent(ev.keyCode, true);\n"
  "\n"
  "    if (fev)\n"
  "	keysym = fev.keysym;\n"
  "    else {\n"
  "	//log(\"Key event (keyCode = \" + ev.keyCode + \") not found on keyDownList\");\n"
  "	keysym = 0;\n"
  "    }\n"
  "\n"
  "    if (keysym > 0)\n"
  "	sendInput (\"K\", [keysym, lastState]);\n"
  "    return cancelEvent(ev);\n"
  "}\n"
  "\n"
  "function onKeyDown (ev) {\n"
  "    updateForEvent(ev);\n"
  "    if (localGrab)\n"
  "	return cancelEvent(ev);\n"
  "    return handleKeyDown(ev);\n"
  "}\n"
  "\n"
  "function onKeyPress(ev) {\n"
  "    updateForEvent(ev);\n"
  "    if (localGrab)\n"
  "	return cancelEvent(ev);\n"
  "    return handleKeyPress(ev);\n"
  "}\n"
  "\n"
  "function onKeyUp (ev) {\n"
  "    updateForEvent(ev);\n"
  "    if (localGrab)\n"
  "	return cancelEvent(ev);\n"
  "    return handleKeyUp(ev);\n"
  "}\n"
  "\n"
  "function cancelEvent(ev)\n"
  "{\n"
  "    ev = ev ? ev : window.event;\n"
  "    if (ev.stopPropagation)\n"
  "	ev.stopPropagation();\n"
  "    if (ev.preventDefault)\n"
  "	ev.preventDefault();\n"
  "    ev.cancelBubble = true;\n"
  "    ev.cancel = true;\n"
  "    ev.returnValue = false;\n"
  "    return false;\n"
  "}\n"
  "\n"
  "function onMouseWheel(ev)\n"
  "{\n"
  "    updateForEvent(ev);\n"
  "    if (localGrab)\n"
  "	return false;\n"
  "    ev = ev ? ev : window.event;\n"
  "\n"
  "    var id = getSurfaceId(ev);\n"
  "    var pos = getPositionsFromEvent(ev, id);\n"
  "\n"
  "    var offset = ev.detail ? ev.detail : ev.wheelDelta;\n"
  "    var dir = 0;\n"
  "    if (offset > 0)\n"
  "	dir = 1;\n"
  "    sendInput (\"s\", [realWindowWithMouse, id, pos.rootX, pos.rootY, pos.winX, pos.winY, lastState, dir]);\n"
  "\n"
  "    return cancelEvent(ev);\n"
  "}\n"
  "\n"
  "function setupDocument(document)\n"
  "{\n"
  "    document.oncontextmenu = function () { return false; };\n"
  "    document.onmousemove = onMouseMove;\n"
  "    document.onmouseover = onMouseOver;\n"
  "    document.onmouseout = onMouseOut;\n"
  "    document.onmousedown = onMouseDown;\n"
  "    document.onmouseup = onMouseUp;\n"
  "    document.onkeydown = onKeyDown;\n"
  "    document.onkeypress = onKeyPress;\n"
  "    document.onkeyup = onKeyUp;\n"
  "\n"
  "    if (document.addEventListener) {\n"
  "      document.addEventListener('DOMMouseScroll', onMouseWheel, false);\n"
  "      document.addEventListener('mousewheel', onMouseWheel, false);\n"
  "    } else if (document.attachEvent) {\n"
  "      element.attachEvent(\"onmousewheel\", onMouseWheel);\n"
  "    }\n"
  "}\n"
  "\n"
  "function connect()\n"
  "{\n"
  "    var url = window.location.toString();\n"
  "    var query_string = url.split(\"?\");\n"
  "    if (query_string.length > 1) {\n"
  "	var params = query_string[1].split(\"&\");\n"
  "	if (params[0].indexOf(\"toplevel\") != -1)\n"
  "	    useToplevelWindows = true;\n"
  "    }\n"
  "\n"
  "    var loc = window.location.toString().replace(\"http:\", \"ws:\");\n"
  "    loc = loc.substr(0, loc.lastIndexOf('/')) + \"/socket\";\n"
  "    var ws = null;\n"
  "\n"
  "    if (\"WebSocket\" in window) {\n"
  "	ws = new WebSocket(loc, \"broadway\");\n"
  "    } else if (\"MozWebSocket\" in window) { // Firefox 6\n"
  "	ws = new MozWebSocket(loc);\n"
  "    } else {\n"
  "	alert(\"WebSocket not supported, input will not work!\");\n"
  "	return;\n"
  "    }\n"
  "\n"
  "	ws.onopen = function() {\n"
  "	    inputSocket = ws;\n"
  "	    var w, h;\n"
  "	    if (useToplevelWindows) {\n"
  "		w = window.screen.width;\n"
  "		h = window.screen.height;\n"
  "	    } else {\n"
  "		w = window.innerWidth;\n"
  "		h = window.innerHeight;\n"
  "		window.onresize = function(ev) {\n"
  "		    var w, h;\n"
  "		    w = window.innerWidth;\n"
  "		    h = window.innerHeight;\n"
  "		    sendInput (\"d\", [w, h]);\n"
  "		};\n"
  "	    }\n"
  "	    sendInput (\"d\", [w, h]);\n"
  "	};\n"
  "	ws.onclose = function() {\n"
  "	    inputSocket = null;\n"
  "	};\n"
  "	ws.onmessage = function(event) {\n"
  "	    handleMessage(event.data);\n"
  "	};\n"
  "\n"
  "    setupDocument(document);\n"
  "    window.onunload = function (ev) {\n"
  "	for (var i = 0; i < toplevelWindows.length; i++)\n"
  "	    toplevelWindows[i].close();\n"
  "    };\n"
  "}\n";
