James Iter 9 жил өмнө
parent
commit
94ed407e5b
100 өөрчлөгдсөн 27543 нэмэгдсэн , 7 устгасан
  1. 41 7
      main.py
  2. 2 0
      models/initialize.py
  3. 1 0
      requirements.txt
  4. 56 0
      static/novnc/app/error-handler.js
  5. 92 0
      static/novnc/app/images/alt.svg
  6. 106 0
      static/novnc/app/images/clipboard.svg
  7. 96 0
      static/novnc/app/images/connect.svg
  8. 96 0
      static/novnc/app/images/ctrl.svg
  9. 100 0
      static/novnc/app/images/ctrlaltdel.svg
  10. 94 0
      static/novnc/app/images/disconnect.svg
  11. 71 0
      static/novnc/app/images/drag.svg
  12. 81 0
      static/novnc/app/images/error.svg
  13. 92 0
      static/novnc/app/images/esc.svg
  14. 69 0
      static/novnc/app/images/expander.svg
  15. 93 0
      static/novnc/app/images/fullscreen.svg
  16. 82 0
      static/novnc/app/images/handle.svg
  17. 172 0
      static/novnc/app/images/handle_bg.svg
  18. 42 0
      static/novnc/app/images/icons/Makefile
  19. BIN
      static/novnc/app/images/icons/novnc-120x120.png
  20. BIN
      static/novnc/app/images/icons/novnc-144x144.png
  21. BIN
      static/novnc/app/images/icons/novnc-152x152.png
  22. BIN
      static/novnc/app/images/icons/novnc-16x16.png
  23. BIN
      static/novnc/app/images/icons/novnc-192x192.png
  24. BIN
      static/novnc/app/images/icons/novnc-24x24.png
  25. BIN
      static/novnc/app/images/icons/novnc-32x32.png
  26. BIN
      static/novnc/app/images/icons/novnc-48x48.png
  27. BIN
      static/novnc/app/images/icons/novnc-60x60.png
  28. BIN
      static/novnc/app/images/icons/novnc-64x64.png
  29. BIN
      static/novnc/app/images/icons/novnc-72x72.png
  30. BIN
      static/novnc/app/images/icons/novnc-76x76.png
  31. BIN
      static/novnc/app/images/icons/novnc-96x96.png
  32. 163 0
      static/novnc/app/images/icons/novnc-icon-sm.svg
  33. 163 0
      static/novnc/app/images/icons/novnc-icon.svg
  34. 81 0
      static/novnc/app/images/info.svg
  35. 75 0
      static/novnc/app/images/keyboard.svg
  36. 92 0
      static/novnc/app/images/mouse_left.svg
  37. 92 0
      static/novnc/app/images/mouse_middle.svg
  38. 92 0
      static/novnc/app/images/mouse_none.svg
  39. 92 0
      static/novnc/app/images/mouse_right.svg
  40. 87 0
      static/novnc/app/images/power.svg
  41. 76 0
      static/novnc/app/images/settings.svg
  42. 86 0
      static/novnc/app/images/tab.svg
  43. 90 0
      static/novnc/app/images/toggleextrakeys.svg
  44. 81 0
      static/novnc/app/images/warning.svg
  45. 70 0
      static/novnc/app/locale/de.json
  46. 70 0
      static/novnc/app/locale/el.json
  47. 11 0
      static/novnc/app/locale/nl.json
  48. 70 0
      static/novnc/app/locale/sv.json
  49. 4 0
      static/novnc/app/sounds/CREDITS
  50. BIN
      static/novnc/app/sounds/bell.mp3
  51. BIN
      static/novnc/app/sounds/bell.oga
  52. BIN
      static/novnc/app/styles/Orbitron700.ttf
  53. BIN
      static/novnc/app/styles/Orbitron700.woff
  54. 916 0
      static/novnc/app/styles/base.css
  55. 89 0
      static/novnc/app/styles/lite.css
  56. 1743 0
      static/novnc/app/ui.js
  57. 235 0
      static/novnc/app/webutil.js
  58. 113 0
      static/novnc/core/base64.js
  59. 276 0
      static/novnc/core/des.js
  60. 749 0
      static/novnc/core/display.js
  61. 38 0
      static/novnc/core/inflator.js
  62. 497 0
      static/novnc/core/input/devices.js
  63. 310 0
      static/novnc/core/input/domkeytable.js
  64. 127 0
      static/novnc/core/input/fixedkeys.js
  65. 614 0
      static/novnc/core/input/keysym.js
  66. 688 0
      static/novnc/core/input/keysymdef.js
  67. 167 0
      static/novnc/core/input/util.js
  68. 116 0
      static/novnc/core/input/vkeys.js
  69. 147 0
      static/novnc/core/input/xtscancodes.js
  70. 2420 0
      static/novnc/core/rfb.js
  71. 53 0
      static/novnc/core/util/browsers.js
  72. 140 0
      static/novnc/core/util/events.js
  73. 170 0
      static/novnc/core/util/localization.js
  74. 53 0
      static/novnc/core/util/logging.js
  75. 138 0
      static/novnc/core/util/properties.js
  76. 15 0
      static/novnc/core/util/strings.js
  77. 351 0
      static/novnc/core/websock.js
  78. 0 0
      static/novnc/vendor/browser-es-module-loader/.npmignore
  79. 15 0
      static/novnc/vendor/browser-es-module-loader/README.md
  80. 7027 0
      static/novnc/vendor/browser-es-module-loader/dist/babel-worker.js
  81. 1429 0
      static/novnc/vendor/browser-es-module-loader/dist/browser-es-module-loader.js
  82. 0 0
      static/novnc/vendor/browser-es-module-loader/dist/browser-es-module-loader.js.map
  83. 16 0
      static/novnc/vendor/browser-es-module-loader/rollup.config.js
  84. 23 0
      static/novnc/vendor/browser-es-module-loader/src/babel-worker.js
  85. 266 0
      static/novnc/vendor/browser-es-module-loader/src/browser-es-module-loader.js
  86. 21 0
      static/novnc/vendor/pako/LICENSE
  87. 6 0
      static/novnc/vendor/pako/README.md
  88. 45 0
      static/novnc/vendor/pako/lib/utils/common.js
  89. 27 0
      static/novnc/vendor/pako/lib/zlib/adler32.js
  90. 47 0
      static/novnc/vendor/pako/lib/zlib/constants.js
  91. 36 0
      static/novnc/vendor/pako/lib/zlib/crc32.js
  92. 1846 0
      static/novnc/vendor/pako/lib/zlib/deflate.js
  93. 35 0
      static/novnc/vendor/pako/lib/zlib/gzheader.js
  94. 324 0
      static/novnc/vendor/pako/lib/zlib/inffast.js
  95. 1527 0
      static/novnc/vendor/pako/lib/zlib/inflate.js
  96. 322 0
      static/novnc/vendor/pako/lib/zlib/inftrees.js
  97. 11 0
      static/novnc/vendor/pako/lib/zlib/messages.js
  98. 1195 0
      static/novnc/vendor/pako/lib/zlib/trees.js
  99. 24 0
      static/novnc/vendor/pako/lib/zlib/zstream.js
  100. 255 0
      static/novnc/vendor/promise.js

+ 41 - 7
main.py

@@ -11,11 +11,13 @@ import time
 import jimit as ji
 import json
 
+import os
+import sys
 from flask import g
 
 from models import Utils
 from models.event_processor import EventProcessor
-from models.initialize import app, logger
+from models.initialize import app, logger, q_ws
 import api_route_table
 import views_route_table
 from models import Database as db
@@ -38,6 +40,8 @@ from api.host import blueprints as host_blueprints
 from views.guest import blueprint as view_guest_blueprint
 from views.guest import blueprints as view_guest_blueprints
 
+from websockify.websocketproxy import WebSocketProxy
+
 
 __author__ = 'James Iter'
 __date__ = '2017/3/31'
@@ -45,6 +49,28 @@ __contact__ = 'james.iter.cn@gmail.com'
 __copyright__ = '(c) 2017 by James Iter.'
 
 
+def instantiation_ws_vnc(listen_port, target_host, target_port):
+    # 用于 Web noVNC 代理
+    ws = WebSocketProxy(listen_host="0.0.0.0", listen_port=listen_port, target_host=target_host,
+                        target_port=target_port, run_once=True, daemon=True, idle_timeout=10)
+    ws.start_server()
+
+
+def ws_engine_for_vnc():
+    while True:
+        payload = q_ws.get()
+        payload = json.loads(payload)
+
+        c_pid = os.fork()
+        if c_pid == 0:
+            instantiation_ws_vnc(payload['listen_port'], payload['target_host'], payload['target_port'])
+
+        # 因为 WebSocketProxy 使用了 daemon 参数,所以当执行到 ws.start_server() 时,会退出其所在的子进程,
+        # 故而这里设置wait来处理结束的子进程的环境,避免出现僵尸进程。
+        os.wait()
+        q_ws.task_done()
+
+
 @app.before_request
 @Utils.dumps2response
 def r_before_request():
@@ -90,15 +116,23 @@ if __name__ == '__main__':
         signal.signal(signal.SIGTERM, Utils.signal_handle)
         signal.signal(signal.SIGINT, Utils.signal_handle)
 
-        thread.start_new_thread(EventProcessor.launch, ())
-        Utils.thread_counter += 1
+        pid = os.fork()
+        if pid == 0:
+            ws_engine_for_vnc()
+
+        else:
+            # 父进程退出时,会清理所有提前退出的子进程的环境。所以这里无需对子进程做等待操作。
+            # 即:即使子进程提前退出,且因父进程没有做wait处理,使其变成了僵尸进程。但当父进程退出时,会对因其所产生的僵尸进程做统一清理操作。
+
+            thread.start_new_thread(EventProcessor.launch, ())
+            Utils.thread_counter += 1
 
-        app.run(host=app.config['jimv_listen'], port=app.config['jimv_port'], use_reloader=False, threaded=True)
+            app.run(host=app.config['jimv_listen'], port=app.config['jimv_port'], use_reloader=False, threaded=True)
 
-        while Utils.thread_counter > 0:
-            time.sleep(1)
+            while Utils.thread_counter > 0:
+                time.sleep(1)
 
-        print 'Main say bye-bye!'
+            print 'Main say bye-bye!'
 
     except:
         logger.error(traceback.format_exc())

+ 2 - 0
models/initialize.py

@@ -2,6 +2,7 @@
 # -*- coding: utf-8 -*-
 
 
+from multiprocessing import JoinableQueue
 from flask import Flask, g
 import logging
 from logging.handlers import TimedRotatingFileHandler
@@ -92,6 +93,7 @@ class Init(object):
         return _logger
 
 
+q_ws = JoinableQueue()
 # 预编译效率更高
 regex_sql_str = re.compile('\\\+"')
 regex_dsl_str = re.compile('^\w+:\w+:\S+$')

+ 1 - 0
requirements.txt

@@ -8,3 +8,4 @@ Flask-Session==0.3.0
 gunicorn
 eventlet
 redis
+websockify

+ 56 - 0
static/novnc/app/error-handler.js

@@ -0,0 +1,56 @@
+// NB: this should *not* be included as a module until we have
+// native support in the browsers, so that our error handler
+// can catch script-loading errors.
+
+
+(function(){
+    "use strict";
+
+    // Fallback for all uncought errors
+    function handleError (event, err) {
+        try {
+            var msg = document.getElementById('noVNC_fallback_errormsg');
+
+            // Only show the initial error
+            if (msg.hasChildNodes()) {
+                return false;
+            }
+
+            var div = document.createElement("div");
+            div.classList.add('noVNC_message');
+            div.appendChild(document.createTextNode(event.message));
+            msg.appendChild(div);
+
+            if (event.filename) {
+                div = document.createElement("div");
+                div.className = 'noVNC_location';
+                var text = event.filename;
+                if (event.lineno !== undefined) {
+                    text += ":" + event.lineno;
+                    if (event.colno !== undefined) {
+                        text += ":" + event.colno;
+                    }
+                }
+                div.appendChild(document.createTextNode(text));
+                msg.appendChild(div);
+            }
+
+            if (err && (err.stack !== undefined)) {
+                div = document.createElement("div");
+                div.className = 'noVNC_stack';
+                div.appendChild(document.createTextNode(err.stack));
+                msg.appendChild(div);
+            }
+
+            document.getElementById('noVNC_fallback_error')
+                .classList.add("noVNC_open");
+        } catch (exc) {
+            document.write("noVNC encountered an error.");
+        }
+        // Don't return true since this would prevent the error
+        // from being printed to the browser console.
+        return false;
+    }
+    window.addEventListener('error', function (evt) { handleError(evt, evt.error); });
+    window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); });
+})();

+ 92 - 0
static/novnc/app/images/alt.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="alt.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="18.205425"
+     inkscape:cy="17.531398"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text5290">
+      <path
+         d="m 9.9560547,1042.3329 -2.9394531,0 -0.4638672,1.3281 -1.8896485,0 2.7001953,-7.29 2.241211,0 2.7001958,7.29 -1.889649,0 -0.4589843,-1.3281 z m -2.4707031,-1.3526 1.9970703,0 -0.9960938,-2.9003 -1.0009765,2.9003 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5340" />
+      <path
+         d="m 13.188477,1036.0634 1.748046,0 0,7.5976 -1.748046,0 0,-7.5976 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5342" />
+      <path
+         d="m 18.535156,1036.6395 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151367,0.5176 0.151368,0.1318 0.600586,0.1318 l 0.898438,0 0,1.25 -1.499024,0 q -1.035156,0 -1.469726,-0.4297 -0.429688,-0.4345 -0.429688,-1.4697 l 0,-2.3193 -0.86914,0 0,-1.25 0.86914,0 0,-1.5528 1.748047,0 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5344" />
+    </g>
+  </g>
+</svg>

+ 106 - 0
static/novnc/app/images/clipboard.svg

@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="clipboard.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="15.366606"
+     inkscape:cy="16.42981"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 9,6 6,6 C 5.4459889,6 5,6.4459889 5,7 l 0,13 c 0,0.554011 0.4459889,1 1,1 l 13,0 c 0.554011,0 1,-0.445989 1,-1 L 20,7 C 20,6.4459889 19.554011,6 19,6 l -3,0"
+       transform="translate(0,1027.3622)"
+       id="rect6083"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cssssssssc" />
+    <rect
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect6085"
+       width="7"
+       height="4"
+       x="9"
+       y="1031.3622"
+       ry="1.00002" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+       d="m 8.5071212,1038.8622 7.9999998,0"
+       id="path6087"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+       d="m 8.5071212,1041.8622 3.9999998,0"
+       id="path6089"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
+       d="m 8.5071212,1044.8622 5.9999998,0"
+       id="path6091"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 96 - 0
static/novnc/app/images/connect.svg

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="connect.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="37.14834"
+     inkscape:cy="1.9525926"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       id="g5103"
+       transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,-729.15757,315.8823)">
+      <path
+         sodipodi:nodetypes="cssssc"
+         inkscape:connector-curvature="0"
+         id="rect5096"
+         d="m 11,1040.3622 -5,0 c -1.108,0 -2,-0.892 -2,-2 l 0,-4 c 0,-1.108 0.892,-2 2,-2 l 5,0"
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+      <path
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+         d="m 14,1032.3622 5,0 c 1.108,0 2,0.892 2,2 l 0,4 c 0,1.108 -0.892,2 -2,2 l -5,0"
+         id="path5099"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cssssc" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path5101"
+         d="m 9,1036.3622 7,0"
+         style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+  </g>
+</svg>

+ 96 - 0
static/novnc/app/images/ctrl.svg

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="ctrl.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="18.205425"
+     inkscape:cy="17.531398"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text5290">
+      <path
+         d="m 9.1210938,1043.1898 q -0.5175782,0.2686 -1.0791016,0.4053 -0.5615235,0.1367 -1.171875,0.1367 -1.8212891,0 -2.8857422,-1.0156 -1.0644531,-1.0205 -1.0644531,-2.7637 0,-1.748 1.0644531,-2.7637 1.0644531,-1.0205 2.8857422,-1.0205 0.6103515,0 1.171875,0.1368 0.5615234,0.1367 1.0791016,0.4052 l 0,1.5088 q -0.522461,-0.3564 -1.0302735,-0.5224 -0.5078125,-0.1661 -1.0693359,-0.1661 -1.0058594,0 -1.5820313,0.6446 -0.5761719,0.6445 -0.5761719,1.7773 0,1.1279 0.5761719,1.7725 0.5761719,0.6445 1.5820313,0.6445 0.5615234,0 1.0693359,-0.166 0.5078125,-0.166 1.0302735,-0.5225 l 0,1.5088 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5370" />
+      <path
+         d="m 12.514648,1036.5687 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151368,0.5176 0.151367,0.1318 0.600586,0.1318 l 0.898437,0 0,1.25 -1.499023,0 q -1.035157,0 -1.469727,-0.4297 -0.429687,-0.4345 -0.429687,-1.4697 l 0,-2.3193 -0.8691411,0 0,-1.25 0.8691411,0 0,-1.5528 1.748046,0 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5372" />
+      <path
+         d="m 19.453125,1039.6107 q -0.229492,-0.1074 -0.458984,-0.1562 -0.22461,-0.054 -0.454102,-0.054 -0.673828,0 -1.040039,0.4345 -0.361328,0.4297 -0.361328,1.2354 l 0,2.5195 -1.748047,0 0,-5.4687 1.748047,0 0,0.8984 q 0.336914,-0.5371 0.771484,-0.7813 0.439453,-0.249 1.049805,-0.249 0.08789,0 0.19043,0.01 0.102539,0 0.297851,0.029 l 0.0049,1.582 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5374" />
+      <path
+         d="m 20.332031,1035.9926 1.748047,0 0,7.5976 -1.748047,0 0,-7.5976 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5376" />
+    </g>
+  </g>
+</svg>

+ 100 - 0
static/novnc/app/images/ctrlaltdel.svg

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="ctrlaltdel.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8"
+     inkscape:cx="11.135667"
+     inkscape:cy="16.407428"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <rect
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5253"
+       width="5"
+       height="5.0000172"
+       x="16"
+       y="1031.3622"
+       ry="1.0000174" />
+    <rect
+       y="1043.3622"
+       x="4"
+       height="5.0000172"
+       width="5"
+       id="rect5255"
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       ry="1.0000174" />
+    <rect
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5257"
+       width="5"
+       height="5.0000172"
+       x="13"
+       y="1043.3622"
+       ry="1.0000174" />
+  </g>
+</svg>

+ 94 - 0
static/novnc/app/images/disconnect.svg

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="disconnect.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="25.05707"
+     inkscape:cy="11.594858"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       id="g5171"
+       transform="translate(-24.062499,-6.15775e-4)">
+      <path
+         id="path5110"
+         transform="translate(0,1027.3622)"
+         d="m 39.744141,3.4960938 c -0.769923,0 -1.539607,0.2915468 -2.121094,0.8730468 l -2.566406,2.5664063 1.414062,1.4140625 2.566406,-2.5664063 c 0.403974,-0.404 1.010089,-0.404 1.414063,0 l 2.828125,2.828125 c 0.40398,0.4039 0.403907,1.0101621 0,1.4140629 l -2.566406,2.566406 1.414062,1.414062 2.566406,-2.566406 c 1.163041,-1.1629 1.162968,-3.0791874 0,-4.2421874 L 41.865234,4.3691406 C 41.283747,3.7876406 40.514063,3.4960937 39.744141,3.4960938 Z M 39.017578,9.015625 a 1.0001,1.0001 0 0 0 -0.6875,0.3027344 l -0.445312,0.4453125 1.414062,1.4140621 0.445313,-0.445312 A 1.0001,1.0001 0 0 0 39.017578,9.015625 Z m -6.363281,0.7070312 a 1.0001,1.0001 0 0 0 -0.6875,0.3027348 L 28.431641,13.5625 c -1.163042,1.163 -1.16297,3.079187 0,4.242188 l 2.828125,2.828124 c 1.162974,1.163101 3.079213,1.163101 4.242187,0 l 3.535156,-3.535156 a 1.0001,1.0001 0 1 0 -1.414062,-1.414062 l -3.535156,3.535156 c -0.403974,0.404 -1.010089,0.404 -1.414063,0 l -2.828125,-2.828125 c -0.403981,-0.404 -0.403908,-1.010162 0,-1.414063 l 3.535156,-3.537109 A 1.0001,1.0001 0 0 0 32.654297,9.7226562 Z m 3.109375,2.1621098 -2.382813,2.384765 a 1.0001,1.0001 0 1 0 1.414063,1.414063 l 2.382812,-2.384766 -1.414062,-1.414062 z"
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         inkscape:connector-curvature="0" />
+      <rect
+         transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
+         y="752.29541"
+         x="-712.31262"
+         height="18.000017"
+         width="3"
+         id="rect5116"
+         style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    </g>
+  </g>
+</svg>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 71 - 0
static/novnc/app/images/drag.svg


+ 81 - 0
static/novnc/app/images/error.svg

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="error.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="14.00357"
+     inkscape:cy="12.443398"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 7 3 C 4.7839905 3 3 4.7839905 3 7 L 3 18 C 3 20.21601 4.7839905 22 7 22 L 18 22 C 20.21601 22 22 20.21601 22 18 L 22 7 C 22 4.7839905 20.21601 3 18 3 L 7 3 z M 7.6992188 6 A 1.6916875 1.6924297 0 0 1 8.9121094 6.5117188 L 12.5 10.101562 L 16.087891 6.5117188 A 1.6916875 1.6924297 0 0 1 17.251953 6 A 1.6916875 1.6924297 0 0 1 18.480469 8.90625 L 14.892578 12.496094 L 18.480469 16.085938 A 1.6916875 1.6924297 0 1 1 16.087891 18.478516 L 12.5 14.888672 L 8.9121094 18.478516 A 1.6916875 1.6924297 0 1 1 6.5214844 16.085938 L 10.109375 12.496094 L 6.5214844 8.90625 A 1.6916875 1.6924297 0 0 1 7.6992188 6 z "
+       transform="translate(0,1027.3622)"
+       id="rect4135" />
+  </g>
+</svg>

+ 92 - 0
static/novnc/app/images/esc.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="esc.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="18.205425"
+     inkscape:cy="17.531398"
+     inkscape:document-units="px"
+     inkscape:current-layer="text5290"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text5290">
+      <path
+         d="m 3.9331055,1036.1464 5.0732422,0 0,1.4209 -3.1933594,0 0,1.3574 3.0029297,0 0,1.4209 -3.0029297,0 0,1.6699 3.3007812,0 0,1.4209 -5.180664,0 0,-7.29 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5314" />
+      <path
+         d="m 14.963379,1038.1385 0,1.3282 q -0.561524,-0.2344 -1.083984,-0.3516 -0.522461,-0.1172 -0.986329,-0.1172 -0.498046,0 -0.742187,0.127 -0.239258,0.122 -0.239258,0.3808 0,0.21 0.180664,0.3223 0.185547,0.1123 0.65918,0.166 l 0.307617,0.044 q 1.342773,0.1709 1.806641,0.5615 0.463867,0.3906 0.463867,1.2256 0,0.874 -0.644531,1.3134 -0.644532,0.4395 -1.923829,0.4395 -0.541992,0 -1.123046,-0.088 -0.576172,-0.083 -1.186524,-0.2539 l 0,-1.3281 q 0.522461,0.2539 1.069336,0.3808 0.551758,0.127 1.118164,0.127 0.512695,0 0.771485,-0.1416 0.258789,-0.1416 0.258789,-0.4199 0,-0.2344 -0.180664,-0.3467 -0.175782,-0.1172 -0.708008,-0.1807 l -0.307617,-0.039 q -1.166993,-0.1465 -1.635743,-0.542 -0.46875,-0.3955 -0.46875,-1.2012 0,-0.8691 0.595703,-1.2891 0.595704,-0.4199 1.826172,-0.4199 0.483399,0 1.015625,0.073 0.532227,0.073 1.157227,0.2294 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5316" />
+      <path
+         d="m 21.066895,1038.1385 0,1.4258 q -0.356446,-0.2441 -0.717774,-0.3613 -0.356445,-0.1172 -0.742187,-0.1172 -0.732422,0 -1.142579,0.4297 -0.405273,0.4248 -0.405273,1.1914 0,0.7666 0.405273,1.1963 0.410157,0.4248 1.142579,0.4248 0.410156,0 0.776367,-0.1221 0.371094,-0.122 0.683594,-0.3613 l 0,1.4307 q -0.410157,0.1513 -0.834961,0.2246 -0.419922,0.078 -0.844727,0.078 -1.479492,0 -2.314453,-0.7568 -0.834961,-0.7618 -0.834961,-2.1143 0,-1.3525 0.834961,-2.1094 0.834961,-0.7617 2.314453,-0.7617 0.429688,0 0.844727,0.078 0.419921,0.073 0.834961,0.2246 z"
+         style="font-size:10px;fill:#ffffff;fill-opacity:1"
+         id="path5318" />
+    </g>
+  </g>
+</svg>

+ 69 - 0
static/novnc/app/images/expander.svg

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="9"
+   height="10"
+   viewBox="0 0 9 10"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="expander.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="45.254834"
+     inkscape:cx="9.8737281"
+     inkscape:cy="6.4583132"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-object-midpoints="false"
+     inkscape:object-nodes="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1042.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="M 2.0800781,1042.3633 A 2.0002,2.0002 0 0 0 0,1044.3613 l 0,6 a 2.0002,2.0002 0 0 0 3.0292969,1.7168 l 5,-3 a 2.0002,2.0002 0 0 0 0,-3.4316 l -5,-3 a 2.0002,2.0002 0 0 0 -0.9492188,-0.2832 z"
+       id="path4138"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 93 - 0
static/novnc/app/images/fullscreen.svg

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="fullscreen.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="16.400723"
+     inkscape:cy="15.083758"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <rect
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect5006"
+       width="17"
+       height="17.000017"
+       x="4"
+       y="1031.3622"
+       ry="3.0000174" />
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+       d="m 7.5,1044.8622 4,0 -1.5,-1.5 1.5,-1.5 -1,-1 -1.5,1.5 -1.5,-1.5 0,4 z"
+       id="path5017"
+       inkscape:connector-curvature="0" />
+    <path
+       inkscape:connector-curvature="0"
+       id="path5025"
+       d="m 17.5,1034.8622 -4,0 1.5,1.5 -1.5,1.5 1,1 1.5,-1.5 1.5,1.5 0,-4 z"
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+  </g>
+</svg>

+ 82 - 0
static/novnc/app/images/handle.svg

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="5"
+   height="6"
+   viewBox="0 0 5 6"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="handle.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32"
+     inkscape:cx="1.3551778"
+     inkscape:cy="8.7800329"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1046.3622)">
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 4.0000803,1049.3622 -3,-2 0,4 z"
+       id="path4247"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cccc" />
+  </g>
+</svg>

+ 172 - 0
static/novnc/app/images/handle_bg.svg

@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="15"
+   height="50"
+   viewBox="0 0 15 50"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="handle_bg.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="-10.001409"
+     inkscape:cy="24.512566"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1002.3622)">
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4249"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1008.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1013.8622"
+       x="9.5"
+       height="1.0000174"
+       width="1"
+       id="rect4255"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       ry="1.7382812e-05"
+       y="1008.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4261"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4263"
+       width="1"
+       height="1.0000174"
+       x="4.5"
+       y="1013.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1039.8622"
+       x="9.5"
+       height="1.0000174"
+       width="1"
+       id="rect4265"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4267"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1044.8622"
+       ry="1.7382812e-05" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4269"
+       width="1"
+       height="1.0000174"
+       x="4.5"
+       y="1039.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1044.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4271"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4273"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1018.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1018.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4275"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4277"
+       width="1"
+       height="1.0000174"
+       x="9.5"
+       y="1034.8622"
+       ry="1.7382812e-05" />
+    <rect
+       ry="1.7382812e-05"
+       y="1034.8622"
+       x="4.5"
+       height="1.0000174"
+       width="1"
+       id="rect4279"
+       style="opacity:0.25;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+  </g>
+</svg>

+ 42 - 0
static/novnc/app/images/icons/Makefile

@@ -0,0 +1,42 @@
+ICONS := \
+	novnc-16x16.png \
+	novnc-24x24.png \
+	novnc-32x32.png \
+	novnc-48x48.png \
+	novnc-64x64.png
+
+ANDROID_LAUNCHER := \
+	novnc-48x48.png \
+	novnc-72x72.png \
+	novnc-96x96.png \
+	novnc-144x144.png \
+	novnc-192x192.png
+
+IPHONE_LAUNCHER := \
+	novnc-60x60.png \
+	novnc-120x120.png
+
+IPAD_LAUNCHER := \
+	novnc-76x76.png \
+	novnc-152x152.png
+
+ALL_ICONS := $(ICONS) $(ANDROID_LAUNCHER) $(IPHONE_LAUNCHER) $(IPAD_LAUNCHER)
+
+all: $(ALL_ICONS)
+
+novnc-16x16.png: novnc-icon-sm.svg
+	convert -density 90 \
+		-background transparent "$<" "$@"
+novnc-24x24.png: novnc-icon-sm.svg
+	convert -density 135 \
+		-background transparent "$<" "$@"
+novnc-32x32.png: novnc-icon-sm.svg
+	convert -density 180 \
+		-background transparent "$<" "$@"
+
+novnc-%.png: novnc-icon.svg
+	convert -density $$[`echo $* | cut -d x -f 1` * 90 / 48] \
+		-background transparent "$<" "$@"
+
+clean:
+	rm -f *.png

BIN
static/novnc/app/images/icons/novnc-120x120.png


BIN
static/novnc/app/images/icons/novnc-144x144.png


BIN
static/novnc/app/images/icons/novnc-152x152.png


BIN
static/novnc/app/images/icons/novnc-16x16.png


BIN
static/novnc/app/images/icons/novnc-192x192.png


BIN
static/novnc/app/images/icons/novnc-24x24.png


BIN
static/novnc/app/images/icons/novnc-32x32.png


BIN
static/novnc/app/images/icons/novnc-48x48.png


BIN
static/novnc/app/images/icons/novnc-60x60.png


BIN
static/novnc/app/images/icons/novnc-64x64.png


BIN
static/novnc/app/images/icons/novnc-72x72.png


BIN
static/novnc/app/images/icons/novnc-76x76.png


BIN
static/novnc/app/images/icons/novnc-96x96.png


+ 163 - 0
static/novnc/app/images/icons/novnc-icon-sm.svg

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   viewBox="0 0 16 16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="novnc-icon-sm.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="45.254834"
+     inkscape:cx="9.722703"
+     inkscape:cy="5.5311896"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4169" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1036.3621)">
+    <rect
+       style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4167"
+       width="16"
+       height="15.999992"
+       x="0"
+       y="1036.3622"
+       ry="2.6666584" />
+    <path
+       style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 2.6666667,1036.3621 C 1.1893373,1036.3621 0,1037.5515 0,1039.0288 l 0,10.6666 c 0,1.4774 1.1893373,2.6667 2.6666667,2.6667 l 4,0 C 11.837333,1052.3621 16,1046.7128 16,1039.6955 l 0,-0.6667 c 0,-1.4773 -1.189337,-2.6667 -2.666667,-2.6667 l -10.6666663,0 z"
+       id="rect4173"
+       inkscape:connector-curvature="0" />
+    <g
+       id="g4381">
+      <g
+         transform="translate(0.25,0.25)"
+         style="fill:#000000;fill-opacity:1"
+         id="g4365">
+        <g
+           style="fill:#000000;fill-opacity:1"
+           id="g4367">
+          <path
+             inkscape:connector-curvature="0"
+             id="path4369"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 4.3289754,1039.3621 c 0.1846149,0 0.3419956,0.071 0.4716623,0.2121 C 4.933546,1039.7121 5,1039.8793 5,1040.0759 l 0,3.2862 -1,0 0,-2.964 c 0,-0.024 -0.011592,-0.036 -0.034038,-0.036 l -1.931924,0 C 2.011349,1040.3621 2,1040.3741 2,1040.3981 l 0,2.964 -1,0 0,-4 z"
+             sodipodi:nodetypes="scsccsssscccs" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path4371"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 6.6710244,1039.3621 2.6579513,0 c 0.184775,0 0.3419957,0.071 0.471662,0.2121 C 9.933546,1039.7121 10,1039.8793 10,1040.0759 l 0,2.5724 c 0,0.1966 -0.066454,0.3655 -0.1993623,0.5069 -0.1296663,0.1379 -0.286887,0.2069 -0.471662,0.2069 l -2.6579513,0 c -0.184775,0 -0.3436164,-0.069 -0.4765247,-0.2069 C 6.0648334,1043.0138 6,1042.8449 6,1042.6483 l 0,-2.5724 c 0,-0.1966 0.064833,-0.3638 0.1944997,-0.5017 0.1329083,-0.1414 0.2917497,-0.2121 0.4765247,-0.2121 z m 2.2949386,1 -1.931926,0 C 7.011344,1040.3621 7,1040.3741 7,1040.3981 l 0,1.928 c 0,0.024 0.011347,0.036 0.034037,0.036 l 1.931926,0 c 0.02269,0 0.034037,-0.012 0.034037,-0.036 l 0,-1.928 c 0,-0.024 -0.011347,-0.036 -0.034037,-0.036 z"
+             sodipodi:nodetypes="sscsscsscsscssssssssss" />
+        </g>
+        <g
+           style="fill:#000000;fill-opacity:1"
+           id="g4373">
+          <path
+             inkscape:connector-curvature="0"
+             id="path4375"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 3,1047.1121 1,-2.75 1,0 -1.5,4 -1,0 -1.5,-4 1,0 z"
+             sodipodi:nodetypes="cccccccc" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path4377"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 9,1046.8621 0,-2.5 1,0 0,4 -1,0 -2,-2.5 0,2.5 -1,0 0,-4 1,0 z"
+             sodipodi:nodetypes="ccccccccccc" />
+          <path
+             inkscape:connector-curvature="0"
+             id="path4379"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             d="m 15,1045.3621 -2.96596,0 c -0.02269,0 -0.03404,0.012 -0.03404,0.036 l 0,1.928 c 0,0.024 0.01135,0.036 0.03404,0.036 l 2.96596,0 0,1 -3.324113,0 c -0.188017,0 -0.348479,-0.068 -0.481388,-0.2037 C 11.064833,1048.0192 11,1047.8511 11,1047.6542 l 0,-2.5842 c 0,-0.1969 0.06483,-0.3633 0.194499,-0.4991 0.132909,-0.1392 0.293371,-0.2088 0.481388,-0.2088 l 3.324113,0 z"
+             sodipodi:nodetypes="cssssccscsscscc" />
+        </g>
+      </g>
+      <g
+         id="g4356">
+        <g
+           id="g4347">
+          <path
+             sodipodi:nodetypes="scsccsssscccs"
+             d="m 4.3289754,1039.3621 c 0.1846149,0 0.3419956,0.071 0.4716623,0.2121 C 4.933546,1039.7121 5,1039.8793 5,1040.0759 l 0,3.2862 -1,0 0,-2.964 c 0,-0.024 -0.011592,-0.036 -0.034038,-0.036 l -1.931924,0 c -0.022689,0 -0.034038,0.012 -0.034038,0.036 l 0,2.964 -1,0 0,-4 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4143"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="sscsscsscsscssssssssss"
+             d="m 6.6710244,1039.3621 2.6579513,0 c 0.184775,0 0.3419957,0.071 0.471662,0.2121 C 9.933546,1039.7121 10,1039.8793 10,1040.0759 l 0,2.5724 c 0,0.1966 -0.066454,0.3655 -0.1993623,0.5069 -0.1296663,0.1379 -0.286887,0.2069 -0.471662,0.2069 l -2.6579513,0 c -0.184775,0 -0.3436164,-0.069 -0.4765247,-0.2069 C 6.0648334,1043.0138 6,1042.8449 6,1042.6483 l 0,-2.5724 c 0,-0.1966 0.064833,-0.3638 0.1944997,-0.5017 0.1329083,-0.1414 0.2917497,-0.2121 0.4765247,-0.2121 z m 2.2949386,1 -1.931926,0 C 7.011344,1040.3621 7,1040.3741 7,1040.3981 l 0,1.928 c 0,0.024 0.011347,0.036 0.034037,0.036 l 1.931926,0 c 0.02269,0 0.034037,-0.012 0.034037,-0.036 l 0,-1.928 c 0,-0.024 -0.011347,-0.036 -0.034037,-0.036 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4145"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="g4351">
+          <path
+             sodipodi:nodetypes="cccccccc"
+             d="m 3,1047.1121 1,-2.75 1,0 -1.5,4 -1,0 -1.5,-4 1,0 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4147"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="ccccccccccc"
+             d="m 9,1046.8621 0,-2.5 1,0 0,4 -1,0 -2,-2.5 0,2.5 -1,0 0,-4 1,0 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4149"
+             inkscape:connector-curvature="0" />
+          <path
+             sodipodi:nodetypes="cssssccscsscscc"
+             d="m 15,1045.3621 -2.96596,0 c -0.02269,0 -0.03404,0.012 -0.03404,0.036 l 0,1.928 c 0,0.024 0.01135,0.036 0.03404,0.036 l 2.96596,0 0,1 -3.324113,0 c -0.188017,0 -0.348479,-0.068 -0.481388,-0.2037 C 11.064833,1048.0192 11,1047.8511 11,1047.6542 l 0,-2.5842 c 0,-0.1969 0.06483,-0.3633 0.194499,-0.4991 0.132909,-0.1392 0.293371,-0.2088 0.481388,-0.2088 l 3.324113,0 z"
+             style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+             id="path4151"
+             inkscape:connector-curvature="0" />
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>

+ 163 - 0
static/novnc/app/images/icons/novnc-icon.svg

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="48"
+   height="48"
+   viewBox="0 0 48 48.000001"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="novnc-icon.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="27.187245"
+     inkscape:cy="17.700974"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:object-nodes="true"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:snap-midpoints="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4169" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1004.3621)">
+    <rect
+       style="opacity:1;fill:#494949;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect4167"
+       width="48"
+       height="48"
+       x="0"
+       y="1004.3621"
+       ry="7.9999785" />
+    <path
+       style="opacity:1;fill:#313131;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="m 8,1004.3621 c -4.4319881,0 -8,3.568 -8,8 l 0,32 c 0,4.432 3.5680119,8 8,8 l 12,0 c 15.512,0 28,-16.948 28,-38 l 0,-2 c 0,-4.432 -3.568012,-8 -8,-8 l -32,0 z"
+       id="rect4173"
+       inkscape:connector-curvature="0" />
+    <g
+       id="g4300"
+       style="fill:#000000;fill-opacity:1;stroke:none"
+       transform="translate(0.5,0.5)">
+      <g
+         id="g4302"
+         style="fill:#000000;fill-opacity:1;stroke:none">
+        <path
+           sodipodi:nodetypes="scsccsssscccs"
+           d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 l 0,6.8586 -2,0 0,-6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 l -4.7957745,0 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 l 0,6.8914 -2,0 0,-9 z"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           id="path4304"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="sscsscsscsscssssssssss"
+           d="m 17.013073,1016.3621 4.973854,0 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 l 0,4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 l -4.973854,0 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 l 0,-4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 -4.795776,0 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 l 0,4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 l 4.795776,0 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 l 0,-4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           id="path4306"
+           inkscape:connector-curvature="0" />
+      </g>
+      <g
+         id="g4308"
+         style="fill:#000000;fill-opacity:1;stroke:none">
+        <path
+           sodipodi:nodetypes="cccccccc"
+           d="m 12,1036.9177 4.768114,-8.5556 2.231886,0 -6,11 -2,0 -6,-11 2.2318854,0 z"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           id="path4310"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="ccccccccccc"
+           d="m 29,1036.3621 0,-8 2,0 0,11 -2,0 -7,-8 0,8 -2,0 0,-11 2,0 z"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           id="path4312"
+           inkscape:connector-curvature="0" />
+        <path
+           sodipodi:nodetypes="cssssccscsscscc"
+           d="m 43,1030.3621 -8.897887,0 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 l 0,6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 l 8.897887,0 0,2 -8.972339,0 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 l 0,-6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 l 8.972339,0 z"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           id="path4314"
+           inkscape:connector-curvature="0" />
+      </g>
+    </g>
+    <g
+       id="g4291"
+       style="stroke:none">
+      <g
+         id="g4282"
+         style="stroke:none">
+        <path
+           inkscape:connector-curvature="0"
+           id="path4143"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 11.986926,1016.3621 c 0.554325,0 1.025987,0.2121 1.414987,0.6362 0.398725,0.4138 0.600909,0.9155 0.598087,1.5052 l 0,6.8586 -2,0 0,-6.8914 c 0,-0.072 -0.03404,-0.1086 -0.102113,-0.1086 l -4.7957745,0 C 7.0340375,1018.3621 7,1018.3983 7,1018.4707 l 0,6.8914 -2,0 0,-9 z"
+           sodipodi:nodetypes="scsccsssscccs" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4145"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#008000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 17.013073,1016.3621 4.973854,0 c 0.554325,0 1.025987,0.2121 1.414986,0.6362 0.398725,0.4138 0.598087,0.9155 0.598087,1.5052 l 0,4.7172 c 0,0.5897 -0.199362,1.0966 -0.598087,1.5207 -0.388999,0.4138 -0.860661,0.6207 -1.414986,0.6207 l -4.973854,0 c -0.554325,0 -1.030849,-0.2069 -1.429574,-0.6207 C 15.1945,1024.3173 15,1023.8104 15,1023.2207 l 0,-4.7172 c 0,-0.5897 0.1945,-1.0914 0.583499,-1.5052 0.398725,-0.4241 0.875249,-0.6362 1.429574,-0.6362 z m 4.884815,2 -4.795776,0 c -0.06808,0 -0.102112,0.036 -0.102112,0.1086 l 0,4.7828 c 0,0.072 0.03404,0.1086 0.102112,0.1086 l 4.795776,0 c 0.06807,0 0.102112,-0.036 0.102112,-0.1086 l 0,-4.7828 c 0,-0.072 -0.03404,-0.1086 -0.102112,-0.1086 z"
+           sodipodi:nodetypes="sscsscsscsscssssssssss" />
+      </g>
+      <g
+         id="g4286"
+         style="stroke:none">
+        <path
+           inkscape:connector-curvature="0"
+           id="path4147"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 12,1036.9177 4.768114,-8.5556 2.231886,0 -6,11 -2,0 -6,-11 2.2318854,0 z"
+           sodipodi:nodetypes="cccccccc" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4149"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 29,1036.3621 0,-8 2,0 0,11 -2,0 -7,-8 0,8 -2,0 0,-11 2,0 z"
+           sodipodi:nodetypes="ccccccccccc" />
+        <path
+           inkscape:connector-curvature="0"
+           id="path4151"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:medium;line-height:125%;font-family:Orbitron;-inkscape-font-specification:'Orbitron Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+           d="m 43,1030.3621 -8.897887,0 c -0.06808,0 -0.102113,0.036 -0.102113,0.1069 l 0,6.7862 c 0,0.071 0.03404,0.1069 0.102113,0.1069 l 8.897887,0 0,2 -8.972339,0 c -0.56405,0 -1.045437,-0.2037 -1.444162,-0.6111 C 32.1945,1038.3334 32,1037.8292 32,1037.2385 l 0,-6.7528 c 0,-0.5907 0.1945,-1.0898 0.583499,-1.4972 0.398725,-0.4176 0.880112,-0.6264 1.444162,-0.6264 l 8.972339,0 z"
+           sodipodi:nodetypes="cssssccscsscscc" />
+      </g>
+    </g>
+  </g>
+</svg>

+ 81 - 0
static/novnc/app/images/info.svg

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="info.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="15.720838"
+     inkscape:cy="8.9111233"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 12.5 3 A 9.5 9.4999914 0 0 0 3 12.5 A 9.5 9.4999914 0 0 0 12.5 22 A 9.5 9.4999914 0 0 0 22 12.5 A 9.5 9.4999914 0 0 0 12.5 3 z M 12.5 5 A 1.5 1.5000087 0 0 1 14 6.5 A 1.5 1.5000087 0 0 1 12.5 8 A 1.5 1.5000087 0 0 1 11 6.5 A 1.5 1.5000087 0 0 1 12.5 5 z M 10.521484 8.9785156 L 12.521484 8.9785156 A 1.50015 1.50015 0 0 1 14.021484 10.478516 L 14.021484 15.972656 A 1.50015 1.50015 0 0 1 14.498047 18.894531 C 14.498047 18.894531 13.74301 19.228309 12.789062 18.912109 C 12.312092 18.754109 11.776235 18.366625 11.458984 17.828125 C 11.141734 17.289525 11.021484 16.668469 11.021484 15.980469 L 11.021484 11.980469 L 10.521484 11.980469 A 1.50015 1.50015 0 1 1 10.521484 8.9804688 L 10.521484 8.9785156 z "
+       transform="translate(0,1027.3622)"
+       id="path4136" />
+  </g>
+</svg>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 75 - 0
static/novnc/app/images/keyboard.svg


+ 92 - 0
static/novnc/app/images/mouse_left.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="mouse_left.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="15.551515"
+     inkscape:cy="12.205592"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+       id="path6219" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+       id="path6217" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+       id="path6215" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+       id="rect6178" />
+  </g>
+</svg>

+ 92 - 0
static/novnc/app/images/mouse_middle.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="mouse_middle.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="15.551515"
+     inkscape:cy="12.205592"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+       id="path6219" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+       id="path6217" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+       id="path6215" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+       id="rect6178" />
+  </g>
+</svg>

+ 92 - 0
static/novnc/app/images/mouse_none.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="mouse_none.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="23.160825"
+     inkscape:cy="13.208262"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+       id="path6219" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+       id="path6217" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+       id="path6215" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+       id="rect6178" />
+  </g>
+</svg>

+ 92 - 0
static/novnc/app/images/mouse_right.svg

@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="mouse_right.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="15.551515"
+     inkscape:cy="12.205592"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 8,1030.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,2 5,0 0,-2 c 0,-1.4738 1.090393,-2.7071 2.5,-2.9492 l 0,-1.0508 -3.5,0 z"
+       id="path6219" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0068f6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 13.5,1030.3622 0,1.0508 c 1.409607,0.2421 2.5,1.4754 2.5,2.9492 l 0,2 5,0 0,-2 c 0,-2.1987 -1.801288,-4 -4,-4 l -3.5,0 z"
+       id="path6217" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 12,1033.3622 c -0.571311,0 -1,0.4287 -1,1 l 0,5 c 0,0.5713 0.428689,1 1,1 l 1,0 c 0.571311,0 1,-0.4287 1,-1 l 0,-5 c 0,-0.5713 -0.428689,-1 -1,-1 l -1,0 z"
+       id="path6215" />
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 4,1038.3622 0,3.5 c 0,4.1377 3.362302,7.5 7.5,7.5 l 2,0 c 4.137698,0 7.5,-3.3623 7.5,-7.5 l 0,-3.5 -5,0 0,1 c 0,1.6447 -1.355293,3 -3,3 l -1,0 c -1.644707,0 -3,-1.3553 -3,-3 l 0,-1 -5,0 z"
+       id="rect6178" />
+  </g>
+</svg>

+ 87 - 0
static/novnc/app/images/power.svg

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="power.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="9.3159849"
+     inkscape:cy="13.436208"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="M 9 6.8183594 C 6.3418164 8.1213032 4.5 10.849161 4.5 14 C 4.5 18.4065 8.0935666 22 12.5 22 C 16.906433 22 20.5 18.4065 20.5 14 C 20.5 10.849161 18.658184 8.1213032 16 6.8183594 L 16 9.125 C 17.514327 10.211757 18.5 11.984508 18.5 14 C 18.5 17.3256 15.825553 20 12.5 20 C 9.1744469 20 6.5 17.3256 6.5 14 C 6.5 11.984508 7.4856727 10.211757 9 9.125 L 9 6.8183594 z "
+       transform="translate(0,1027.3622)"
+       id="path6140" />
+    <path
+       style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 12.5,1031.8836 0,6.4786"
+       id="path6142"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cc" />
+  </g>
+</svg>

+ 76 - 0
static/novnc/app/images/settings.svg

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="settings.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.627417"
+     inkscape:cx="14.69683"
+     inkscape:cy="8.8039511"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 11 3 L 11 5.1601562 A 7.5 7.5 0 0 0 8.3671875 6.2460938 L 6.84375 4.7226562 L 4.7226562 6.84375 L 6.2480469 8.3691406 A 7.5 7.5 0 0 0 5.1523438 11 L 3 11 L 3 14 L 5.1601562 14 A 7.5 7.5 0 0 0 6.2460938 16.632812 L 4.7226562 18.15625 L 6.84375 20.277344 L 8.3691406 18.751953 A 7.5 7.5 0 0 0 11 19.847656 L 11 22 L 14 22 L 14 19.839844 A 7.5 7.5 0 0 0 16.632812 18.753906 L 18.15625 20.277344 L 20.277344 18.15625 L 18.751953 16.630859 A 7.5 7.5 0 0 0 19.847656 14 L 22 14 L 22 11 L 19.839844 11 A 7.5 7.5 0 0 0 18.753906 8.3671875 L 20.277344 6.84375 L 18.15625 4.7226562 L 16.630859 6.2480469 A 7.5 7.5 0 0 0 14 5.1523438 L 14 3 L 11 3 z M 12.5 10 A 2.5 2.5 0 0 1 15 12.5 A 2.5 2.5 0 0 1 12.5 15 A 2.5 2.5 0 0 1 10 12.5 A 2.5 2.5 0 0 1 12.5 10 z "
+       transform="translate(0,1027.3622)"
+       id="rect4967" />
+  </g>
+</svg>

+ 86 - 0
static/novnc/app/images/tab.svg

@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="tab.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="11.67335"
+     inkscape:cy="17.881696"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="m 3,1031.3622 0,8 2,0 0,-4 0,-4 -2,0 z m 2,4 4,4 0,-3 13,0 0,-2 -13,0 0,-3 -4,4 z"
+       id="rect5194"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path5211"
+       d="m 22,1048.3622 0,-8 -2,0 0,4 0,4 2,0 z m -2,-4 -4,-4 0,3 -13,0 0,2 13,0 0,3 4,-4 z"
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>

+ 90 - 0
static/novnc/app/images/toggleextrakeys.svg

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="extrakeys.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="15.234555"
+     inkscape:cy="9.9710826"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 8,1031.3622 c -2.1987124,0 -4,1.8013 -4,4 l 0,8.9996 c 0,2.1987 1.8012876,4 4,4 l 9,0 c 2.198712,0 4,-1.8013 4,-4 l 0,-8.9996 c 0,-2.1987 -1.801288,-4 -4,-4 z m 0,2 9,0 c 1.125307,0 2,0.8747 2,2 l 0,7.0005 c 0,1.1253 -0.874693,2 -2,2 l -9,0 c -1.1253069,0 -2,-0.8747 -2,-2 l 0,-7.0005 c 0,-1.1253 0.8746931,-2 2,-2 z"
+       id="rect5006"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ssssssssssssssssss" />
+    <g
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text4167"
+       transform="matrix(0.96021948,0,0,0.96021948,0.18921715,41.80659)">
+      <path
+         d="m 14.292969,1040.6791 -2.939453,0 -0.463868,1.3281 -1.889648,0 2.700195,-7.29 2.241211,0 2.700196,7.29 -1.889649,0 -0.458984,-1.3281 z m -2.470703,-1.3526 1.99707,0 -0.996094,-2.9004 -1.000976,2.9004 z"
+         id="path4172"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>

+ 81 - 0
static/novnc/app/images/warning.svg

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="25"
+   height="25"
+   viewBox="0 0 25 25"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="warning.svg"
+   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
+   inkscape:export-xdpi="90"
+   inkscape:export-ydpi="90">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#959595"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="16.457343"
+     inkscape:cy="12.179552"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox-edge-midpoints="true"
+     inkscape:object-paths="true"
+     showguides="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     inkscape:snap-smooth-nodes="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-intersection-paths="true"
+     inkscape:snap-nodes="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4136" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1027.3622)">
+    <path
+       style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="M 12.513672 3.0019531 C 11.751609 2.9919531 11.052563 3.4242687 10.710938 4.1054688 L 3.2109375 19.105469 C 2.5461937 20.435369 3.5132277 21.9999 5 22 L 20 22 C 21.486772 21.9999 22.453806 20.435369 21.789062 19.105469 L 14.289062 4.1054688 C 13.951849 3.4330688 13.265888 3.0066531 12.513672 3.0019531 z M 12.478516 6.9804688 A 1.50015 1.50015 0 0 1 14 8.5 L 14 14.5 A 1.50015 1.50015 0 1 1 11 14.5 L 11 8.5 A 1.50015 1.50015 0 0 1 12.478516 6.9804688 z M 12.5 17 A 1.5 1.5 0 0 1 14 18.5 A 1.5 1.5 0 0 1 12.5 20 A 1.5 1.5 0 0 1 11 18.5 A 1.5 1.5 0 0 1 12.5 17 z "
+       transform="translate(0,1027.3622)"
+       id="path4208" />
+  </g>
+</svg>

+ 70 - 0
static/novnc/app/locale/de.json

@@ -0,0 +1,70 @@
+{
+    "Connecting...": "Verbinden...",
+    "Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ",
+    "Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ",
+    "Disconnecting...": "Verbindung trennen...",
+    "Disconnected": "Verbindung zum Server getrennt",
+    "Must set host and port": "Richten Sie Server und Port ein",
+    "Reconnecting...": "Verbindung wiederherstellen...",
+    "Password is required": "Passwort ist erforderlich",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt",
+    "Disconnect timeout": "Zeitüberschreitung beim Trennen",
+    "noVNC encountered an error:": "Ein Fehler ist aufgetreten:",
+    "Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen",
+    "Move/Drag Viewport": "Ansichtsfenster verschieben/ziehen",
+    "viewport drag": "Ansichtsfenster ziehen",
+    "Active Mouse Button": "Aktive Maustaste",
+    "No mousebutton": "Keine Maustaste",
+    "Left mousebutton": "Linke Maustaste",
+    "Middle mousebutton": "Mittlere Maustaste",
+    "Right mousebutton": "Rechte Maustaste",
+    "Keyboard": "Tastatur",
+    "Show Keyboard": "Tastatur anzeigen",
+    "Extra keys": "Zusatztasten",
+    "Show Extra Keys": "Zusatztasten anzeigen",
+    "Ctrl": "Strg",
+    "Toggle Ctrl": "Strg umschalten",
+    "Alt": "Alt",
+    "Toggle Alt": "Alt umschalten",
+    "Send Tab": "Tab senden",
+    "Tab": "Tab",
+    "Esc": "Esc",
+    "Send Escape": "Escape senden",
+    "Ctrl+Alt+Del": "Strg+Alt+Entf",
+    "Send Ctrl-Alt-Del": "Strg+Alt+Entf senden",
+    "Shutdown/Reboot": "Herunterfahren/Neustarten",
+    "Shutdown/Reboot...": "Herunterfahren/Neustarten...",
+    "Power": "Energie",
+    "Shutdown": "Herunterfahren",
+    "Reboot": "Neustarten",
+    "Reset": "Zurücksetzen",
+    "Clipboard": "Zwischenablage",
+    "Clear": "Löschen",
+    "Fullscreen": "Vollbild",
+    "Settings": "Einstellungen",
+    "Shared Mode": "Geteilter Modus",
+    "View Only": "Nur betrachten",
+    "Clip to Window": "Auf Fenster begrenzen",
+    "Scaling Mode:": "Skalierungsmodus:",
+    "None": "Keiner",
+    "Local Scaling": "Lokales skalieren",
+    "Local Downscaling": "Lokales herunterskalieren",
+    "Remote Resizing": "Serverseitiges skalieren",
+    "Advanced": "Erweitert",
+    "True Color": "True Color",
+    "Local Cursor": "Lokaler Mauszeiger",
+    "Repeater ID:": "Repeater ID:",
+    "WebSocket": "WebSocket",
+    "Encrypt": "Verschlüsselt",
+    "Host:": "Server:",
+    "Port:": "Port:",
+    "Path:": "Pfad:",
+    "Automatic Reconnect": "Automatisch wiederverbinden",
+    "Reconnect Delay (ms):": "Wiederverbindungsverzögerung (ms):",
+    "Logging:": "Protokollierung:",
+    "Disconnect": "Verbindung trennen",
+    "Connect": "Verbinden",
+    "Password:": "Passwort:",
+    "Cancel": "Abbrechen",
+    "Canvas not supported.": "Canvas nicht unterstützt."
+}

+ 70 - 0
static/novnc/app/locale/el.json

@@ -0,0 +1,70 @@
+{
+    "Connecting...": "Συνδέεται...",
+    "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
+    "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
+    "Disconnecting...": "Aποσυνδέεται...",
+    "Disconnected": "Αποσυνδέθηκε",
+    "Must set host and port": "Πρέπει να οριστεί το όνομα και η πόρτα του διακομιστή",
+    "Password is required": "Απαιτείται ο κωδικός πρόσβασης",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης σε πλήρη οθόνη στον IE",
+    "Disconnect timeout": "Παρέλευση χρονικού ορίου αποσύνδεσης",
+    "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
+    "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
+    "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
+    "viewport drag": "σύρσιμο θεατού πεδίου",
+    "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
+    "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
+    "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
+    "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
+    "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
+    "Keyboard": "Πληκτρολόγιο",
+    "Show Keyboard": "Εμφάνιση Πληκτρολογίου",
+    "Extra keys": "Επιπλέον πλήκτρα",
+    "Show Extra Keys": "Εμφάνιση Επιπλέον Πλήκτρων",
+    "Ctrl": "Ctrl",
+    "Toggle Ctrl": "Εναλλαγή Ctrl",
+    "Alt": "Alt",
+    "Toggle Alt": "Εναλλαγή Alt",
+    "Send Tab": "Αποστολή Tab",
+    "Tab": "Tab",
+    "Esc": "Esc",
+    "Send Escape": "Αποστολή Escape",
+    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+    "Send Ctrl-Alt-Del": "Αποστολή Ctrl-Alt-Del",
+    "Shutdown/Reboot": "Κλείσιμο/Επανεκκίνηση",
+    "Shutdown/Reboot...": "Κλείσιμο/Επανεκκίνηση...",
+    "Power": "Απενεργοποίηση",
+    "Shutdown": "Κλείσιμο",
+    "Reboot": "Επανεκκίνηση",
+    "Reset": "Επαναφορά",
+    "Clipboard": "Πρόχειρο",
+    "Clear": "Καθάρισμα",
+    "Fullscreen": "Πλήρης Οθόνη",
+    "Settings": "Ρυθμίσεις",
+    "Encrypt": "Κρυπτογράφηση",
+    "True Color": "Πραγματικά Χρώματα",
+    "Local Cursor": "Τοπικός Δρομέας",
+    "Clip to Window": "Αποκοπή στο όριο του Παράθυρου",
+    "Shared Mode": "Κοινόχρηστη Λειτουργία",
+    "View Only": "Μόνο Θέαση",
+    "Path:": "Διαδρομή:",
+    "Scaling Mode:": "Λειτουργία Κλιμάκωσης:",
+    "None": "Καμία",
+    "Local Scaling": "Τοπική Κλιμάκωση",
+    "Local Downscaling": "Τοπική Συρρίκνωση",
+    "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
+    "Repeater ID:": "Repeater ID:",
+    "Style:": "Στυλ:",
+    "default": "προεπιλεγμένο",
+    "Logging:": "Καταγραφή:",
+    "Apply": "Εφαρμογή",
+    "Connect": "Σύνδεση",
+    "Disconnect": "Αποσύνδεση",
+    "Connection": "Σύνδεση",
+    "Host:": "Όνομα διακομιστή:",
+    "Port:": "Πόρτα διακομιστή:",
+    "Password:": "Κωδικός Πρόσβασης:",
+    "Token:": "Διακριτικό:",
+    "Send Password": "Αποστολή Κωδικού Πρόσβασης",
+    "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas"
+}

+ 11 - 0
static/novnc/app/locale/nl.json

@@ -0,0 +1,11 @@
+{
+    "Connecting...": "Verbinden...",
+    "Connected (encrypted) to ": "Verbonden (versleuteld) met ",
+    "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
+    "Disconnecting...": "Verbinding verbreken...",
+    "Disconnected": "Verbinding verbroken",
+    "Must set host and port": "Host en poort moeten worden ingesteld",
+    "Password is required": "Wachtwoord is vereist",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus in IE niet worden ondersteund",
+    "Disconnect timeout": "Timeout tijdens verbreken van verbinding"
+}

+ 70 - 0
static/novnc/app/locale/sv.json

@@ -0,0 +1,70 @@
+{
+    "Connecting...": "Ansluter...",
+    "Connected (encrypted) to ": "Ansluten (krypterat) till ",
+    "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
+    "Disconnecting...": "Kopplar ner...",
+    "Disconnected": "Frånkopplad",
+    "Must set host and port": "Du måste specifiera en host och port",
+    "Password is required": "Lösenord krävs",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Tvingar 'Clipping mode' eftersom skrollning inte stödjs av IE i fullskärm",
+    "Disconnect timeout": "Det tog för lång tid att koppla ner",
+    "noVNC encountered an error:": "noVNC stötte på ett problem:",
+    "Hide/Show the control bar": "Göm/Visa kontrollbaren",
+    "Move/Drag Viewport": "Flytta/Dra Vyn",
+    "viewport drag": "dra vy",
+    "Active Mouse Button": "Aktiv musknapp",
+    "No mousebutton": "Ingen musknapp",
+    "Left mousebutton": "Vänster musknapp",
+    "Middle mousebutton": "Mitten-musknapp",
+    "Right mousebutton": "Höger musknapp",
+    "Keyboard": "Tangentbord",
+    "Show Keyboard": "Visa Tangentbord",
+    "Extra keys": "Extraknappar",
+    "Show Extra Keys": "Visa Extraknappar",
+    "Ctrl": "Ctrl",
+    "Toggle Ctrl": "Växla Ctrl",
+    "Alt": "Alt",
+    "Toggle Alt": "Växla Alt",
+    "Send Tab": "Skicka Tab",
+    "Tab": "Tab",
+    "Esc": "Esc",
+    "Send Escape": "Skicka Escape",
+    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+    "Send Ctrl-Alt-Del": "Skicka Ctrl-Alt-Del",
+    "Shutdown/Reboot": "Stäng av/Boota om",
+    "Shutdown/Reboot...": "Stäng av/Boota om...",
+    "Power": "Ström",
+    "Shutdown": "Stäng av",
+    "Reboot": "Boota om",
+    "Reset": "Återställ",
+    "Clipboard": "Urklipp",
+    "Clear": "Rensa",
+    "Fullscreen": "Fullskärm",
+    "Settings": "Inställningar",
+    "Encrypt": "Kryptera",
+    "True Color": "Fullfärg",
+    "Local Cursor": "Lokal Muspekare",
+    "Clip to Window": "Begränsa till Fönster",
+    "Shared Mode": "Delat Läge",
+    "View Only": "Endast Visning",
+    "Path:": "Sökväg:",
+    "Scaling Mode:": "Skalningsläge:",
+    "None": "Ingen",
+    "Local Scaling": "Lokal Skalning",
+    "Local Downscaling": "Lokal Nedskalning",
+    "Remote Resizing": "Ändra Storlek",
+    "Repeater ID:": "Repeater-ID:",
+    "Style:": "Stil:",
+    "default": "standard",
+    "Logging:": "Loggning:",
+    "Apply": "Verkställ",
+    "Connect": "Anslut",
+    "Disconnect": "Koppla från",
+    "Connection": "Uppkoppling",
+    "Host:": "Värd:",
+    "Port:": "Port:",
+    "Password:": "Lösenord:",
+    "Token:": "Token:",
+    "Send Password": "Skicka Lösenord",
+    "Canvas not supported.": "Canvas stöds ej"
+}

+ 4 - 0
static/novnc/app/sounds/CREDITS

@@ -0,0 +1,4 @@
+bell
+        Copyright: Dr. Richard Boulanger et al
+        URL: http://www.archive.org/details/Berklee44v12
+        License: CC-BY Attribution 3.0 Unported

BIN
static/novnc/app/sounds/bell.mp3


BIN
static/novnc/app/sounds/bell.oga


BIN
static/novnc/app/styles/Orbitron700.ttf


BIN
static/novnc/app/styles/Orbitron700.woff


+ 916 - 0
static/novnc/app/styles/base.css

@@ -0,0 +1,916 @@
+/*
+ * noVNC base CSS
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2016 Pierre Ossman for Cendio AB
+ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+ */
+
+/*
+ * Z index layers:
+ *
+ * 0: Main screen
+ * 10: Control bar
+ * 50: Transition blocker
+ * 60: Connection popups
+ * 100: Status bar
+ * ...
+ * 1000: Javascript crash
+ * ...
+ * 10000: Max (used for polyfills)
+ */
+
+body {
+  margin:0;
+  padding:0;
+  font-family: Helvetica;
+  /*Background image with light grey curve.*/
+  background-color:#494949;
+  background-repeat:no-repeat;
+  background-position:right bottom;
+  height:100%;
+  touch-action: none;
+}
+
+html {
+  height:100%;
+}
+
+.noVNC_only_touch.noVNC_hidden {
+  display: none;
+}
+
+.noVNC_disabled {
+  color: rgb(128, 128, 128);
+}
+
+/* ----------------------------------------
+ * Spinner
+ * ----------------------------------------
+ */
+
+.noVNC_spinner {
+  position: relative;
+}
+.noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after {
+  width: 10px;
+  height: 10px;
+  border-radius: 2px;
+  box-shadow: -60px 10px 0 rgba(255, 255, 255, 0);
+  animation: noVNC_spinner 1.0s linear infinite;
+}
+.noVNC_spinner::before {
+  content: "";
+  position: absolute;
+  left: 0px;
+  top: 0px;
+  animation-delay: -0.1s;
+}
+.noVNC_spinner::after {
+  content: "";
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  animation-delay: 0.1s;
+}
+@keyframes noVNC_spinner {
+  0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; }
+  25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; }
+  50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }
+}
+
+/* ----------------------------------------
+ * Input Elements
+ * ----------------------------------------
+ */
+
+input[type=input], input[type=password], input[type=number],
+input:not([type]), textarea {
+  /* Disable default rendering */
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  background: none;
+
+  margin: 2px;
+  padding: 2px;
+  border: 1px solid rgb(192, 192, 192);
+  border-radius: 5px;
+  color: black;
+  background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
+}
+
+input[type=button], input[type=submit], select {
+  /* Disable default rendering */
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  background: none;
+
+  margin: 2px;
+  padding: 2px;
+  border: 1px solid rgb(192, 192, 192);
+  border-bottom-width: 2px;
+  border-radius: 5px;
+  color: black;
+  background: linear-gradient(to top, rgb(255, 255, 255), rgb(240, 240, 240));
+
+  /* This avoids it jumping around when :active */
+  vertical-align: middle;
+}
+
+input[type=button], input[type=submit] {
+  padding-left: 20px;
+  padding-right: 20px;
+}
+
+option {
+  color: black;
+  background: white;
+}
+
+input[type=input]:focus, input[type=password]:focus,
+input:not([type]):focus, input[type=button]:focus,
+input[type=submit]:focus,
+textarea:focus, select:focus {
+  box-shadow: 0px 0px 3px rgba(74, 144, 217, 0.5);
+  border-color: rgb(74, 144, 217);
+  outline: none;
+}
+
+input[type=button]::-moz-focus-inner,
+input[type=submit]::-moz-focus-inner {
+  border: none;
+}
+
+input[type=input]:disabled, input[type=password]:disabled,
+input:not([type]):disabled, input[type=button]:disabled,
+input[type=submit]:disabled, input[type=number]:disabled,
+textarea:disabled, select:disabled {
+  color: rgb(128, 128, 128);
+  background: rgb(240, 240, 240);
+}
+
+input[type=button]:active, input[type=submit]:active,
+select:active {
+  border-bottom-width: 1px;
+  margin-top: 3px;
+}
+
+:root:not(.noVNC_touch) input[type=button]:hover:not(:disabled),
+:root:not(.noVNC_touch) input[type=submit]:hover:not(:disabled),
+:root:not(.noVNC_touch) select:hover:not(:disabled) {
+  background: linear-gradient(to top, rgb(255, 255, 255), rgb(250, 250, 250));
+}
+
+/* ----------------------------------------
+ * WebKit centering hacks
+ * ----------------------------------------
+ */
+
+.noVNC_center {
+  /*
+   * This is a workaround because webkit misrenders transforms and
+   * uses non-integer coordinates, resulting in blurry content.
+   * Ideally we'd use "top: 50%; transform: translateY(-50%);" on
+   * the objects instead.
+   */
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+}
+.noVNC_center > * {
+  pointer-events: auto;
+}
+.noVNC_vcenter {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  position: fixed;
+  top: 0;
+  left: 0;
+  height: 100%;
+  pointer-events: none;
+}
+.noVNC_vcenter > * {
+  pointer-events: auto;
+}
+
+/* ----------------------------------------
+ * Layering
+ * ----------------------------------------
+ */
+
+.noVNC_connect_layer {
+  z-index: 60;
+}
+
+/* ----------------------------------------
+ * Fallback error
+ * ----------------------------------------
+ */
+
+#noVNC_fallback_error {
+  z-index: 1000;
+  visibility: hidden;
+}
+#noVNC_fallback_error.noVNC_open {
+  visibility: visible;
+}
+
+#noVNC_fallback_error > div {
+  max-width: 90%;
+  padding: 15px;
+
+  transition: 0.5s ease-in-out;
+
+  transform: translateY(-50px);
+  opacity: 0;
+
+  text-align: center;
+  font-weight: bold;
+  color: #fff;
+
+  border-radius: 10px;
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+  background: rgba(200,55,55,0.8);
+}
+#noVNC_fallback_error.noVNC_open > div {
+  transform: translateY(0);
+  opacity: 1;
+}
+
+#noVNC_fallback_errormsg {
+  font-weight: normal;
+}
+
+#noVNC_fallback_errormsg .noVNC_message {
+  display: inline-block;
+  text-align: left;
+  font-family: monospace;
+  white-space: pre-wrap;
+}
+
+#noVNC_fallback_error .noVNC_location {
+  font-style: italic;
+  font-size: 0.8em;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+#noVNC_fallback_error .noVNC_stack {
+  padding: 10px;
+  margin: 10px;
+  font-size: 0.8em;
+  text-align: left;
+  font-family: monospace;
+  white-space: pre-wrap;
+  border: 1px solid rgba(0, 0, 0, 0.5);
+  background: rgba(0, 0, 0, 0.2);
+}
+
+/* ----------------------------------------
+ * Control Bar
+ * ----------------------------------------
+ */
+
+#noVNC_control_bar_anchor {
+  /* The anchor is needed to get z-stacking to work */
+  position: fixed;
+  z-index: 10;
+
+  transition: 0.5s ease-in-out;
+
+  /* Edge misrenders animations wihthout this */
+  transform: translateX(0);
+}
+:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
+  opacity: 0.8;
+}
+#noVNC_control_bar_anchor.noVNC_right {
+  left: auto;
+  right: 0;
+}
+
+#noVNC_control_bar {
+  position: relative;
+  left: -100%;
+
+  transition: 0.5s ease-in-out;
+
+  background-color: rgb(110, 132, 163);
+  border-radius: 0 10px 10px 0;
+
+}
+#noVNC_control_bar.noVNC_open {
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+  left: 0;
+}
+#noVNC_control_bar::before {
+  /* This extra element is to get a proper shadow */
+  content: "";
+  position: absolute;
+  z-index: -1;
+  height: 100%;
+  width: 30px;
+  left: -30px;
+  transition: box-shadow 0.5s ease-in-out;
+}
+#noVNC_control_bar.noVNC_open::before {
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+.noVNC_right #noVNC_control_bar {
+  left: 100%;
+  border-radius: 10px 0 0 10px;
+}
+.noVNC_right #noVNC_control_bar.noVNC_open {
+  left: 0;
+}
+.noVNC_right #noVNC_control_bar::before {
+  visibility: hidden;
+}
+
+#noVNC_control_bar_handle {
+  position: absolute;
+  left: -15px;
+  top: 0;
+  transform: translateY(35px);
+  width: calc(100% + 30px);
+  height: 50px;
+  z-index: -1;
+  cursor: pointer;
+  border-radius: 5px;
+  background-color: rgb(83, 99, 122);
+  background-image: url("../images/handle_bg.svg");
+  background-repeat: no-repeat;
+  background-position: right;
+  box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
+}
+#noVNC_control_bar_handle:after {
+  content: "";
+  transition: transform 0.5s ease-in-out;
+  background: url("../images/handle.svg");
+  position: absolute;
+  top: 22px; /* (50px-6px)/2 */
+  right: 5px;
+  width: 5px;
+  height: 6px;
+}
+#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
+  transform: translateX(1px) rotate(180deg);
+}
+:root:not(.noVNC_connected) #noVNC_control_bar_handle {
+  display: none;
+}
+.noVNC_right #noVNC_control_bar_handle {
+  background-position: left;
+}
+.noVNC_right #noVNC_control_bar_handle:after {
+  left: 5px;
+  right: 0;
+  transform: translateX(1px) rotate(180deg);
+}
+.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
+  transform: none;
+}
+#noVNC_control_bar_handle div {
+  position: absolute;
+  right: -35px;
+  top: 0;
+  width: 50px;
+  height: 50px;
+}
+:root:not(.noVNC_touch) #noVNC_control_bar_handle div {
+  display: none;
+}
+.noVNC_right #noVNC_control_bar_handle div {
+  left: -35px;
+  right: auto;
+}
+
+#noVNC_control_bar .noVNC_scroll {
+  max-height: 100vh; /* Chrome is buggy with 100% */
+  overflow-x: hidden;
+  overflow-y: auto;
+  padding: 0 10px 0 5px;
+}
+.noVNC_right #noVNC_control_bar .noVNC_scroll {
+  padding: 0 5px 0 10px;
+}
+
+/* Control bar hint */
+#noVNC_control_bar_hint {
+  position: fixed;
+  left: calc(100vw - 50px);
+  right: auto;
+  width: 100px;
+  height: 50%;
+  max-height: 600px;
+
+  visibility: hidden;
+  opacity: 0;
+  transition: 0.2s ease-in-out;
+  background: transparent;
+  box-shadow: 0 0 10px black, inset 0 0 10px 10px rgba(110, 132, 163, 0.8);
+  border-radius: 10px;
+  transition-delay: 0s;
+  transform: scale(0);
+}
+#noVNC_control_bar_anchor.noVNC_right #noVNC_control_bar_hint{
+  left: auto;
+  right: calc(100vw - 50px);
+}
+#noVNC_control_bar_hint.noVNC_active {
+  visibility: visible;
+  opacity: 1;
+  transition-delay: 0.2s;
+  transform: scale(1);
+}
+
+/* General button style */
+.noVNC_button {
+  display: block;
+  padding: 4px 4px;
+  margin: 10px 0;
+  vertical-align: middle;
+  border:1px solid rgba(255, 255, 255, 0.2);
+  border-radius: 6px;
+}
+.noVNC_button.noVNC_selected {
+  border-color: rgba(0, 0, 0, 0.8);
+  background: rgba(0, 0, 0, 0.5);
+}
+.noVNC_button:disabled {
+  opacity: 0.4;
+}
+.noVNC_button:focus {
+  outline: none;
+}
+.noVNC_button:active {
+  padding-top: 5px;
+  padding-bottom: 3px;
+}
+:root:not(.noVNC_touch) .noVNC_button.noVNC_selected:hover {
+  border-color: rgba(0, 0, 0, 0.4);
+  background: rgba(0, 0, 0, 0.2);
+}
+:root:not(.noVNC_touch) .noVNC_button:hover {
+  background: rgba(255, 255, 255, 0.2);
+}
+.noVNC_button.noVNC_hidden {
+  display: none;
+}
+
+/* Panels */
+.noVNC_panel {
+  transform: translateX(25px);
+
+  transition: 0.5s ease-in-out;
+
+  max-height: 100vh; /* Chrome is buggy with 100% */
+  overflow-x: hidden;
+  overflow-y: auto;
+
+  visibility: hidden;
+  opacity: 0;
+
+  padding: 15px;
+
+  background: #fff;
+  border-radius: 10px;
+  color: #000;
+  border: 2px solid #E0E0E0;
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+.noVNC_panel.noVNC_open {
+  visibility: visible;
+  opacity: 1;
+  transform: translateX(75px);
+}
+.noVNC_right .noVNC_vcenter {
+  left: auto;
+  right: 0;
+}
+.noVNC_right .noVNC_panel {
+  transform: translateX(-25px);
+}
+.noVNC_right .noVNC_panel.noVNC_open {
+  transform: translateX(-75px);
+}
+
+.noVNC_panel hr {
+  border: none;
+  border-top: 1px solid rgb(192, 192, 192);
+}
+
+.noVNC_panel label {
+  display: block;
+  white-space: nowrap;
+}
+
+.noVNC_panel .noVNC_heading {
+  background-color: rgb(110, 132, 163);
+  border-radius: 5px;
+  padding: 5px;
+  /* Compensate for padding in image */
+  padding-right: 8px;
+  color: white;
+  font-size: 20px;
+  margin-bottom: 10px;
+  white-space: nowrap;
+}
+.noVNC_panel .noVNC_heading img {
+  vertical-align: bottom;
+}
+
+.noVNC_submit {
+  float: right;
+}
+
+/* Expanders */
+.noVNC_expander {
+  cursor: pointer;
+}
+.noVNC_expander::before {
+  content: url("../images/expander.svg");
+  display: inline-block;
+  margin-right: 5px;
+  transition: 0.2s ease-in-out;
+}
+.noVNC_expander.noVNC_open::before {
+  transform: rotateZ(90deg);
+}
+.noVNC_expander ~ * {
+  margin: 5px;
+  margin-left: 10px;
+  padding: 5px;
+  background: rgba(0, 0, 0, 0.05);
+  border-radius: 5px;
+}
+.noVNC_expander:not(.noVNC_open) ~ * {
+  display: none;
+}
+
+/* Control bar content */
+
+#noVNC_control_bar .noVNC_logo {
+  font-size: 13px;
+}
+
+:root:not(.noVNC_connected) #noVNC_view_drag_button {
+  display: none;
+}
+
+/* noVNC Touch Device only buttons */
+:root:not(.noVNC_connected) #noVNC_mobile_buttons {
+  display: none;
+}
+:root:not(.noVNC_touch) #noVNC_mobile_buttons {
+  display: none;
+}
+
+/* Extra manual keys */
+:root:not(.noVNC_connected) #noVNC_extra_keys {
+  display: none;
+}
+
+#noVNC_modifiers {
+  background-color: rgb(92, 92, 92);
+  border: none;
+  padding: 0 10px;
+}
+
+/* XVP Shutdown/Reboot */
+:root:not(.noVNC_connected) #noVNC_xvp_button {
+  display: none;
+}
+#noVNC_xvp {
+}
+#noVNC_xvp_buttons {
+  display: none;
+}
+
+#noVNC_xvp input[type=button] {
+  width: 100%;
+}
+
+/* Clipboard */
+:root:not(.noVNC_connected) #noVNC_clipboard_button {
+  display: none;
+}
+#noVNC_clipboard {
+  /* Full screen, minus padding and left and right margins */
+  max-width: calc(100vw - 2*15px - 75px - 25px);
+}
+#noVNC_clipboard_text {
+  width: 500px;
+  max-width: 100%;
+}
+
+/* Settings */
+#noVNC_settings {
+}
+#noVNC_settings ul {
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+}
+#noVNC_setting_port {
+  width: 80px;
+}
+#noVNC_setting_path {
+  width: 100px;
+}
+
+/* Connection Controls */
+:root:not(.noVNC_connected) #noVNC_disconnect_button {
+  display: none;
+}
+
+/* ----------------------------------------
+ * Status Dialog
+ * ----------------------------------------
+ */
+
+#noVNC_status {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  z-index: 100;
+  transform: translateY(-100%);
+
+  cursor: pointer;
+
+  transition: 0.5s ease-in-out;
+
+  visibility: hidden;
+  opacity: 0;
+
+  padding: 5px;
+
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  align-content: center;
+
+  line-height: 25px;
+  word-wrap: break-word;
+  color: #fff;
+
+  border-bottom: 1px solid rgba(0, 0, 0, 0.9);
+}
+#noVNC_status.noVNC_open {
+  transform: translateY(0);
+  visibility: visible;
+  opacity: 1;
+}
+
+#noVNC_status::before {
+  content: "";
+  display: inline-block;
+  width: 25px;
+  height: 25px;
+  margin-right: 5px;
+}
+
+#noVNC_status.noVNC_status_normal {
+  background: rgba(128,128,128,0.9);
+}
+#noVNC_status.noVNC_status_normal::before {
+  content: url("../images/info.svg") " ";
+}
+#noVNC_status.noVNC_status_error {
+  background: rgba(200,55,55,0.9);
+}
+#noVNC_status.noVNC_status_error::before {
+  content: url("../images/error.svg") " ";
+}
+#noVNC_status.noVNC_status_warn {
+  background: rgba(180,180,30,0.9);
+}
+#noVNC_status.noVNC_status_warn::before {
+  content: url("../images/warning.svg") " ";
+}
+
+/* ----------------------------------------
+ * Connect Dialog
+ * ----------------------------------------
+ */
+
+#noVNC_connect_dlg {
+  transition: 0.5s ease-in-out;
+
+  transform: scale(0, 0);
+  visibility: hidden;
+  opacity: 0;
+}
+#noVNC_connect_dlg.noVNC_open {
+  transform: scale(1, 1);
+  visibility: visible;
+  opacity: 1;
+}
+#noVNC_connect_dlg .noVNC_logo {
+  transition: 0.5s ease-in-out;
+  padding: 10px;
+  margin-bottom: 10px;
+
+  font-size: 80px;
+  text-align: center;
+
+  border-radius: 5px;
+}
+@media (max-width: 440px) {
+  #noVNC_connect_dlg {
+    max-width: calc(100vw - 100px);
+  }
+  #noVNC_connect_dlg .noVNC_logo {
+    font-size: calc(25vw - 30px);
+  }
+}
+#noVNC_connect_button {
+  cursor: pointer;
+
+  padding: 10px;
+
+  color: white;
+  background-color: rgb(110, 132, 163);
+  border-radius: 12px;
+
+  text-align: center;
+  font-size: 20px;
+
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+#noVNC_connect_button div {
+  margin: 2px;
+  padding: 5px 30px;
+  border: 1px solid rgb(83, 99, 122);
+  border-bottom-width: 2px;
+  border-radius: 5px;
+  background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147));
+
+  /* This avoids it jumping around when :active */
+  vertical-align: middle;
+}
+#noVNC_connect_button div:active {
+  border-bottom-width: 1px;
+  margin-top: 3px;
+}
+:root:not(.noVNC_touch) #noVNC_connect_button div:hover {
+  background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155));
+}
+
+#noVNC_connect_button img {
+  vertical-align: bottom;
+  height: 1.3em;
+}
+
+/* ----------------------------------------
+ * Password Dialog
+ * ----------------------------------------
+ */
+
+#noVNC_password_dlg {
+  position: relative;
+
+  transform: translateY(-50px);
+}
+#noVNC_password_dlg.noVNC_open {
+  transform: translateY(0);
+}
+#noVNC_password_dlg ul {
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+}
+
+/* ----------------------------------------
+ * Main Area
+ * ----------------------------------------
+ */
+
+/* Transition screen */
+#noVNC_transition {
+  display: none;
+
+  position: fixed;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+
+  color: white;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 50;
+
+  /*display: flex;*/
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+:root.noVNC_loading #noVNC_transition,
+:root.noVNC_connecting #noVNC_transition,
+:root.noVNC_disconnecting #noVNC_transition,
+:root.noVNC_reconnecting #noVNC_transition {
+  display: flex;
+}
+:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button {
+  display: none;
+}
+#noVNC_transition_text {
+  font-size: 1.5em;
+}
+
+/* Main container */
+#noVNC_container {
+  width: 100%;
+  height: 100%;
+  background-color: #313131;
+  border-bottom-right-radius: 800px 600px;
+  /*border-top-left-radius: 800px 600px;*/
+}
+
+#noVNC_keyboardinput {
+  width: 1px;
+  height: 1px;
+  background-color: #fff;
+  color: #fff;
+  border: 0;
+  position: absolute;
+  left: -40px;
+  z-index: -1;
+  ime-mode: disabled;
+}
+
+/* HTML5 Canvas */
+#noVNC_screen {
+  display: flex;
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  background-color: rgb(40, 40, 40);
+}
+:root:not(.noVNC_connected) #noVNC_screen {
+  display: none;
+}
+
+/* Do not set width/height for VNC_canvas or incorrect
+ * scaling will occur. Canvas size depends on remote VNC
+ * settings and noVNC settings. */
+#noVNC_canvas {
+  margin: auto;
+  /* IE miscalculates width without this :( */
+  flex-shrink: 0;
+}
+
+/*Default noVNC logo.*/
+/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
+@font-face {
+  font-family: 'Orbitron';
+  font-style: normal;
+  font-weight: 700;
+  src: local('?'), url('Orbitron700.woff') format('woff'),
+                   url('Orbitron700.ttf') format('truetype');
+}
+
+.noVNC_logo {
+  color:yellow;
+  font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
+  line-height:90%;
+  text-shadow: 0.1em 0.1em 0 black;
+}
+.noVNC_logo span{
+  color:green;
+}
+
+#noVNC_bell {
+  display: none;
+}
+
+/* ----------------------------------------
+ * Media sizing
+ * ----------------------------------------
+ */
+
+@media screen and (max-width: 640px){
+  #noVNC_logo {
+    font-size: 150px;
+  }
+}
+
+@media screen and (min-width: 321px) and (max-width: 480px) {
+  #noVNC_logo {
+    font-size: 110px;
+  }
+}
+
+@media screen and (max-width: 320px) {
+  #noVNC_logo {
+    font-size: 90px;
+  }
+}

+ 89 - 0
static/novnc/app/styles/lite.css

@@ -0,0 +1,89 @@
+/*
+ * noVNC auto CSS
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+ */
+
+body {
+  margin:0;
+  padding:0;
+  font-family: Helvetica;
+  /*Background image with light grey curve.*/
+  background-color:#494949;
+  background-repeat:no-repeat;
+  background-position:right bottom;
+  height:100%;
+}
+
+html {
+  height:100%;
+}
+
+#noVNC_container {
+  display: table;
+  width:100%;
+  height:100%;
+  background-color:#313131;
+  border-bottom-right-radius: 800px 600px;
+  /*border-top-left-radius: 800px 600px;*/
+}
+
+#noVNC_status {
+  font-size: 12px;
+  padding-top: 4px;
+  height:32px;
+  text-align: center;
+  font-weight: bold;
+  color: #fff;
+  z-index: 0;
+  position: absolute;
+  width: 100%;
+  margin-left: 0px;
+}
+
+.noVNC_status_normal {
+  background: #b2bdcd; /* Old browsers */
+  background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+  background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+}
+
+.noVNC_status_error {
+  background: #f04040; /* Old browsers */
+  background: -moz-linear-gradient(top, #f04040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+  background: linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+}
+
+.noVNC_status_warn {
+  background: #f0f040; /* Old browsers */
+  background: -moz-linear-gradient(top, #f0f040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+  background: -webkit-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+  background: -o-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+  background: -ms-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+  background: linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+}
+
+#noVNC_buttons {
+  white-space: nowrap;
+}
+
+/* Do not set width/height for VNC_canvas or incorrect
+ * scaling will occur. Canvas size depends on remote VNC
+ * settings and noVNC settings. */
+#noVNC_canvas {
+  position: absolute;
+  left: 0;
+  right: 0;
+  margin-left: auto;
+  margin-right: auto;
+}

+ 1743 - 0
static/novnc/app/ui.js

@@ -0,0 +1,1743 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * Copyright (C) 2016 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/* jslint white: false, browser: true */
+/* global window, document.getElementById, Util, WebUtil, RFB, Display */
+
+import * as Log from '../core/util/logging.js';
+import _, { l10n } from '../core/util/localization.js';
+import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js';
+import { setCapture, getPointerEvent } from '../core/util/events.js';
+import KeyTable from "../core/input/keysym.js";
+import keysyms from "../core/input/keysymdef.js";
+import RFB from "../core/rfb.js";
+import Display from "../core/display.js";
+import * as WebUtil from "./webutil.js";
+
+var UI = {
+
+    connected: false,
+    desktopName: "",
+
+    resizeTimeout: null,
+    statusTimeout: null,
+    hideKeyboardTimeout: null,
+    idleControlbarTimeout: null,
+    closeControlbarTimeout: null,
+
+    controlbarGrabbed: false,
+    controlbarDrag: false,
+    controlbarMouseDownClientY: 0,
+    controlbarMouseDownOffsetY: 0,
+
+    isSafari: false,
+    rememberedClipSetting: null,
+    lastKeyboardinput: null,
+    defaultKeyboardinputLen: 100,
+
+    inhibit_reconnect: true,
+    reconnect_callback: null,
+    reconnect_password: null,
+
+    prime: function(callback) {
+        if (document.readyState === "interactive" || document.readyState === "complete") {
+            UI.load(callback);
+        } else {
+            document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback));
+        }
+    },
+
+    // Setup rfb object, load settings from browser storage, then call
+    // UI.init to setup the UI/menus
+    load: function(callback) {
+        WebUtil.initSettings(UI.start, callback);
+    },
+
+    // Render default UI and initialize settings menu
+    start: function(callback) {
+
+        // Setup global variables first
+        UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
+                       navigator.userAgent.indexOf('Chrome') === -1);
+
+        UI.initSettings();
+
+        // Translate the DOM
+        l10n.translateDOM();
+
+        // Adapt the interface for touch screen devices
+        if (isTouchDevice) {
+            document.documentElement.classList.add("noVNC_touch");
+            // Remove the address bar
+            setTimeout(function() { window.scrollTo(0, 1); }, 100);
+        }
+
+        // Restore control bar position
+        if (WebUtil.readSetting('controlbar_pos') === 'right') {
+            UI.toggleControlbarSide();
+        }
+
+        UI.initFullscreen();
+
+        // Setup event handlers
+        UI.addResizeHandlers();
+        UI.addControlbarHandlers();
+        UI.addTouchSpecificHandlers();
+        UI.addExtraKeysHandlers();
+        UI.addXvpHandlers();
+        UI.addConnectionControlHandlers();
+        UI.addClipboardHandlers();
+        UI.addSettingsHandlers();
+        document.getElementById("noVNC_status")
+            .addEventListener('click', UI.hideStatus);
+
+        // Bootstrap fallback input handler
+        UI.keyboardinputReset();
+
+        UI.openControlbar();
+
+        // Show the connect panel on first load unless autoconnecting
+        if (!autoconnect) {
+            UI.openConnectPanel();
+        }
+
+        UI.updateViewClip();
+
+        UI.updateVisualState();
+
+        document.getElementById('noVNC_setting_host').focus();
+        document.documentElement.classList.remove("noVNC_loading");
+
+        var autoconnect = WebUtil.getConfigVar('autoconnect', false);
+        if (autoconnect === 'true' || autoconnect == '1') {
+            autoconnect = true;
+            UI.connect();
+        } else {
+            autoconnect = false;
+        }
+
+        if (typeof callback === "function") {
+            callback(UI.rfb);
+        }
+    },
+
+    initFullscreen: function() {
+        // Only show the button if fullscreen is properly supported
+        // * Safari doesn't support alphanumerical input while in fullscreen
+        if (!UI.isSafari &&
+            (document.documentElement.requestFullscreen ||
+             document.documentElement.mozRequestFullScreen ||
+             document.documentElement.webkitRequestFullscreen ||
+             document.body.msRequestFullscreen)) {
+            document.getElementById('noVNC_fullscreen_button')
+                .classList.remove("noVNC_hidden");
+            UI.addFullscreenHandlers();
+        }
+    },
+
+    initSettings: function() {
+        var i;
+
+        // Logging selection dropdown
+        var llevels = ['error', 'warn', 'info', 'debug'];
+        for (i = 0; i < llevels.length; i += 1) {
+            UI.addOption(document.getElementById('noVNC_setting_logging'),llevels[i], llevels[i]);
+        }
+
+        // Settings with immediate effects
+        UI.initSetting('logging', 'warn');
+        UI.updateLogging();
+
+        // if port == 80 (or 443) then it won't be present and should be
+        // set manually
+        var port = window.location.port;
+        if (!port) {
+            if (window.location.protocol.substring(0,5) == 'https') {
+                port = 443;
+            }
+            else if (window.location.protocol.substring(0,4) == 'http') {
+                port = 80;
+            }
+        }
+
+        /* Populate the controls if defaults are provided in the URL */
+        UI.initSetting('host', window.location.hostname);
+        UI.initSetting('port', port);
+        UI.initSetting('encrypt', (window.location.protocol === "https:"));
+        UI.initSetting('cursor', !isTouchDevice);
+        UI.initSetting('clip', false);
+        UI.initSetting('resize', 'off');
+        UI.initSetting('shared', true);
+        UI.initSetting('view_only', false);
+        UI.initSetting('path', 'websockify');
+        UI.initSetting('repeaterID', '');
+        UI.initSetting('reconnect', false);
+        UI.initSetting('reconnect_delay', 5000);
+
+        UI.setupSettingLabels();
+    },
+    // Adds a link to the label elements on the corresponding input elements
+    setupSettingLabels: function() {
+        var labels = document.getElementsByTagName('LABEL');
+        for (var i = 0; i < labels.length; i++) {
+            var htmlFor = labels[i].htmlFor;
+            if (htmlFor != '') {
+                var elem = document.getElementById(htmlFor);
+                if (elem) elem.label = labels[i];
+            } else {
+                // If 'for' isn't set, use the first input element child
+                var children = labels[i].children;
+                for (var j = 0; j < children.length; j++) {
+                    if (children[j].form !== undefined) {
+                        children[j].label = labels[i];
+                        break;
+                    }
+                }
+            }
+        }
+    },
+
+    initRFB: function() {
+        try {
+            UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'),
+                              'onNotification': UI.notification,
+                              'onUpdateState': UI.updateState,
+                              'onDisconnected': UI.disconnectFinished,
+                              'onPasswordRequired': UI.passwordRequired,
+                              'onXvpInit': UI.updateXvpButton,
+                              'onClipboard': UI.clipboardReceive,
+                              'onBell': UI.bell,
+                              'onFBUComplete': UI.initialResize,
+                              'onFBResize': UI.updateSessionSize,
+                              'onDesktopName': UI.updateDesktopName});
+            return true;
+        } catch (exc) {
+            var msg = "Unable to create RFB client -- " + exc;
+            Log.Error(msg);
+            UI.showStatus(msg, 'error');
+            return false;
+        }
+    },
+
+/* ------^-------
+*     /INIT
+* ==============
+* EVENT HANDLERS
+* ------v------*/
+
+    addResizeHandlers: function() {
+        window.addEventListener('resize', UI.applyResizeMode);
+        window.addEventListener('resize', UI.updateViewClip);
+    },
+
+    addControlbarHandlers: function() {
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('mousemove', UI.activateControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('mouseup', UI.activateControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('mousedown', UI.activateControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('keypress', UI.activateControlbar);
+
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('mousedown', UI.keepControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('keypress', UI.keepControlbar);
+
+        document.getElementById("noVNC_view_drag_button")
+            .addEventListener('click', UI.toggleViewDrag);
+
+        document.getElementById("noVNC_control_bar_handle")
+            .addEventListener('mousedown', UI.controlbarHandleMouseDown);
+        document.getElementById("noVNC_control_bar_handle")
+            .addEventListener('mouseup', UI.controlbarHandleMouseUp);
+        document.getElementById("noVNC_control_bar_handle")
+            .addEventListener('mousemove', UI.dragControlbarHandle);
+        // resize events aren't available for elements
+        window.addEventListener('resize', UI.updateControlbarHandle);
+
+        var exps = document.getElementsByClassName("noVNC_expander");
+        for (var i = 0;i < exps.length;i++) {
+            exps[i].addEventListener('click', UI.toggleExpander);
+        }
+    },
+
+    addTouchSpecificHandlers: function() {
+        document.getElementById("noVNC_mouse_button0")
+            .addEventListener('click', function () { UI.setMouseButton(1); });
+        document.getElementById("noVNC_mouse_button1")
+            .addEventListener('click', function () { UI.setMouseButton(2); });
+        document.getElementById("noVNC_mouse_button2")
+            .addEventListener('click', function () { UI.setMouseButton(4); });
+        document.getElementById("noVNC_mouse_button4")
+            .addEventListener('click', function () { UI.setMouseButton(0); });
+        document.getElementById("noVNC_keyboard_button")
+            .addEventListener('click', UI.toggleVirtualKeyboard);
+
+        document.getElementById("noVNC_keyboardinput")
+            .addEventListener('input', UI.keyInput);
+        document.getElementById("noVNC_keyboardinput")
+            .addEventListener('focus', UI.onfocusVirtualKeyboard);
+        document.getElementById("noVNC_keyboardinput")
+            .addEventListener('blur', UI.onblurVirtualKeyboard);
+        document.getElementById("noVNC_keyboardinput")
+            .addEventListener('submit', function () { return false; });
+
+        document.documentElement
+            .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
+
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('touchstart', UI.activateControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('touchmove', UI.activateControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('touchend', UI.activateControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('input', UI.activateControlbar);
+
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('touchstart', UI.keepControlbar);
+        document.getElementById("noVNC_control_bar")
+            .addEventListener('input', UI.keepControlbar);
+
+        document.getElementById("noVNC_control_bar_handle")
+            .addEventListener('touchstart', UI.controlbarHandleMouseDown);
+        document.getElementById("noVNC_control_bar_handle")
+            .addEventListener('touchend', UI.controlbarHandleMouseUp);
+        document.getElementById("noVNC_control_bar_handle")
+            .addEventListener('touchmove', UI.dragControlbarHandle);
+    },
+
+    addExtraKeysHandlers: function() {
+        document.getElementById("noVNC_toggle_extra_keys_button")
+            .addEventListener('click', UI.toggleExtraKeys);
+        document.getElementById("noVNC_toggle_ctrl_button")
+            .addEventListener('click', UI.toggleCtrl);
+        document.getElementById("noVNC_toggle_alt_button")
+            .addEventListener('click', UI.toggleAlt);
+        document.getElementById("noVNC_send_tab_button")
+            .addEventListener('click', UI.sendTab);
+        document.getElementById("noVNC_send_esc_button")
+            .addEventListener('click', UI.sendEsc);
+        document.getElementById("noVNC_send_ctrl_alt_del_button")
+            .addEventListener('click', UI.sendCtrlAltDel);
+    },
+
+    addXvpHandlers: function() {
+        document.getElementById("noVNC_xvp_shutdown_button")
+            .addEventListener('click', function() { UI.rfb.xvpShutdown(); });
+        document.getElementById("noVNC_xvp_reboot_button")
+            .addEventListener('click', function() { UI.rfb.xvpReboot(); });
+        document.getElementById("noVNC_xvp_reset_button")
+            .addEventListener('click', function() { UI.rfb.xvpReset(); });
+        document.getElementById("noVNC_xvp_button")
+            .addEventListener('click', UI.toggleXvpPanel);
+    },
+
+    addConnectionControlHandlers: function() {
+        document.getElementById("noVNC_disconnect_button")
+            .addEventListener('click', UI.disconnect);
+        document.getElementById("noVNC_connect_button")
+            .addEventListener('click', UI.connect);
+        document.getElementById("noVNC_cancel_reconnect_button")
+            .addEventListener('click', UI.cancelReconnect);
+
+        document.getElementById("noVNC_password_button")
+            .addEventListener('click', UI.setPassword);
+    },
+
+    addClipboardHandlers: function() {
+        document.getElementById("noVNC_clipboard_button")
+            .addEventListener('click', UI.toggleClipboardPanel);
+        document.getElementById("noVNC_clipboard_text")
+            .addEventListener('focus', UI.displayBlur);
+        document.getElementById("noVNC_clipboard_text")
+            .addEventListener('blur', UI.displayFocus);
+        document.getElementById("noVNC_clipboard_text")
+            .addEventListener('change', UI.clipboardSend);
+        document.getElementById("noVNC_clipboard_clear_button")
+            .addEventListener('click', UI.clipboardClear);
+    },
+
+    // Add a call to save settings when the element changes,
+    // unless the optional parameter changeFunc is used instead.
+    addSettingChangeHandler: function(name, changeFunc) {
+        var settingElem = document.getElementById("noVNC_setting_" + name);
+        if (changeFunc === undefined) {
+            changeFunc = function () { UI.saveSetting(name); };
+        }
+        settingElem.addEventListener('change', changeFunc);
+    },
+
+    addSettingsHandlers: function() {
+        document.getElementById("noVNC_settings_button")
+            .addEventListener('click', UI.toggleSettingsPanel);
+
+        UI.addSettingChangeHandler('encrypt');
+        UI.addSettingChangeHandler('cursor');
+        UI.addSettingChangeHandler('cursor', UI.updateLocalCursor);
+        UI.addSettingChangeHandler('resize');
+        UI.addSettingChangeHandler('resize', UI.enableDisableViewClip);
+        UI.addSettingChangeHandler('resize', UI.applyResizeMode);
+        UI.addSettingChangeHandler('clip');
+        UI.addSettingChangeHandler('clip', UI.updateViewClip);
+        UI.addSettingChangeHandler('shared');
+        UI.addSettingChangeHandler('view_only');
+        UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
+        UI.addSettingChangeHandler('host');
+        UI.addSettingChangeHandler('port');
+        UI.addSettingChangeHandler('path');
+        UI.addSettingChangeHandler('repeaterID');
+        UI.addSettingChangeHandler('logging');
+        UI.addSettingChangeHandler('logging', UI.updateLogging);
+        UI.addSettingChangeHandler('reconnect');
+        UI.addSettingChangeHandler('reconnect_delay');
+    },
+
+    addFullscreenHandlers: function() {
+        document.getElementById("noVNC_fullscreen_button")
+            .addEventListener('click', UI.toggleFullscreen);
+
+        window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
+        window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
+        window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);
+        window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
+    },
+
+/* ------^-------
+ * /EVENT HANDLERS
+ * ==============
+ *     VISUAL
+ * ------v------*/
+
+    updateState: function(rfb, state, oldstate) {
+        var msg;
+
+        document.documentElement.classList.remove("noVNC_connecting");
+        document.documentElement.classList.remove("noVNC_connected");
+        document.documentElement.classList.remove("noVNC_disconnecting");
+        document.documentElement.classList.remove("noVNC_reconnecting");
+
+        switch (state) {
+            case 'connecting':
+                document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
+                document.documentElement.classList.add("noVNC_connecting");
+                break;
+            case 'connected':
+                UI.connected = true;
+                UI.inhibit_reconnect = false;
+                document.documentElement.classList.add("noVNC_connected");
+                if (rfb && rfb.get_encrypt()) {
+                    msg = _("Connected (encrypted) to ") + UI.desktopName;
+                } else {
+                    msg = _("Connected (unencrypted) to ") + UI.desktopName;
+                }
+                UI.showStatus(msg);
+                break;
+            case 'disconnecting':
+                UI.connected = false;
+                document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
+                document.documentElement.classList.add("noVNC_disconnecting");
+                break;
+            case 'disconnected':
+                UI.showStatus(_("Disconnected"));
+                break;
+            default:
+                msg = "Invalid UI state";
+                Log.Error(msg);
+                UI.showStatus(msg, 'error');
+                break;
+        }
+
+        UI.updateVisualState();
+    },
+
+    // Disable/enable controls depending on connection state
+    updateVisualState: function() {
+        //Log.Debug(">> updateVisualState");
+
+        UI.enableDisableViewClip();
+
+        if (cursorURIsSupported() && !isTouchDevice) {
+            UI.enableSetting('cursor');
+        } else {
+            UI.disableSetting('cursor');
+        }
+
+        if (UI.connected) {
+            UI.disableSetting('encrypt');
+            UI.disableSetting('shared');
+            UI.disableSetting('host');
+            UI.disableSetting('port');
+            UI.disableSetting('path');
+            UI.disableSetting('repeaterID');
+            UI.updateViewClip();
+            UI.setMouseButton(1);
+
+            // Hide the controlbar after 2 seconds
+            UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
+        } else {
+            UI.enableSetting('encrypt');
+            UI.enableSetting('shared');
+            UI.enableSetting('host');
+            UI.enableSetting('port');
+            UI.enableSetting('path');
+            UI.enableSetting('repeaterID');
+            UI.updateXvpButton(0);
+            UI.keepControlbar();
+        }
+
+        // Hide input related buttons in view only mode
+        if (UI.rfb && UI.rfb.get_view_only()) {
+            document.getElementById('noVNC_keyboard_button')
+                .classList.add('noVNC_hidden');
+            document.getElementById('noVNC_toggle_extra_keys_button')
+                .classList.add('noVNC_hidden');
+        } else {
+            document.getElementById('noVNC_keyboard_button')
+                .classList.remove('noVNC_hidden');
+            document.getElementById('noVNC_toggle_extra_keys_button')
+                .classList.remove('noVNC_hidden');
+        }
+
+        // State change disables viewport dragging.
+        // It is enabled (toggled) by direct click on the button
+        UI.setViewDrag(false);
+
+        // State change also closes the password dialog
+        document.getElementById('noVNC_password_dlg')
+            .classList.remove('noVNC_open');
+
+        //Log.Debug("<< updateVisualState");
+    },
+
+    showStatus: function(text, status_type, time) {
+        var statusElem = document.getElementById('noVNC_status');
+
+        clearTimeout(UI.statusTimeout);
+
+        if (typeof status_type === 'undefined') {
+            status_type = 'normal';
+        }
+
+        statusElem.classList.remove("noVNC_status_normal");
+        statusElem.classList.remove("noVNC_status_warn");
+        statusElem.classList.remove("noVNC_status_error");
+
+        switch (status_type) {
+            case 'warning':
+            case 'warn':
+                statusElem.classList.add("noVNC_status_warn");
+                break;
+            case 'error':
+                statusElem.classList.add("noVNC_status_error");
+                break;
+            case 'normal':
+            case 'info':
+            default:
+                statusElem.classList.add("noVNC_status_normal");
+                break;
+        }
+
+        statusElem.textContent = text;
+        statusElem.classList.add("noVNC_open");
+
+        // If no time was specified, show the status for 1.5 seconds
+        if (typeof time === 'undefined') {
+            time = 1500;
+        }
+
+        // Error messages do not timeout
+        if (status_type !== 'error') {
+            UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
+        }
+    },
+
+    hideStatus: function() {
+        clearTimeout(UI.statusTimeout);
+        document.getElementById('noVNC_status').classList.remove("noVNC_open");
+    },
+
+    notification: function (rfb, msg, level, options) {
+        UI.showStatus(msg, level);
+    },
+
+    activateControlbar: function(event) {
+        clearTimeout(UI.idleControlbarTimeout);
+        // We manipulate the anchor instead of the actual control
+        // bar in order to avoid creating new a stacking group
+        document.getElementById('noVNC_control_bar_anchor')
+            .classList.remove("noVNC_idle");
+        UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
+    },
+
+    idleControlbar: function() {
+        document.getElementById('noVNC_control_bar_anchor')
+            .classList.add("noVNC_idle");
+    },
+
+    keepControlbar: function() {
+        clearTimeout(UI.closeControlbarTimeout);
+    },
+
+    openControlbar: function() {
+        document.getElementById('noVNC_control_bar')
+            .classList.add("noVNC_open");
+    },
+
+    closeControlbar: function() {
+        UI.closeAllPanels();
+        document.getElementById('noVNC_control_bar')
+            .classList.remove("noVNC_open");
+    },
+
+    toggleControlbar: function() {
+        if (document.getElementById('noVNC_control_bar')
+            .classList.contains("noVNC_open")) {
+            UI.closeControlbar();
+        } else {
+            UI.openControlbar();
+        }
+    },
+
+    toggleControlbarSide: function () {
+        // Temporarily disable animation to avoid weird movement
+        var bar = document.getElementById('noVNC_control_bar');
+        bar.style.transitionDuration = '0s';
+        bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
+
+        var anchor = document.getElementById('noVNC_control_bar_anchor');
+        if (anchor.classList.contains("noVNC_right")) {
+            WebUtil.writeSetting('controlbar_pos', 'left');
+            anchor.classList.remove("noVNC_right");
+        } else {
+            WebUtil.writeSetting('controlbar_pos', 'right');
+            anchor.classList.add("noVNC_right");
+        }
+
+        // Consider this a movement of the handle
+        UI.controlbarDrag = true;
+    },
+
+    showControlbarHint: function (show) {
+        var hint = document.getElementById('noVNC_control_bar_hint');
+        if (show) {
+            hint.classList.add("noVNC_active");
+        } else {
+            hint.classList.remove("noVNC_active");
+        }
+    },
+
+    dragControlbarHandle: function (e) {
+        if (!UI.controlbarGrabbed) return;
+
+        var ptr = getPointerEvent(e);
+
+        var anchor = document.getElementById('noVNC_control_bar_anchor');
+        if (ptr.clientX < (window.innerWidth * 0.1)) {
+            if (anchor.classList.contains("noVNC_right")) {
+                UI.toggleControlbarSide();
+            }
+        } else if (ptr.clientX > (window.innerWidth * 0.9)) {
+            if (!anchor.classList.contains("noVNC_right")) {
+                UI.toggleControlbarSide();
+            }
+        }
+
+        if (!UI.controlbarDrag) {
+            // The goal is to trigger on a certain physical width, the
+            // devicePixelRatio brings us a bit closer but is not optimal.
+            var dragThreshold = 10 * (window.devicePixelRatio || 1);
+            var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
+
+            if (dragDistance < dragThreshold) return;
+
+            UI.controlbarDrag = true;
+        }
+
+        var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
+
+        UI.moveControlbarHandle(eventY);
+
+        e.preventDefault();
+        e.stopPropagation();
+        UI.keepControlbar();
+        UI.activateControlbar();
+    },
+
+    // Move the handle but don't allow any position outside the bounds
+    moveControlbarHandle: function (viewportRelativeY) {
+        var handle = document.getElementById("noVNC_control_bar_handle");
+        var handleHeight = handle.getBoundingClientRect().height;
+        var controlbarBounds = document.getElementById("noVNC_control_bar")
+            .getBoundingClientRect();
+        var margin = 10;
+
+        // These heights need to be non-zero for the below logic to work
+        if (handleHeight === 0 || controlbarBounds.height === 0) {
+            return;
+        }
+
+        var newY = viewportRelativeY;
+
+        // Check if the coordinates are outside the control bar
+        if (newY < controlbarBounds.top + margin) {
+            // Force coordinates to be below the top of the control bar
+            newY = controlbarBounds.top + margin;
+
+        } else if (newY > controlbarBounds.top +
+                   controlbarBounds.height - handleHeight - margin) {
+            // Force coordinates to be above the bottom of the control bar
+            newY = controlbarBounds.top +
+                controlbarBounds.height - handleHeight - margin;
+        }
+
+        // Corner case: control bar too small for stable position
+        if (controlbarBounds.height < (handleHeight + margin * 2)) {
+            newY = controlbarBounds.top +
+                (controlbarBounds.height - handleHeight) / 2;
+        }
+
+        // The transform needs coordinates that are relative to the parent
+        var parentRelativeY = newY - controlbarBounds.top;
+        handle.style.transform = "translateY(" + parentRelativeY + "px)";
+    },
+
+    updateControlbarHandle: function () {
+        // Since the control bar is fixed on the viewport and not the page,
+        // the move function expects coordinates relative the the viewport.
+        var handle = document.getElementById("noVNC_control_bar_handle");
+        var handleBounds = handle.getBoundingClientRect();
+        UI.moveControlbarHandle(handleBounds.top);
+    },
+
+    controlbarHandleMouseUp: function(e) {
+        if ((e.type == "mouseup") && (e.button != 0)) return;
+
+        // mouseup and mousedown on the same place toggles the controlbar
+        if (UI.controlbarGrabbed && !UI.controlbarDrag) {
+            UI.toggleControlbar();
+            e.preventDefault();
+            e.stopPropagation();
+            UI.keepControlbar();
+            UI.activateControlbar();
+        }
+        UI.controlbarGrabbed = false;
+        UI.showControlbarHint(false);
+    },
+
+    controlbarHandleMouseDown: function(e) {
+        if ((e.type == "mousedown") && (e.button != 0)) return;
+
+        var ptr = getPointerEvent(e);
+
+        var handle = document.getElementById("noVNC_control_bar_handle");
+        var bounds = handle.getBoundingClientRect();
+
+        // Touch events have implicit capture
+        if (e.type === "mousedown") {
+            setCapture(handle);
+        }
+
+        UI.controlbarGrabbed = true;
+        UI.controlbarDrag = false;
+
+        UI.showControlbarHint(true);
+
+        UI.controlbarMouseDownClientY = ptr.clientY;
+        UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
+        e.preventDefault();
+        e.stopPropagation();
+        UI.keepControlbar();
+        UI.activateControlbar();
+    },
+
+    toggleExpander: function(e) {
+        if (this.classList.contains("noVNC_open")) {
+            this.classList.remove("noVNC_open");
+        } else {
+            this.classList.add("noVNC_open");
+        }
+    },
+
+/* ------^-------
+ *    /VISUAL
+ * ==============
+ *    SETTINGS
+ * ------v------*/
+
+    // Initial page load read/initialization of settings
+    initSetting: function(name, defVal) {
+        // Check Query string followed by cookie
+        var val = WebUtil.getConfigVar(name);
+        if (val === null) {
+            val = WebUtil.readSetting(name, defVal);
+        }
+        UI.updateSetting(name, val);
+        return val;
+    },
+
+    // Update cookie and form control setting. If value is not set, then
+    // updates from control to current cookie setting.
+    updateSetting: function(name, value) {
+
+        // Save the cookie for this session
+        if (typeof value !== 'undefined') {
+            WebUtil.writeSetting(name, value);
+        }
+
+        // Update the settings control
+        value = UI.getSetting(name);
+
+        var ctrl = document.getElementById('noVNC_setting_' + name);
+        if (ctrl.type === 'checkbox') {
+            ctrl.checked = value;
+
+        } else if (typeof ctrl.options !== 'undefined') {
+            for (var i = 0; i < ctrl.options.length; i += 1) {
+                if (ctrl.options[i].value === value) {
+                    ctrl.selectedIndex = i;
+                    break;
+                }
+            }
+        } else {
+            /*Weird IE9 error leads to 'null' appearring
+            in textboxes instead of ''.*/
+            if (value === null) {
+                value = "";
+            }
+            ctrl.value = value;
+        }
+    },
+
+    // Save control setting to cookie
+    saveSetting: function(name) {
+        var val, ctrl = document.getElementById('noVNC_setting_' + name);
+        if (ctrl.type === 'checkbox') {
+            val = ctrl.checked;
+        } else if (typeof ctrl.options !== 'undefined') {
+            val = ctrl.options[ctrl.selectedIndex].value;
+        } else {
+            val = ctrl.value;
+        }
+        WebUtil.writeSetting(name, val);
+        //Log.Debug("Setting saved '" + name + "=" + val + "'");
+        return val;
+    },
+
+    // Read form control compatible setting from cookie
+    getSetting: function(name) {
+        var ctrl = document.getElementById('noVNC_setting_' + name);
+        var val = WebUtil.readSetting(name);
+        if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
+            if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
+                val = false;
+            } else {
+                val = true;
+            }
+        }
+        return val;
+    },
+
+    // These helpers compensate for the lack of parent-selectors and
+    // previous-sibling-selectors in CSS which are needed when we want to
+    // disable the labels that belong to disabled input elements.
+    disableSetting: function(name) {
+        var ctrl = document.getElementById('noVNC_setting_' + name);
+        ctrl.disabled = true;
+        ctrl.label.classList.add('noVNC_disabled');
+    },
+
+    enableSetting: function(name) {
+        var ctrl = document.getElementById('noVNC_setting_' + name);
+        ctrl.disabled = false;
+        ctrl.label.classList.remove('noVNC_disabled');
+    },
+
+/* ------^-------
+ *   /SETTINGS
+ * ==============
+ *    PANELS
+ * ------v------*/
+
+    closeAllPanels: function() {
+        UI.closeSettingsPanel();
+        UI.closeXvpPanel();
+        UI.closeClipboardPanel();
+        UI.closeExtraKeys();
+    },
+
+/* ------^-------
+ *   /PANELS
+ * ==============
+ * SETTINGS (panel)
+ * ------v------*/
+
+    openSettingsPanel: function() {
+        UI.closeAllPanels();
+        UI.openControlbar();
+
+        // Refresh UI elements from saved cookies
+        UI.updateSetting('encrypt');
+        if (cursorURIsSupported()) {
+            UI.updateSetting('cursor');
+        } else {
+            UI.updateSetting('cursor', !isTouchDevice);
+            UI.disableSetting('cursor');
+        }
+        UI.updateSetting('clip');
+        UI.updateSetting('resize');
+        UI.updateSetting('shared');
+        UI.updateSetting('view_only');
+        UI.updateSetting('path');
+        UI.updateSetting('repeaterID');
+        UI.updateSetting('logging');
+        UI.updateSetting('reconnect');
+        UI.updateSetting('reconnect_delay');
+
+        document.getElementById('noVNC_settings')
+            .classList.add("noVNC_open");
+        document.getElementById('noVNC_settings_button')
+            .classList.add("noVNC_selected");
+    },
+
+    closeSettingsPanel: function() {
+        document.getElementById('noVNC_settings')
+            .classList.remove("noVNC_open");
+        document.getElementById('noVNC_settings_button')
+            .classList.remove("noVNC_selected");
+    },
+
+    toggleSettingsPanel: function() {
+        if (document.getElementById('noVNC_settings')
+            .classList.contains("noVNC_open")) {
+            UI.closeSettingsPanel();
+        } else {
+            UI.openSettingsPanel();
+        }
+    },
+
+/* ------^-------
+ *   /SETTINGS
+ * ==============
+ *      XVP
+ * ------v------*/
+
+    openXvpPanel: function() {
+        UI.closeAllPanels();
+        UI.openControlbar();
+
+        document.getElementById('noVNC_xvp')
+            .classList.add("noVNC_open");
+        document.getElementById('noVNC_xvp_button')
+            .classList.add("noVNC_selected");
+    },
+
+    closeXvpPanel: function() {
+        document.getElementById('noVNC_xvp')
+            .classList.remove("noVNC_open");
+        document.getElementById('noVNC_xvp_button')
+            .classList.remove("noVNC_selected");
+    },
+
+    toggleXvpPanel: function() {
+        if (document.getElementById('noVNC_xvp')
+            .classList.contains("noVNC_open")) {
+            UI.closeXvpPanel();
+        } else {
+            UI.openXvpPanel();
+        }
+    },
+
+    // Disable/enable XVP button
+    updateXvpButton: function(ver) {
+        if (ver >= 1 && !UI.rfb.get_view_only()) {
+            document.getElementById('noVNC_xvp_button')
+                .classList.remove("noVNC_hidden");
+        } else {
+            document.getElementById('noVNC_xvp_button')
+                .classList.add("noVNC_hidden");
+            // Close XVP panel if open
+            UI.closeXvpPanel();
+        }
+    },
+
+/* ------^-------
+ *     /XVP
+ * ==============
+ *   CLIPBOARD
+ * ------v------*/
+
+    openClipboardPanel: function() {
+        UI.closeAllPanels();
+        UI.openControlbar();
+
+        document.getElementById('noVNC_clipboard')
+            .classList.add("noVNC_open");
+        document.getElementById('noVNC_clipboard_button')
+            .classList.add("noVNC_selected");
+    },
+
+    closeClipboardPanel: function() {
+        document.getElementById('noVNC_clipboard')
+            .classList.remove("noVNC_open");
+        document.getElementById('noVNC_clipboard_button')
+            .classList.remove("noVNC_selected");
+    },
+
+    toggleClipboardPanel: function() {
+        if (document.getElementById('noVNC_clipboard')
+            .classList.contains("noVNC_open")) {
+            UI.closeClipboardPanel();
+        } else {
+            UI.openClipboardPanel();
+        }
+    },
+
+    clipboardReceive: function(rfb, text) {
+        Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
+        document.getElementById('noVNC_clipboard_text').value = text;
+        Log.Debug("<< UI.clipboardReceive");
+    },
+
+    clipboardClear: function() {
+        document.getElementById('noVNC_clipboard_text').value = "";
+        UI.rfb.clipboardPasteFrom("");
+    },
+
+    clipboardSend: function() {
+        var text = document.getElementById('noVNC_clipboard_text').value;
+        Log.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
+        UI.rfb.clipboardPasteFrom(text);
+        Log.Debug("<< UI.clipboardSend");
+    },
+
+/* ------^-------
+ *  /CLIPBOARD
+ * ==============
+ *  CONNECTION
+ * ------v------*/
+
+    openConnectPanel: function() {
+        document.getElementById('noVNC_connect_dlg')
+            .classList.add("noVNC_open");
+    },
+
+    closeConnectPanel: function() {
+        document.getElementById('noVNC_connect_dlg')
+            .classList.remove("noVNC_open");
+    },
+
+    connect: function(event, password) {
+        var host = UI.getSetting('host');
+        var port = UI.getSetting('port');
+        var path = UI.getSetting('path');
+
+        if (typeof password === 'undefined') {
+            password = WebUtil.getConfigVar('password');
+        }
+
+        if (password === null) {
+            password = undefined;
+        }
+
+        if ((!host) || (!port)) {
+            var msg = _("Must set host and port");
+            Log.Error(msg);
+            UI.showStatus(msg, 'error');
+            return;
+        }
+
+        if (!UI.initRFB()) return;
+
+        UI.closeAllPanels();
+        UI.closeConnectPanel();
+
+        UI.rfb.set_encrypt(UI.getSetting('encrypt'));
+        UI.rfb.set_shared(UI.getSetting('shared'));
+        UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
+
+        UI.updateLocalCursor();
+        UI.updateViewOnly();
+
+        UI.rfb.connect(host, port, password, path);
+    },
+
+    disconnect: function() {
+        UI.closeAllPanels();
+        UI.rfb.disconnect();
+
+        // Disable automatic reconnecting
+        UI.inhibit_reconnect = true;
+
+        // Restore the callback used for initial resize
+        UI.rfb.set_onFBUComplete(UI.initialResize);
+
+        // Don't display the connection settings until we're actually disconnected
+    },
+
+    reconnect: function() {
+        UI.reconnect_callback = null;
+
+        // if reconnect has been disabled in the meantime, do nothing.
+        if (UI.inhibit_reconnect) {
+            return;
+        }
+
+        UI.connect(null, UI.reconnect_password);
+    },
+
+    disconnectFinished: function (rfb, reason) {
+        if (typeof reason !== 'undefined') {
+            UI.showStatus(reason, 'error');
+        } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
+            document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
+            document.documentElement.classList.add("noVNC_reconnecting");
+
+            var delay = parseInt(UI.getSetting('reconnect_delay'));
+            UI.reconnect_callback = setTimeout(UI.reconnect, delay);
+            return;
+        }
+
+        UI.openControlbar();
+        UI.openConnectPanel();
+    },
+
+    cancelReconnect: function() {
+        if (UI.reconnect_callback !== null) {
+            clearTimeout(UI.reconnect_callback);
+            UI.reconnect_callback = null;
+        }
+
+        document.documentElement.classList.remove("noVNC_reconnecting");
+        UI.openControlbar();
+        UI.openConnectPanel();
+    },
+
+/* ------^-------
+ *  /CONNECTION
+ * ==============
+ *   PASSWORD
+ * ------v------*/
+
+    passwordRequired: function(rfb, msg) {
+
+        document.getElementById('noVNC_password_dlg')
+            .classList.add('noVNC_open');
+
+        setTimeout(function () {
+                document.getElementById('noVNC_password_input').focus();
+            }, 100);
+
+        if (typeof msg === 'undefined') {
+            msg = _("Password is required");
+        }
+        Log.Warn(msg);
+        UI.showStatus(msg, "warning");
+    },
+
+    setPassword: function(e) {
+        var inputElem = document.getElementById('noVNC_password_input');
+        var password = inputElem.value;
+        // Clear the input after reading the password
+        inputElem.value = "";
+        UI.rfb.sendPassword(password);
+        UI.reconnect_password = password;
+        document.getElementById('noVNC_password_dlg')
+            .classList.remove('noVNC_open');
+        // Prevent actually submitting the form
+        e.preventDefault();
+    },
+
+/* ------^-------
+ *  /PASSWORD
+ * ==============
+ *   FULLSCREEN
+ * ------v------*/
+
+    toggleFullscreen: function() {
+        if (document.fullscreenElement || // alternative standard method
+            document.mozFullScreenElement || // currently working methods
+            document.webkitFullscreenElement ||
+            document.msFullscreenElement) {
+            if (document.exitFullscreen) {
+                document.exitFullscreen();
+            } else if (document.mozCancelFullScreen) {
+                document.mozCancelFullScreen();
+            } else if (document.webkitExitFullscreen) {
+                document.webkitExitFullscreen();
+            } else if (document.msExitFullscreen) {
+                document.msExitFullscreen();
+            }
+        } else {
+            if (document.documentElement.requestFullscreen) {
+                document.documentElement.requestFullscreen();
+            } else if (document.documentElement.mozRequestFullScreen) {
+                document.documentElement.mozRequestFullScreen();
+            } else if (document.documentElement.webkitRequestFullscreen) {
+                document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+            } else if (document.body.msRequestFullscreen) {
+                document.body.msRequestFullscreen();
+            }
+        }
+        UI.enableDisableViewClip();
+        UI.updateFullscreenButton();
+    },
+
+    updateFullscreenButton: function() {
+        if (document.fullscreenElement || // alternative standard method
+            document.mozFullScreenElement || // currently working methods
+            document.webkitFullscreenElement ||
+            document.msFullscreenElement ) {
+            document.getElementById('noVNC_fullscreen_button')
+                .classList.add("noVNC_selected");
+        } else {
+            document.getElementById('noVNC_fullscreen_button')
+                .classList.remove("noVNC_selected");
+        }
+    },
+
+/* ------^-------
+ *  /FULLSCREEN
+ * ==============
+ *     RESIZE
+ * ------v------*/
+
+    // Apply remote resizing or local scaling
+    applyResizeMode: function() {
+        if (!UI.rfb) return;
+
+        var screen = UI.screenSize();
+
+        if (screen && UI.connected && UI.rfb.get_display()) {
+
+            var display = UI.rfb.get_display();
+            var resizeMode = UI.getSetting('resize');
+            display.set_scale(1);
+
+            // Make sure the viewport is adjusted first
+            UI.updateViewClip();
+
+            if (resizeMode === 'remote') {
+
+                // Request changing the resolution of the remote display to
+                // the size of the local browser viewport.
+
+                // In order to not send multiple requests before the browser-resize
+                // is finished we wait 0.5 seconds before sending the request.
+                clearTimeout(UI.resizeTimeout);
+                UI.resizeTimeout = setTimeout(function(){
+                    // Request a remote size covering the viewport
+                    if (UI.rfb.requestDesktopSize(screen.w, screen.h)) {
+                        Log.Debug('Requested new desktop size: ' +
+                                   screen.w + 'x' + screen.h);
+                    }
+                }, 500);
+
+            } else if (resizeMode === 'scale' || resizeMode === 'downscale') {
+                var downscaleOnly = resizeMode === 'downscale';
+                display.autoscale(screen.w, screen.h, downscaleOnly);
+                UI.fixScrollbars();
+            }
+        }
+    },
+
+    // Gets the the size of the available viewport in the browser window
+    screenSize: function() {
+        var screen = document.getElementById('noVNC_screen');
+        return {w: screen.offsetWidth, h: screen.offsetHeight};
+    },
+
+    // Normally we only apply the current resize mode after a window resize
+    // event. This means that when a new connection is opened, there is no
+    // resize mode active.
+    // We have to wait until the first FBU because this is where the client
+    // will find the supported encodings of the server. Some calls later in
+    // the chain is dependant on knowing the server-capabilities.
+    initialResize: function(rfb, fbu) {
+        UI.applyResizeMode();
+        // After doing this once, we remove the callback.
+        UI.rfb.set_onFBUComplete(function() { });
+    },
+
+/* ------^-------
+ *    /RESIZE
+ * ==============
+ *    CLIPPING
+ * ------v------*/
+
+    // Set and configure viewport clipping
+    setViewClip: function(clip) {
+        UI.updateSetting('clip', clip);
+        UI.updateViewClip();
+    },
+
+    // Update parameters that depend on the clip setting
+    updateViewClip: function() {
+        if (!UI.rfb) return;
+
+        var display = UI.rfb.get_display();
+        var cur_clip = display.get_viewport();
+        var new_clip = UI.getSetting('clip');
+
+        var resizeSetting = UI.getSetting('resize');
+        if (resizeSetting === 'downscale' || resizeSetting === 'scale') {
+            // Disable clipping if we are scaling
+            new_clip = false;
+        } else if (isTouchDevice) {
+            // Touch devices usually have shit scrollbars
+            new_clip = true;
+        }
+
+        if (cur_clip !== new_clip) {
+            display.set_viewport(new_clip);
+        }
+
+        var size = UI.screenSize();
+
+        if (new_clip && size) {
+            // When clipping is enabled, the screen is limited to
+            // the size of the browser window.
+            display.viewportChangeSize(size.w, size.h);
+            UI.fixScrollbars();
+        }
+
+        // Changing the viewport may change the state of
+        // the dragging button
+        UI.updateViewDrag();
+    },
+
+    // Handle special cases where clipping is forced on/off or locked
+    enableDisableViewClip: function() {
+        var resizeSetting = UI.getSetting('resize');
+        // Disable clipping if we are scaling, connected or on touch
+        if (resizeSetting === 'downscale' || resizeSetting === 'scale' ||
+            isTouchDevice) {
+            UI.disableSetting('clip');
+        } else {
+            UI.enableSetting('clip');
+        }
+    },
+
+/* ------^-------
+ *   /CLIPPING
+ * ==============
+ *    VIEWDRAG
+ * ------v------*/
+
+    toggleViewDrag: function() {
+        if (!UI.rfb) return;
+
+        var drag = UI.rfb.get_viewportDrag();
+        UI.setViewDrag(!drag);
+     },
+
+    // Set the view drag mode which moves the viewport on mouse drags
+    setViewDrag: function(drag) {
+        if (!UI.rfb) return;
+
+        UI.rfb.set_viewportDrag(drag);
+
+        UI.updateViewDrag();
+    },
+
+    updateViewDrag: function() {
+        var clipping = false;
+
+        if (!UI.connected) return;
+
+        // Check if viewport drag is possible. It is only possible
+        // if the remote display is clipping the client display.
+        if (UI.rfb.get_display().get_viewport() &&
+            UI.rfb.get_display().clippingDisplay()) {
+            clipping = true;
+        }
+
+        var viewDragButton = document.getElementById('noVNC_view_drag_button');
+
+        if (!clipping &&
+            UI.rfb.get_viewportDrag()) {
+            // The size of the remote display is the same or smaller
+            // than the client display. Make sure viewport drag isn't
+            // active when it can't be used.
+            UI.rfb.set_viewportDrag(false);
+        }
+
+        if (UI.rfb.get_viewportDrag()) {
+            viewDragButton.classList.add("noVNC_selected");
+        } else {
+            viewDragButton.classList.remove("noVNC_selected");
+        }
+
+        // Different behaviour for touch vs non-touch
+        // The button is disabled instead of hidden on touch devices
+        if (isTouchDevice) {
+            viewDragButton.classList.remove("noVNC_hidden");
+
+            if (clipping) {
+                viewDragButton.disabled = false;
+            } else {
+                viewDragButton.disabled = true;
+            }
+        } else {
+            viewDragButton.disabled = false;
+
+            if (clipping) {
+                viewDragButton.classList.remove("noVNC_hidden");
+            } else {
+                viewDragButton.classList.add("noVNC_hidden");
+            }
+        }
+    },
+
+/* ------^-------
+ *   /VIEWDRAG
+ * ==============
+ *    KEYBOARD
+ * ------v------*/
+
+    showVirtualKeyboard: function() {
+        if (!isTouchDevice) return;
+
+        var input = document.getElementById('noVNC_keyboardinput');
+
+        if (document.activeElement == input) return;
+
+        input.focus();
+
+        try {
+            var l = input.value.length;
+            // Move the caret to the end
+            input.setSelectionRange(l, l);
+        } catch (err) {} // setSelectionRange is undefined in Google Chrome
+    },
+
+    hideVirtualKeyboard: function() {
+        if (!isTouchDevice) return;
+
+        var input = document.getElementById('noVNC_keyboardinput');
+
+        if (document.activeElement != input) return;
+
+        input.blur();
+    },
+
+    toggleVirtualKeyboard: function () {
+        if (document.getElementById('noVNC_keyboard_button')
+            .classList.contains("noVNC_selected")) {
+            UI.hideVirtualKeyboard();
+        } else {
+            UI.showVirtualKeyboard();
+        }
+    },
+
+    onfocusVirtualKeyboard: function(event) {
+        document.getElementById('noVNC_keyboard_button')
+            .classList.add("noVNC_selected");
+    },
+
+    onblurVirtualKeyboard: function(event) {
+        document.getElementById('noVNC_keyboard_button')
+            .classList.remove("noVNC_selected");
+    },
+
+    keepVirtualKeyboard: function(event) {
+        var input = document.getElementById('noVNC_keyboardinput');
+
+        // Only prevent focus change if the virtual keyboard is active
+        if (document.activeElement != input) {
+            return;
+        }
+
+        // Only allow focus to move to other elements that need
+        // focus to function properly
+        if (event.target.form !== undefined) {
+            switch (event.target.type) {
+                case 'text':
+                case 'email':
+                case 'search':
+                case 'password':
+                case 'tel':
+                case 'url':
+                case 'textarea':
+                case 'select-one':
+                case 'select-multiple':
+                    return;
+            }
+        }
+
+        event.preventDefault();
+    },
+
+    keyboardinputReset: function() {
+        var kbi = document.getElementById('noVNC_keyboardinput');
+        kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
+        UI.lastKeyboardinput = kbi.value;
+    },
+
+    // When normal keyboard events are left uncought, use the input events from
+    // the keyboardinput element instead and generate the corresponding key events.
+    // This code is required since some browsers on Android are inconsistent in
+    // sending keyCodes in the normal keyboard events when using on screen keyboards.
+    keyInput: function(event) {
+
+        if (!UI.rfb) return;
+
+        var newValue = event.target.value;
+
+        if (!UI.lastKeyboardinput) {
+            UI.keyboardinputReset();
+        }
+        var oldValue = UI.lastKeyboardinput;
+
+        var newLen;
+        try {
+            // Try to check caret position since whitespace at the end
+            // will not be considered by value.length in some browsers
+            newLen = Math.max(event.target.selectionStart, newValue.length);
+        } catch (err) {
+            // selectionStart is undefined in Google Chrome
+            newLen = newValue.length;
+        }
+        var oldLen = oldValue.length;
+
+        var backspaces;
+        var inputs = newLen - oldLen;
+        if (inputs < 0) {
+            backspaces = -inputs;
+        } else {
+            backspaces = 0;
+        }
+
+        // Compare the old string with the new to account for
+        // text-corrections or other input that modify existing text
+        var i;
+        for (i = 0; i < Math.min(oldLen, newLen); i++) {
+            if (newValue.charAt(i) != oldValue.charAt(i)) {
+                inputs = newLen - i;
+                backspaces = oldLen - i;
+                break;
+            }
+        }
+
+        // Send the key events
+        for (i = 0; i < backspaces; i++) {
+            UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
+        }
+        for (i = newLen - inputs; i < newLen; i++) {
+            UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
+        }
+
+        // Control the text content length in the keyboardinput element
+        if (newLen > 2 * UI.defaultKeyboardinputLen) {
+            UI.keyboardinputReset();
+        } else if (newLen < 1) {
+            // There always have to be some text in the keyboardinput
+            // element with which backspace can interact.
+            UI.keyboardinputReset();
+            // This sometimes causes the keyboard to disappear for a second
+            // but it is required for the android keyboard to recognize that
+            // text has been added to the field
+            event.target.blur();
+            // This has to be ran outside of the input handler in order to work
+            setTimeout(event.target.focus.bind(event.target), 0);
+        } else {
+            UI.lastKeyboardinput = newValue;
+        }
+    },
+
+/* ------^-------
+ *   /KEYBOARD
+ * ==============
+ *   EXTRA KEYS
+ * ------v------*/
+
+    openExtraKeys: function() {
+        UI.closeAllPanels();
+        UI.openControlbar();
+
+        document.getElementById('noVNC_modifiers')
+            .classList.add("noVNC_open");
+        document.getElementById('noVNC_toggle_extra_keys_button')
+            .classList.add("noVNC_selected");
+    },
+
+    closeExtraKeys: function() {
+        document.getElementById('noVNC_modifiers')
+            .classList.remove("noVNC_open");
+        document.getElementById('noVNC_toggle_extra_keys_button')
+            .classList.remove("noVNC_selected");
+    },
+
+    toggleExtraKeys: function() {
+        if(document.getElementById('noVNC_modifiers')
+            .classList.contains("noVNC_open")) {
+            UI.closeExtraKeys();
+        } else  {
+            UI.openExtraKeys();
+        }
+    },
+
+    sendEsc: function() {
+        UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
+    },
+
+    sendTab: function() {
+        UI.rfb.sendKey(KeyTable.XK_Tab);
+    },
+
+    toggleCtrl: function() {
+        var btn = document.getElementById('noVNC_toggle_ctrl_button');
+        if (btn.classList.contains("noVNC_selected")) {
+            UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+            btn.classList.remove("noVNC_selected");
+        } else {
+            UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+            btn.classList.add("noVNC_selected");
+        }
+    },
+
+    toggleAlt: function() {
+        var btn = document.getElementById('noVNC_toggle_alt_button');
+        if (btn.classList.contains("noVNC_selected")) {
+            UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+            btn.classList.remove("noVNC_selected");
+        } else {
+            UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+            btn.classList.add("noVNC_selected");
+        }
+    },
+
+    sendCtrlAltDel: function() {
+        UI.rfb.sendCtrlAltDel();
+    },
+
+/* ------^-------
+ *   /EXTRA KEYS
+ * ==============
+ *     MISC
+ * ------v------*/
+
+    setMouseButton: function(num) {
+        var view_only = UI.rfb.get_view_only();
+        if (UI.rfb && !view_only) {
+            UI.rfb.get_mouse().set_touchButton(num);
+        }
+
+        var blist = [0, 1,2,4];
+        for (var b = 0; b < blist.length; b++) {
+            var button = document.getElementById('noVNC_mouse_button' +
+                                                 blist[b]);
+            if (blist[b] === num && !view_only) {
+                button.classList.remove("noVNC_hidden");
+            } else {
+                button.classList.add("noVNC_hidden");
+            }
+        }
+    },
+
+    displayBlur: function() {
+        if (UI.rfb && !UI.rfb.get_view_only()) {
+            UI.rfb.get_keyboard().set_focused(false);
+            UI.rfb.get_mouse().set_focused(false);
+        }
+    },
+
+    displayFocus: function() {
+        if (UI.rfb && !UI.rfb.get_view_only()) {
+            UI.rfb.get_keyboard().set_focused(true);
+            UI.rfb.get_mouse().set_focused(true);
+        }
+    },
+
+    updateLocalCursor: function() {
+        if (!UI.rfb) return;
+        UI.rfb.set_local_cursor(UI.getSetting('cursor'));
+    },
+
+    updateViewOnly: function() {
+        if (!UI.rfb) return;
+        UI.rfb.set_view_only(UI.getSetting('view_only'));
+    },
+
+    updateLogging: function() {
+        WebUtil.init_logging(UI.getSetting('logging'));
+    },
+
+    updateSessionSize: function(rfb, width, height) {
+        UI.updateViewClip();
+        UI.fixScrollbars();
+    },
+
+    fixScrollbars: function() {
+        // This is a hack because Chrome screws up the calculation
+        // for when scrollbars are needed. So to fix it we temporarily
+        // toggle them off and on.
+        var screen = document.getElementById('noVNC_screen');
+        screen.style.overflow = 'hidden';
+        // Force Chrome to recalculate the layout by asking for
+        // an element's dimensions
+        screen.getBoundingClientRect();
+        screen.style.overflow = "";
+    },
+
+    updateDesktopName: function(rfb, name) {
+        UI.desktopName = name;
+        // Display the desktop name in the document title
+        document.title = name + " - noVNC";
+    },
+
+    bell: function(rfb) {
+        if (WebUtil.getConfigVar('bell', 'on') === 'on') {
+            document.getElementById('noVNC_bell').play()
+                .catch(function(e) {
+                    if (e.name === "NotAllowedError") {
+                        // Ignore when the browser doesn't let us play audio.
+                        // It is common that the browsers require audio to be
+                        // initiated from a user action.
+                    } else {
+                        Log.Error("Unable to play bell: " + e);
+                    }
+                });
+        }
+    },
+
+    //Helper to add options to dropdown.
+    addOption: function(selectbox, text, value) {
+        var optn = document.createElement("OPTION");
+        optn.text = text;
+        optn.value = value;
+        selectbox.options.add(optn);
+    },
+
+/* ------^-------
+ *    /MISC
+ * ==============
+ */
+};
+
+// Set up translations
+var LINGUAS = ["de", "el", "nl", "sv"];
+l10n.setup(LINGUAS);
+if (l10n.language !== "en" && l10n.dictionary === undefined) {
+    WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {
+        l10n.dictionary = translations;
+
+        // wait for translations to load before loading the UI
+        UI.prime();
+    }, function (err) {
+        throw err;
+    });
+} else {
+    UI.prime();
+}
+
+export default UI;

+ 235 - 0
static/novnc/app/webutil.js

@@ -0,0 +1,235 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 NTT corp.
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint bitwise: false, white: false, browser: true, devel: true */
+/*global Util, window, document */
+
+import { init_logging as main_init_logging } from '../core/util/logging.js';
+
+// init log level reading the logging HTTP param
+export function init_logging (level) {
+    "use strict";
+    if (typeof level !== "undefined") {
+        main_init_logging(level);
+    } else {
+        var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/);
+        main_init_logging(param || undefined);
+    }
+};
+
+// Read a query string variable
+export function getQueryVar (name, defVal) {
+    "use strict";
+    var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
+        match = document.location.href.match(re);
+    if (typeof defVal === 'undefined') { defVal = null; }
+    if (match) {
+        return decodeURIComponent(match[1]);
+    } else {
+        return defVal;
+    }
+};
+
+// Read a hash fragment variable
+export function getHashVar (name, defVal) {
+    "use strict";
+    var re = new RegExp('.*[&#]' + name + '=([^&]*)'),
+        match = document.location.hash.match(re);
+    if (typeof defVal === 'undefined') { defVal = null; }
+    if (match) {
+        return decodeURIComponent(match[1]);
+    } else {
+        return defVal;
+    }
+};
+
+// Read a variable from the fragment or the query string
+// Fragment takes precedence
+export function getConfigVar (name, defVal) {
+    "use strict";
+    var val = getHashVar(name);
+    if (val === null) {
+        val = getQueryVar(name, defVal);
+    }
+    return val;
+};
+
+/*
+ * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
+ */
+
+// No days means only for this browser session
+export function createCookie (name, value, days) {
+    "use strict";
+    var date, expires;
+    if (days) {
+        date = new Date();
+        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+        expires = "; expires=" + date.toGMTString();
+    } else {
+        expires = "";
+    }
+
+    var secure;
+    if (document.location.protocol === "https:") {
+        secure = "; secure";
+    } else {
+        secure = "";
+    }
+    document.cookie = name + "=" + value + expires + "; path=/" + secure;
+};
+
+export function readCookie (name, defaultValue) {
+    "use strict";
+    var nameEQ = name + "=",
+        ca = document.cookie.split(';');
+
+    for (var i = 0; i < ca.length; i += 1) {
+        var c = ca[i];
+        while (c.charAt(0) === ' ') { c = c.substring(1, c.length); }
+        if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); }
+    }
+    return (typeof defaultValue !== 'undefined') ? defaultValue : null;
+};
+
+export function eraseCookie (name) {
+    "use strict";
+    createCookie(name, "", -1);
+};
+
+/*
+ * Setting handling.
+ */
+
+var settings = {};
+
+export function initSettings (callback /*, ...callbackArgs */) {
+    "use strict";
+    var callbackArgs = Array.prototype.slice.call(arguments, 1);
+    if (window.chrome && window.chrome.storage) {
+        window.chrome.storage.sync.get(function (cfg) {
+            settings = cfg;
+            console.log(settings);
+            if (callback) {
+                callback.apply(this, callbackArgs);
+            }
+        });
+    } else {
+        // No-op
+        if (callback) {
+            callback.apply(this, callbackArgs);
+        }
+    }
+};
+
+// No days means only for this browser session
+export function writeSetting (name, value) {
+    "use strict";
+    if (window.chrome && window.chrome.storage) {
+        //console.log("writeSetting:", name, value);
+        if (settings[name] !== value) {
+            settings[name] = value;
+            window.chrome.storage.sync.set(settings);
+        }
+    } else {
+        localStorage.setItem(name, value);
+    }
+};
+
+export function readSetting (name, defaultValue) {
+    "use strict";
+    var value;
+    if (window.chrome && window.chrome.storage) {
+        value = settings[name];
+    } else {
+        value = localStorage.getItem(name);
+    }
+    if (typeof value === "undefined") {
+        value = null;
+    }
+    if (value === null && typeof defaultValue !== undefined) {
+        return defaultValue;
+    } else {
+        return value;
+    }
+};
+
+export function eraseSetting (name) {
+    "use strict";
+    if (window.chrome && window.chrome.storage) {
+        window.chrome.storage.sync.remove(name);
+        delete settings[name];
+    } else {
+        localStorage.removeItem(name);
+    }
+};
+
+export function injectParamIfMissing (path, param, value) {
+    // force pretend that we're dealing with a relative path
+    // (assume that we wanted an extra if we pass one in)
+    path = "/" + path;
+
+    var elem = document.createElement('a');
+    elem.href = path;
+
+    var param_eq = encodeURIComponent(param) + "=";
+    var query;
+    if (elem.search) {
+        query = elem.search.slice(1).split('&');
+    } else {
+        query = [];
+    }
+
+    if (!query.some(function (v) { return v.startsWith(param_eq); })) {
+        query.push(param_eq + encodeURIComponent(value));
+        elem.search = "?" + query.join("&");
+    }
+
+    // some browsers (e.g. IE11) may occasionally omit the leading slash
+    // in the elem.pathname string. Handle that case gracefully.
+    if (elem.pathname.charAt(0) == "/") {
+        return elem.pathname.slice(1) + elem.search + elem.hash;
+    } else {
+        return elem.pathname + elem.search + elem.hash;
+    }
+};
+
+// sadly, we can't use the Fetch API until we decide to drop
+// IE11 support or polyfill promises and fetch in IE11.
+// resolve will receive an object on success, while reject
+// will receive either an event or an error on failure.
+export function fetchJSON(path, resolve, reject) {
+    // NB: IE11 doesn't support JSON as a responseType
+    var req = new XMLHttpRequest();
+    req.open('GET', path);
+
+    req.onload = function () {
+        if (req.status === 200) {
+            try {
+                var resObj = JSON.parse(req.responseText);
+            } catch (err) {
+                reject(err);
+                return;
+            }
+            resolve(resObj);
+        } else {
+            reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
+        }
+    };
+
+    req.onerror = function (evt) {
+        reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
+    };
+
+    req.ontimeout = function (evt) {
+        reject(new Error("XHR timed out while trying to load '" + path + "'"));
+    };
+
+    req.send();
+}

+ 113 - 0
static/novnc/core/base64.js

@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
+
+/*jslint white: false */
+/*global console */
+
+export default {
+    /* Convert data (an array of integers) to a Base64 string. */
+    toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
+    base64Pad     : '=',
+
+    encode: function (data) {
+        "use strict";
+        var result = '';
+        var toBase64Table = this.toBase64Table;
+        var length = data.length;
+        var lengthpad = (length % 3);
+        // Convert every three bytes to 4 ascii characters.
+
+        for (var i = 0; i < (length - 2); i += 3) {
+            result += toBase64Table[data[i] >> 2];
+            result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
+            result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
+            result += toBase64Table[data[i + 2] & 0x3f];
+        }
+
+        // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+        var j = 0;
+        if (lengthpad === 2) {
+            j = length - lengthpad;
+            result += toBase64Table[data[j] >> 2];
+            result += toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
+            result += toBase64Table[(data[j + 1] & 0x0f) << 2];
+            result += toBase64Table[64];
+        } else if (lengthpad === 1) {
+            j = length - lengthpad;
+            result += toBase64Table[data[j] >> 2];
+            result += toBase64Table[(data[j] & 0x03) << 4];
+            result += toBase64Table[64];
+            result += toBase64Table[64];
+        }
+
+        return result;
+    },
+
+    /* Convert Base64 data to a string */
+    /* jshint -W013 */
+    toBinaryTable : [
+        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+        -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+        52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+        -1, 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,-1, -1,-1,-1,-1,
+        -1,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,-1, -1,-1,-1,-1
+    ],
+    /* jshint +W013 */
+
+    decode: function (data, offset) {
+        "use strict";
+        offset = typeof(offset) !== 'undefined' ? offset : 0;
+        var toBinaryTable = this.toBinaryTable;
+        var base64Pad = this.base64Pad;
+        var result, result_length;
+        var leftbits = 0; // number of bits decoded, but yet to be appended
+        var leftdata = 0; // bits decoded, but yet to be appended
+        var data_length = data.indexOf('=') - offset;
+
+        if (data_length < 0) { data_length = data.length - offset; }
+
+        /* Every four characters is 3 resulting numbers */
+        result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
+        result = new Array(result_length);
+
+        // Convert one by one.
+        for (var idx = 0, i = offset; i < data.length; i++) {
+            var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+            var padding = (data.charAt(i) === base64Pad);
+            // Skip illegal characters and whitespace
+            if (c === -1) {
+                console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
+                continue;
+            }
+
+            // Collect data into leftdata, update bitcount
+            leftdata = (leftdata << 6) | c;
+            leftbits += 6;
+
+            // If we have 8 or more bits, append 8 bits to the result
+            if (leftbits >= 8) {
+                leftbits -= 8;
+                // Append if not padding.
+                if (!padding) {
+                    result[idx++] = (leftdata >> leftbits) & 0xff;
+                }
+                leftdata &= (1 << leftbits) - 1;
+            }
+        }
+
+        // If there are any bits left, the base64 string was corrupted
+        if (leftbits) {
+            err = new Error('Corrupted base64 string');
+            err.name = 'Base64-Error';
+            throw err;
+        }
+
+        return result;
+    }
+}; /* End of Base64 namespace */

+ 276 - 0
static/novnc/core/des.js

@@ -0,0 +1,276 @@
+/*
+ * Ported from Flashlight VNC ActionScript implementation:
+ *     http://www.wizhelp.com/flashlight-vnc/
+ *
+ * Full attribution follows:
+ *
+ * -------------------------------------------------------------------------
+ *
+ * This DES class has been extracted from package Acme.Crypto for use in VNC.
+ * The unnecessary odd parity code has been removed.
+ *
+ * These changes are:
+ *  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+
+ * DesCipher - the DES encryption method
+ *
+ * The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
+ *
+ * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+ * without fee is hereby granted, provided that this copyright notice is kept
+ * intact.
+ *
+ * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+ * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ *
+ * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+ * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+ * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+ * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+ * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+ * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  WIDGET WORKSHOP
+ * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+ * HIGH RISK ACTIVITIES.
+ *
+ *
+ * The rest is:
+ *
+ * Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Visit the ACME Labs Java page for up-to-date versions of this and other
+ * fine Java utilities: http://www.acme.com/java/
+ */
+
+/* jslint white: false */
+
+export default function DES(passwd) {
+    "use strict";
+
+    // Tables, permutations, S-boxes, etc.
+    // jshint -W013
+    var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
+               25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
+               50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
+        totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28],
+        z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8,
+        keys = [];
+
+    // jshint -W015
+    a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
+    SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
+           z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
+           a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
+           c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
+    a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
+    SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
+           a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
+           z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
+           z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
+    a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
+    SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
+           b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
+           c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
+           b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
+    a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
+    SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
+           z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
+           b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
+           c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
+    a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
+    SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
+           a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
+           z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
+           c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
+    a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
+    SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
+           z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
+           b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
+           a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
+    a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
+    SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
+           b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
+           b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
+           z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
+    a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
+    SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
+           c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
+           a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
+           z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
+    // jshint +W013,+W015
+
+    // Set the key.
+    function setKeys(keyBlock) {
+        var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
+            raw0, raw1, rawi, KnLi;
+
+        for (j = 0, l = 56; j < 56; ++j, l -= 8) {
+            l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
+            m = l & 0x7;
+            pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
+        }
+
+        for (i = 0; i < 16; ++i) {
+            m = i << 1;
+            n = m + 1;
+            kn[m] = kn[n] = 0;
+            for (o = 28; o < 59; o += 28) {
+                for (j = o - 28; j < o; ++j) {
+                    l = j + totrot[i];
+                    if (l < o) {
+                        pcr[j] = pc1m[l];
+                    } else {
+                        pcr[j] = pc1m[l - 28];
+                    }
+                }
+            }
+            for (j = 0; j < 24; ++j) {
+                if (pcr[PC2[j]] !== 0) {
+                    kn[m] |= 1 << (23 - j);
+                }
+                if (pcr[PC2[j + 24]] !== 0) {
+                    kn[n] |= 1 << (23 - j);
+                }
+            }
+        }
+
+        // cookey
+        for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
+            raw0 = kn[rawi++];
+            raw1 = kn[rawi++];
+            keys[KnLi] = (raw0 & 0x00fc0000) << 6;
+            keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
+            keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+            keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+            ++KnLi;
+            keys[KnLi] = (raw0 & 0x0003f000) << 12;
+            keys[KnLi] |= (raw0 & 0x0000003f) << 16;
+            keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+            keys[KnLi] |= (raw1 & 0x0000003f);
+            ++KnLi;
+        }
+    }
+
+    // Encrypt 8 bytes of text
+    function enc8(text) {
+        var i = 0, b = text.slice(), fval, keysi = 0,
+            l, r, x; // left, right, accumulator
+
+        // Squash 8 bytes to 2 ints
+        l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+        r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+
+        x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
+        r ^= x;
+        l ^= (x << 4);
+        x = ((l >>> 16) ^ r) & 0x0000ffff;
+        r ^= x;
+        l ^= (x << 16);
+        x = ((r >>> 2) ^ l) & 0x33333333;
+        l ^= x;
+        r ^= (x << 2);
+        x = ((r >>> 8) ^ l) & 0x00ff00ff;
+        l ^= x;
+        r ^= (x << 8);
+        r = (r << 1) | ((r >>> 31) & 1);
+        x = (l ^ r) & 0xaaaaaaaa;
+        l ^= x;
+        r ^= x;
+        l = (l << 1) | ((l >>> 31) & 1);
+
+        for (i = 0; i < 8; ++i) {
+            x = (r << 28) | (r >>> 4);
+            x ^= keys[keysi++];
+            fval =  SP7[x & 0x3f];
+            fval |= SP5[(x >>> 8) & 0x3f];
+            fval |= SP3[(x >>> 16) & 0x3f];
+            fval |= SP1[(x >>> 24) & 0x3f];
+            x = r ^ keys[keysi++];
+            fval |= SP8[x & 0x3f];
+            fval |= SP6[(x >>> 8) & 0x3f];
+            fval |= SP4[(x >>> 16) & 0x3f];
+            fval |= SP2[(x >>> 24) & 0x3f];
+            l ^= fval;
+            x = (l << 28) | (l >>> 4);
+            x ^= keys[keysi++];
+            fval =  SP7[x & 0x3f];
+            fval |= SP5[(x >>> 8) & 0x3f];
+            fval |= SP3[(x >>> 16) & 0x3f];
+            fval |= SP1[(x >>> 24) & 0x3f];
+            x = l ^ keys[keysi++];
+            fval |= SP8[x & 0x0000003f];
+            fval |= SP6[(x >>> 8) & 0x3f];
+            fval |= SP4[(x >>> 16) & 0x3f];
+            fval |= SP2[(x >>> 24) & 0x3f];
+            r ^= fval;
+        }
+
+        r = (r << 31) | (r >>> 1);
+        x = (l ^ r) & 0xaaaaaaaa;
+        l ^= x;
+        r ^= x;
+        l = (l << 31) | (l >>> 1);
+        x = ((l >>> 8) ^ r) & 0x00ff00ff;
+        r ^= x;
+        l ^= (x << 8);
+        x = ((l >>> 2) ^ r) & 0x33333333;
+        r ^= x;
+        l ^= (x << 2);
+        x = ((r >>> 16) ^ l) & 0x0000ffff;
+        l ^= x;
+        r ^= (x << 16);
+        x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
+        l ^= x;
+        r ^= (x << 4);
+
+        // Spread ints to bytes
+        x = [r, l];
+        for (i = 0; i < 8; i++) {
+            b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
+            if (b[i] < 0) { b[i] += 256; } // unsigned
+        }
+        return b;
+    }
+
+    // Encrypt 16 bytes of text using passwd as key
+    function encrypt(t) {
+        return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
+    }
+
+    setKeys(passwd);             // Setup keys
+    return {'encrypt': encrypt}; // Public interface
+
+}; // function DES

+ 749 - 0
static/novnc/core/display.js

@@ -0,0 +1,749 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2015 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint browser: true, white: false */
+/*global Util, Base64, changeCursor */
+
+import { browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js';
+import { set_defaults, make_properties } from './util/properties.js';
+import * as Log from './util/logging.js';
+import Base64 from "./base64.js";
+
+export default function Display(defaults) {
+    this._drawCtx = null;
+    this._c_forceCanvas = false;
+
+    this._renderQ = [];  // queue drawing actions for in-oder rendering
+    this._flushing = false;
+
+    // the full frame buffer (logical canvas) size
+    this._fb_width = 0;
+    this._fb_height = 0;
+
+    this._prevDrawStyle = "";
+    this._tile = null;
+    this._tile16x16 = null;
+    this._tile_x = 0;
+    this._tile_y = 0;
+
+    set_defaults(this, defaults, {
+        'scale': 1.0,
+        'viewport': false,
+        'render_mode': '',
+        "onFlush": function () {},
+    });
+
+    Log.Debug(">> Display.constructor");
+
+    // The visible canvas
+    if (!this._target) {
+        throw new Error("Target must be set");
+    }
+
+    if (typeof this._target === 'string') {
+        throw new Error('target must be a DOM element');
+    }
+
+    if (!this._target.getContext) {
+        throw new Error("no getContext method");
+    }
+
+    this._targetCtx = this._target.getContext('2d');
+
+    // the visible canvas viewport (i.e. what actually gets seen)
+    this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
+
+    // The hidden canvas, where we do the actual rendering
+    this._backbuffer = document.createElement('canvas');
+    this._drawCtx = this._backbuffer.getContext('2d');
+
+    this._damageBounds = { left:0, top:0,
+                           right: this._backbuffer.width,
+                           bottom: this._backbuffer.height };
+
+    Log.Debug("User Agent: " + navigator.userAgent);
+
+    this.clear();
+
+    // Check canvas features
+    if ('createImageData' in this._drawCtx) {
+        this._render_mode = 'canvas rendering';
+    } else {
+        throw new Error("Canvas does not support createImageData");
+    }
+
+    if (this._prefer_js === null) {
+        Log.Info("Prefering javascript operations");
+        this._prefer_js = true;
+    }
+
+    // Determine browser support for setting the cursor via data URI scheme
+    if (this._cursor_uri || this._cursor_uri === null ||
+            this._cursor_uri === undefined) {
+        this._cursor_uri = cursorURIsSupported();
+    }
+
+    Log.Debug("<< Display.constructor");
+};
+
+var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
+try {
+    new ImageData(new Uint8ClampedArray(4), 1, 1);
+    SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
+} catch (ex) {
+    // ignore failure
+}
+
+Display.prototype = {
+    // Public methods
+    viewportChangePos: function (deltaX, deltaY) {
+        var vp = this._viewportLoc;
+        deltaX = Math.floor(deltaX);
+        deltaY = Math.floor(deltaY);
+
+        if (!this._viewport) {
+            deltaX = -vp.w;  // clamped later of out of bounds
+            deltaY = -vp.h;
+        }
+
+        var vx2 = vp.x + vp.w - 1;
+        var vy2 = vp.y + vp.h - 1;
+
+        // Position change
+
+        if (deltaX < 0 && vp.x + deltaX < 0) {
+            deltaX = -vp.x;
+        }
+        if (vx2 + deltaX >= this._fb_width) {
+            deltaX -= vx2 + deltaX - this._fb_width + 1;
+        }
+
+        if (vp.y + deltaY < 0) {
+            deltaY = -vp.y;
+        }
+        if (vy2 + deltaY >= this._fb_height) {
+            deltaY -= (vy2 + deltaY - this._fb_height + 1);
+        }
+
+        if (deltaX === 0 && deltaY === 0) {
+            return;
+        }
+        Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
+
+        vp.x += deltaX;
+        vp.y += deltaY;
+
+        this._damage(vp.x, vp.y, vp.w, vp.h);
+
+        this.flip();
+    },
+
+    viewportChangeSize: function(width, height) {
+
+        if (!this._viewport ||
+            typeof(width) === "undefined" ||
+            typeof(height) === "undefined") {
+
+            Log.Debug("Setting viewport to full display region");
+            width = this._fb_width;
+            height = this._fb_height;
+        }
+
+        if (width > this._fb_width) {
+            width = this._fb_width;
+        }
+        if (height > this._fb_height) {
+            height = this._fb_height;
+        }
+
+        var vp = this._viewportLoc;
+        if (vp.w !== width || vp.h !== height) {
+            vp.w = width;
+            vp.h = height;
+
+            var canvas = this._target;
+            canvas.width = width;
+            canvas.height = height;
+
+            // The position might need to be updated if we've grown
+            this.viewportChangePos(0, 0);
+
+            this._damage(vp.x, vp.y, vp.w, vp.h);
+            this.flip();
+
+            // Update the visible size of the target canvas
+            this._rescale(this._scale);
+        }
+    },
+
+    absX: function (x) {
+        return x / this._scale + this._viewportLoc.x;
+    },
+
+    absY: function (y) {
+        return y / this._scale + this._viewportLoc.y;
+    },
+
+    resize: function (width, height) {
+        this._prevDrawStyle = "";
+
+        this._fb_width = width;
+        this._fb_height = height;
+
+        var canvas = this._backbuffer;
+        if (canvas.width !== width || canvas.height !== height) {
+
+            // We have to save the canvas data since changing the size will clear it
+            var saveImg = null;
+            if (canvas.width > 0 && canvas.height > 0) {
+                saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
+            }
+
+            if (canvas.width !== width) {
+                canvas.width = width;
+            }
+            if (canvas.height !== height) {
+                canvas.height = height;
+            }
+
+            if (saveImg) {
+                this._drawCtx.putImageData(saveImg, 0, 0);
+            }
+        }
+
+        // Readjust the viewport as it may be incorrectly sized
+        // and positioned
+        var vp = this._viewportLoc;
+        this.viewportChangeSize(vp.w, vp.h);
+        this.viewportChangePos(0, 0);
+    },
+
+    // Track what parts of the visible canvas that need updating
+    _damage: function(x, y, w, h) {
+        if (x < this._damageBounds.left) {
+            this._damageBounds.left = x;
+        }
+        if (y < this._damageBounds.top) {
+            this._damageBounds.top = y;
+        }
+        if ((x + w) > this._damageBounds.right) {
+            this._damageBounds.right = x + w;
+        }
+        if ((y + h) > this._damageBounds.bottom) {
+            this._damageBounds.bottom = y + h;
+        }
+    },
+
+    // Update the visible canvas with the contents of the
+    // rendering canvas
+    flip: function(from_queue) {
+        if (this._renderQ.length !== 0 && !from_queue) {
+            this._renderQ_push({
+                'type': 'flip'
+            });
+        } else {
+            var x, y, vx, vy, w, h;
+
+            x = this._damageBounds.left;
+            y = this._damageBounds.top;
+            w = this._damageBounds.right - x;
+            h = this._damageBounds.bottom - y;
+
+            vx = x - this._viewportLoc.x;
+            vy = y - this._viewportLoc.y;
+
+            if (vx < 0) {
+                w += vx;
+                x -= vx;
+                vx = 0;
+            }
+            if (vy < 0) {
+                h += vy;
+                y -= vy;
+                vy = 0;
+            }
+
+            if ((vx + w) > this._viewportLoc.w) {
+                w = this._viewportLoc.w - vx;
+            }
+            if ((vy + h) > this._viewportLoc.h) {
+                h = this._viewportLoc.h - vy;
+            }
+
+            if ((w > 0) && (h > 0)) {
+                // FIXME: We may need to disable image smoothing here
+                //        as well (see copyImage()), but we haven't
+                //        noticed any problem yet.
+                this._targetCtx.drawImage(this._backbuffer,
+                                          x, y, w, h,
+                                          vx, vy, w, h);
+            }
+
+            this._damageBounds.left = this._damageBounds.top = 65535;
+            this._damageBounds.right = this._damageBounds.bottom = 0;
+        }
+    },
+
+    clear: function () {
+        if (this._logo) {
+            this.resize(this._logo.width, this._logo.height);
+            this.imageRect(0, 0, this._logo.type, this._logo.data);
+        } else {
+            this.resize(240, 20);
+            this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
+        }
+        this.flip();
+    },
+
+    pending: function() {
+        return this._renderQ.length > 0;
+    },
+
+    flush: function() {
+        if (this._renderQ.length === 0) {
+            this._onFlush();
+        } else {
+            this._flushing = true;
+        }
+    },
+
+    fillRect: function (x, y, width, height, color, from_queue) {
+        if (this._renderQ.length !== 0 && !from_queue) {
+            this._renderQ_push({
+                'type': 'fill',
+                'x': x,
+                'y': y,
+                'width': width,
+                'height': height,
+                'color': color
+            });
+        } else {
+            this._setFillColor(color);
+            this._drawCtx.fillRect(x, y, width, height);
+            this._damage(x, y, width, height);
+        }
+    },
+
+    copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
+        if (this._renderQ.length !== 0 && !from_queue) {
+            this._renderQ_push({
+                'type': 'copy',
+                'old_x': old_x,
+                'old_y': old_y,
+                'x': new_x,
+                'y': new_y,
+                'width': w,
+                'height': h,
+            });
+        } else {
+            // Due to this bug among others [1] we need to disable the image-smoothing to
+            // avoid getting a blur effect when copying data.
+            //
+            // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
+            //
+            // We need to set these every time since all properties are reset
+            // when the the size is changed
+            this._drawCtx.mozImageSmoothingEnabled = false;
+            this._drawCtx.webkitImageSmoothingEnabled = false;
+            this._drawCtx.msImageSmoothingEnabled = false;
+            this._drawCtx.imageSmoothingEnabled = false;
+
+            this._drawCtx.drawImage(this._backbuffer,
+                                    old_x, old_y, w, h,
+                                    new_x, new_y, w, h);
+            this._damage(new_x, new_y, w, h);
+        }
+    },
+
+    imageRect: function(x, y, mime, arr) {
+        var img = new Image();
+        img.src = "data: " + mime + ";base64," + Base64.encode(arr);
+        this._renderQ_push({
+            'type': 'img',
+            'img': img,
+            'x': x,
+            'y': y
+        });
+    },
+
+    // start updating a tile
+    startTile: function (x, y, width, height, color) {
+        this._tile_x = x;
+        this._tile_y = y;
+        if (width === 16 && height === 16) {
+            this._tile = this._tile16x16;
+        } else {
+            this._tile = this._drawCtx.createImageData(width, height);
+        }
+
+        if (this._prefer_js) {
+            var red = color[2];
+            var green = color[1];
+            var blue = color[0];
+
+            var data = this._tile.data;
+            for (var i = 0; i < width * height * 4; i += 4) {
+                data[i] = red;
+                data[i + 1] = green;
+                data[i + 2] = blue;
+                data[i + 3] = 255;
+            }
+        } else {
+            this.fillRect(x, y, width, height, color, true);
+        }
+    },
+
+    // update sub-rectangle of the current tile
+    subTile: function (x, y, w, h, color) {
+        if (this._prefer_js) {
+            var red = color[2];
+            var green = color[1];
+            var blue = color[0];
+            var xend = x + w;
+            var yend = y + h;
+
+            var data = this._tile.data;
+            var width = this._tile.width;
+            for (var j = y; j < yend; j++) {
+                for (var i = x; i < xend; i++) {
+                    var p = (i + (j * width)) * 4;
+                    data[p] = red;
+                    data[p + 1] = green;
+                    data[p + 2] = blue;
+                    data[p + 3] = 255;
+                }
+            }
+        } else {
+            this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true);
+        }
+    },
+
+    // draw the current tile to the screen
+    finishTile: function () {
+        if (this._prefer_js) {
+            this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
+            this._damage(this._tile_x, this._tile_y,
+                         this._tile.width, this._tile.height);
+        }
+        // else: No-op -- already done by setSubTile
+    },
+
+    blitImage: function (x, y, width, height, arr, offset, from_queue) {
+        if (this._renderQ.length !== 0 && !from_queue) {
+            // NB(directxman12): it's technically more performant here to use preallocated arrays,
+            // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+            // this probably isn't getting called *nearly* as much
+            var new_arr = new Uint8Array(width * height * 4);
+            new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+            this._renderQ_push({
+                'type': 'blit',
+                'data': new_arr,
+                'x': x,
+                'y': y,
+                'width': width,
+                'height': height,
+            });
+        } else {
+            this._bgrxImageData(x, y, width, height, arr, offset);
+        }
+    },
+
+    blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
+        if (this._renderQ.length !== 0 && !from_queue) {
+            // NB(directxman12): it's technically more performant here to use preallocated arrays,
+            // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+            // this probably isn't getting called *nearly* as much
+            var new_arr = new Uint8Array(width * height * 3);
+            new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+            this._renderQ_push({
+                'type': 'blitRgb',
+                'data': new_arr,
+                'x': x,
+                'y': y,
+                'width': width,
+                'height': height,
+            });
+        } else {
+            this._rgbImageData(x, y, width, height, arr, offset);
+        }
+    },
+
+    blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
+        if (this._renderQ.length !== 0 && !from_queue) {
+            // NB(directxman12): it's technically more performant here to use preallocated arrays,
+            // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+            // this probably isn't getting called *nearly* as much
+            var new_arr = new Uint8Array(width * height * 4);
+            new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+            this._renderQ_push({
+                'type': 'blitRgbx',
+                'data': new_arr,
+                'x': x,
+                'y': y,
+                'width': width,
+                'height': height,
+            });
+        } else {
+            this._rgbxImageData(x, y, width, height, arr, offset);
+        }
+    },
+
+    drawImage: function (img, x, y) {
+        this._drawCtx.drawImage(img, x, y);
+        this._damage(x, y, img.width, img.height);
+    },
+
+    changeCursor: function (pixels, mask, hotx, hoty, w, h) {
+        if (this._cursor_uri === false) {
+            Log.Warn("changeCursor called but no cursor data URI support");
+            return;
+        }
+
+        Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
+    },
+
+    defaultCursor: function () {
+        this._target.style.cursor = "default";
+    },
+
+    disableLocalCursor: function () {
+        this._target.style.cursor = "none";
+    },
+
+    clippingDisplay: function () {
+        var vp = this._viewportLoc;
+        return this._fb_width > vp.w || this._fb_height > vp.h;
+    },
+
+    // Overridden getters/setters
+    set_scale: function (scale) {
+        this._rescale(scale);
+    },
+
+    set_viewport: function (viewport) {
+        this._viewport = viewport;
+        // May need to readjust the viewport dimensions
+        var vp = this._viewportLoc;
+        this.viewportChangeSize(vp.w, vp.h);
+        this.viewportChangePos(0, 0);
+    },
+
+    get_width: function () {
+        return this._fb_width;
+    },
+    get_height: function () {
+        return this._fb_height;
+    },
+
+    autoscale: function (containerWidth, containerHeight, downscaleOnly) {
+        var vp = this._viewportLoc;
+        var targetAspectRatio = containerWidth / containerHeight;
+        var fbAspectRatio = vp.w / vp.h;
+
+        var scaleRatio;
+        if (fbAspectRatio >= targetAspectRatio) {
+            scaleRatio = containerWidth / vp.w;
+        } else {
+            scaleRatio = containerHeight / vp.h;
+        }
+
+        if (scaleRatio > 1.0 && downscaleOnly) {
+            scaleRatio = 1.0;
+        }
+
+        this._rescale(scaleRatio);
+    },
+
+    // Private Methods
+    _rescale: function (factor) {
+        this._scale = factor;
+        var vp = this._viewportLoc;
+
+        // NB(directxman12): If you set the width directly, or set the
+        //                   style width to a number, the canvas is cleared.
+        //                   However, if you set the style width to a string
+        //                   ('NNNpx'), the canvas is scaled without clearing.
+        var width = Math.round(factor * vp.w) + 'px';
+        var height = Math.round(factor * vp.h) + 'px';
+
+        if ((this._target.style.width !== width) ||
+            (this._target.style.height !== height)) {
+            this._target.style.width = width;
+            this._target.style.height = height;
+        }
+    },
+
+    _setFillColor: function (color) {
+        var newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
+        if (newStyle !== this._prevDrawStyle) {
+            this._drawCtx.fillStyle = newStyle;
+            this._prevDrawStyle = newStyle;
+        }
+    },
+
+    _rgbImageData: function (x, y, width, height, arr, offset) {
+        var img = this._drawCtx.createImageData(width, height);
+        var data = img.data;
+        for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
+            data[i]     = arr[j];
+            data[i + 1] = arr[j + 1];
+            data[i + 2] = arr[j + 2];
+            data[i + 3] = 255;  // Alpha
+        }
+        this._drawCtx.putImageData(img, x, y);
+        this._damage(x, y, img.width, img.height);
+    },
+
+    _bgrxImageData: function (x, y, width, height, arr, offset) {
+        var img = this._drawCtx.createImageData(width, height);
+        var data = img.data;
+        for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
+            data[i]     = arr[j + 2];
+            data[i + 1] = arr[j + 1];
+            data[i + 2] = arr[j];
+            data[i + 3] = 255;  // Alpha
+        }
+        this._drawCtx.putImageData(img, x, y);
+        this._damage(x, y, img.width, img.height);
+    },
+
+    _rgbxImageData: function (x, y, width, height, arr, offset) {
+        // NB(directxman12): arr must be an Type Array view
+        var img;
+        if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
+            img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
+        } else {
+            img = this._drawCtx.createImageData(width, height);
+            img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
+        }
+        this._drawCtx.putImageData(img, x, y);
+        this._damage(x, y, img.width, img.height);
+    },
+
+    _renderQ_push: function (action) {
+        this._renderQ.push(action);
+        if (this._renderQ.length === 1) {
+            // If this can be rendered immediately it will be, otherwise
+            // the scanner will wait for the relevant event
+            this._scan_renderQ();
+        }
+    },
+
+    _resume_renderQ: function() {
+        // "this" is the object that is ready, not the
+        // display object
+        this.removeEventListener('load', this._noVNC_display._resume_renderQ);
+        this._noVNC_display._scan_renderQ();
+    },
+
+    _scan_renderQ: function () {
+        var ready = true;
+        while (ready && this._renderQ.length > 0) {
+            var a = this._renderQ[0];
+            switch (a.type) {
+                case 'flip':
+                    this.flip(true);
+                    break;
+                case 'copy':
+                    this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
+                    break;
+                case 'fill':
+                    this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
+                    break;
+                case 'blit':
+                    this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+                    break;
+                case 'blitRgb':
+                    this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+                    break;
+                case 'blitRgbx':
+                    this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+                    break;
+                case 'img':
+                    if (a.img.complete) {
+                        this.drawImage(a.img, a.x, a.y);
+                    } else {
+                        a.img._noVNC_display = this;
+                        a.img.addEventListener('load', this._resume_renderQ);
+                        // We need to wait for this image to 'load'
+                        // to keep things in-order
+                        ready = false;
+                    }
+                    break;
+            }
+
+            if (ready) {
+                this._renderQ.shift();
+            }
+        }
+
+        if (this._renderQ.length === 0 && this._flushing) {
+            this._flushing = false;
+            this._onFlush();
+        }
+    },
+};
+
+make_properties(Display, [
+    ['target', 'wo', 'dom'],       // Canvas element for rendering
+    ['context', 'ro', 'raw'],      // Canvas 2D context for rendering (read-only)
+    ['logo', 'rw', 'raw'],         // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data}
+    ['scale', 'rw', 'float'],      // Display area scale factor 0.0 - 1.0
+    ['viewport', 'rw', 'bool'],    // Use viewport clipping
+    ['width', 'ro', 'int'],        // Display area width
+    ['height', 'ro', 'int'],       // Display area height
+
+    ['render_mode', 'ro', 'str'],  // Canvas rendering mode (read-only)
+
+    ['prefer_js', 'rw', 'str'],    // Prefer Javascript over canvas methods
+    ['cursor_uri', 'rw', 'raw'],   // Can we render cursor using data URI
+
+    ['onFlush', 'rw', 'func'],     // onFlush(): A flush request has finished
+]);
+
+// Class Methods
+Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
+    if ((w === 0) || (h === 0)) {
+        target.style.cursor = 'none';
+        return;
+    }
+
+    var cur = []
+    var y, x;
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            var idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
+            var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
+            idx = ((w * y) + x) * 4;
+            cur.push(pixels[idx + 2]); // red
+            cur.push(pixels[idx + 1]); // green
+            cur.push(pixels[idx]);     // blue
+            cur.push(alpha);           // alpha
+        }
+    }
+
+    var canvas = document.createElement('canvas');
+    var ctx = canvas.getContext('2d');
+
+    canvas.width = w;
+    canvas.height = h;
+
+    var img;
+    if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
+        img = new ImageData(new Uint8ClampedArray(cur), w, h);
+    } else {
+        img = ctx.createImageData(w, h);
+        img.data.set(new Uint8ClampedArray(cur));
+    }
+    ctx.clearRect(0, 0, w, h);
+    ctx.putImageData(img, 0, 0);
+
+    var url = canvas.toDataURL();
+    target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
+};

+ 38 - 0
static/novnc/core/inflator.js

@@ -0,0 +1,38 @@
+import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
+import ZStream from "../vendor/pako/lib/zlib/zstream.js";
+
+Inflate.prototype = {
+    inflate: function (data, flush, expected) {
+        this.strm.input = data;
+        this.strm.avail_in = this.strm.input.length;
+        this.strm.next_in = 0;
+        this.strm.next_out = 0;
+
+        // resize our output buffer if it's too small
+        // (we could just use multiple chunks, but that would cause an extra
+        // allocation each time to flatten the chunks)
+        if (expected > this.chunkSize) {
+            this.chunkSize = expected;
+            this.strm.output = new Uint8Array(this.chunkSize);
+        }
+
+        this.strm.avail_out = this.chunkSize;
+
+        inflate(this.strm, flush);
+
+        return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
+    },
+
+    reset: function () {
+        inflateReset(this.strm);
+    }
+};
+
+export default function Inflate() {
+    this.strm = new ZStream();
+    this.chunkSize = 1024 * 10 * 10;
+    this.strm.output = new Uint8Array(this.chunkSize);
+    this.windowBits = 5;
+
+    inflateInit(this.strm, this.windowBits);
+};

+ 497 - 0
static/novnc/core/input/devices.js

@@ -0,0 +1,497 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*jslint browser: true, white: false */
+/*global window, Util */
+
+import * as Log from '../util/logging.js';
+import { isTouchDevice } from '../util/browsers.js';
+import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
+import { set_defaults, make_properties } from '../util/properties.js';
+import * as KeyboardUtil from "./util.js";
+import KeyTable from "./keysym.js";
+
+//
+// Keyboard event handler
+//
+
+function Keyboard(defaults) {
+    this._keyDownList = {};         // List of depressed keys
+                                    // (even if they are happy)
+    this._pendingKey = null;        // Key waiting for keypress
+
+    set_defaults(this, defaults, {
+        'target': document,
+        'focused': true
+    });
+
+    // keep these here so we can refer to them later
+    this._eventHandlers = {
+        'keyup': this._handleKeyUp.bind(this),
+        'keydown': this._handleKeyDown.bind(this),
+        'keypress': this._handleKeyPress.bind(this),
+        'blur': this._allKeysUp.bind(this)
+    };
+};
+
+function isMac() {
+    return navigator && !!(/mac/i).exec(navigator.platform);
+}
+function isWindows() {
+    return navigator && !!(/win/i).exec(navigator.platform);
+}
+
+Keyboard.prototype = {
+    // private methods
+
+    _sendKeyEvent: function (keysym, code, down) {
+        if (!this._onKeyEvent) {
+            return;
+        }
+
+        Log.Debug("onKeyEvent " + (down ? "down" : "up") +
+                  ", keysym: " + keysym, ", code: " + code);
+
+        // Windows sends CtrlLeft+AltRight when you press
+        // AltGraph, which tends to confuse the hell out of
+        // remote systems. Fake a release of these keys until
+        // there is a way to detect AltGraph properly.
+        var fakeAltGraph = false;
+        if (down && isWindows()) {
+            if ((code !== 'ControlLeft') &&
+                (code !== 'AltRight') &&
+                ('ControlLeft' in this._keyDownList) &&
+                ('AltRight' in this._keyDownList)) {
+                fakeAltGraph = true;
+                this._onKeyEvent(this._keyDownList['AltRight'],
+                                 'AltRight', false);
+                this._onKeyEvent(this._keyDownList['ControlLeft'],
+                                 'ControlLeft', false);
+            }
+        }
+
+        this._onKeyEvent(keysym, code, down);
+
+        if (fakeAltGraph) {
+            this._onKeyEvent(this._keyDownList['ControlLeft'],
+                             'ControlLeft', true);
+            this._onKeyEvent(this._keyDownList['AltRight'],
+                             'AltRight', true);
+        }
+    },
+
+    _getKeyCode: function (e) {
+        var code = KeyboardUtil.getKeycode(e);
+        if (code === 'Unidentified') {
+            // Unstable, but we don't have anything else to go on
+            // (don't use it for 'keypress' events thought since
+            // WebKit sets it to the same as charCode)
+            if (e.keyCode && (e.type !== 'keypress')) {
+                code = 'Platform' + e.keyCode;
+            }
+        }
+
+        return code;
+    },
+
+    _handleKeyDown: function (e) {
+        if (!this._focused) { return; }
+
+        var code = this._getKeyCode(e);
+        var keysym = KeyboardUtil.getKeysym(e);
+
+        // We cannot handle keys we cannot track, but we also need
+        // to deal with virtual keyboards which omit key info
+        if (code === 'Unidentified') {
+            if (keysym) {
+                // If it's a virtual keyboard then it should be
+                // sufficient to just send press and release right
+                // after each other
+                this._sendKeyEvent(keysym, 'Unidentified', true);
+                this._sendKeyEvent(keysym, 'Unidentified', false);
+            }
+
+            stopEvent(e);
+            return;
+        }
+
+        // Alt behaves more like AltGraph on macOS, so shuffle the
+        // keys around a bit to make things more sane for the remote
+        // server. This method is used by RealVNC and TigerVNC (and
+        // possibly others).
+        if (isMac()) {
+            switch (keysym) {
+            case KeyTable.XK_Super_L:
+                keysym = KeyTable.XK_Alt_L;
+                break;
+            case KeyTable.XK_Super_R:
+                keysym = KeyTable.XK_Super_L;
+                break;
+            case KeyTable.XK_Alt_L:
+                keysym = KeyTable.XK_Mode_switch;
+                break;
+            case KeyTable.XK_Alt_R:
+                keysym = KeyTable.XK_ISO_Level3_Shift;
+                break;
+            }
+        }
+
+        // Is this key already pressed? If so, then we must use the
+        // same keysym or we'll confuse the server
+        if (code in this._keyDownList) {
+            keysym = this._keyDownList[code];
+        }
+
+        // macOS doesn't send proper key events for modifiers, only
+        // state change events. That gets extra confusing for CapsLock
+        // which toggles on each press, but not on release. So pretend
+        // it was a quick press and release of the button.
+        if (isMac() && (code === 'CapsLock')) {
+            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
+            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
+            stopEvent(e);
+            return;
+        }
+
+        // If this is a legacy browser then we'll need to wait for
+        // a keypress event as well
+        if (!keysym) {
+            this._pendingKey = code;
+            return;
+        }
+
+        this._pendingKey = null;
+        stopEvent(e);
+
+        this._keyDownList[code] = keysym;
+
+        this._sendKeyEvent(keysym, code, true);
+    },
+
+    // Legacy event for browsers without code/key
+    _handleKeyPress: function (e) {
+        if (!this._focused) { return; }
+
+        stopEvent(e);
+
+        // Are we expecting a keypress?
+        if (this._pendingKey === null) {
+            return;
+        }
+
+        var code = this._getKeyCode(e);
+        var keysym = KeyboardUtil.getKeysym(e);
+
+        // The key we were waiting for?
+        if ((code !== 'Unidentified') && (code != this._pendingKey)) {
+            return;
+        }
+
+        code = this._pendingKey;
+        this._pendingKey = null;
+
+        if (!keysym) {
+            console.log('keypress with no keysym:', e);
+            return;
+        }
+
+        this._keyDownList[code] = keysym;
+
+        this._sendKeyEvent(keysym, code, true);
+    },
+
+    _handleKeyUp: function (e) {
+        if (!this._focused) { return; }
+
+        stopEvent(e);
+
+        var code = this._getKeyCode(e);
+
+        // See comment in _handleKeyDown()
+        if (isMac() && (code === 'CapsLock')) {
+            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
+            this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
+            return;
+        }
+
+        // Do we really think this key is down?
+        if (!(code in this._keyDownList)) {
+            return;
+        }
+
+        this._sendKeyEvent(this._keyDownList[code], code, false);
+
+        delete this._keyDownList[code];
+    },
+
+    _allKeysUp: function () {
+        Log.Debug(">> Keyboard.allKeysUp");
+        for (var code in this._keyDownList) {
+            this._sendKeyEvent(this._keyDownList[code], code, false);
+        };
+        this._keyDownList = {};
+        Log.Debug("<< Keyboard.allKeysUp");
+    },
+
+    // Public methods
+
+    grab: function () {
+        //Log.Debug(">> Keyboard.grab");
+        var c = this._target;
+
+        c.addEventListener('keydown', this._eventHandlers.keydown);
+        c.addEventListener('keyup', this._eventHandlers.keyup);
+        c.addEventListener('keypress', this._eventHandlers.keypress);
+
+        // Release (key up) if window loses focus
+        window.addEventListener('blur', this._eventHandlers.blur);
+
+        //Log.Debug("<< Keyboard.grab");
+    },
+
+    ungrab: function () {
+        //Log.Debug(">> Keyboard.ungrab");
+        var c = this._target;
+
+        c.removeEventListener('keydown', this._eventHandlers.keydown);
+        c.removeEventListener('keyup', this._eventHandlers.keyup);
+        c.removeEventListener('keypress', this._eventHandlers.keypress);
+        window.removeEventListener('blur', this._eventHandlers.blur);
+
+        // Release (key up) all keys that are in a down state
+        this._allKeysUp();
+
+        //Log.Debug(">> Keyboard.ungrab");
+    },
+};
+
+make_properties(Keyboard, [
+    ['target',     'wo', 'dom'],  // DOM element that captures keyboard input
+    ['focused',    'rw', 'bool'], // Capture and send key events
+
+    ['onKeyEvent', 'rw', 'func'] // Handler for key press/release
+]);
+
+function Mouse(defaults) {
+
+    this._doubleClickTimer = null;
+    this._lastTouchPos = null;
+
+    // Configuration attributes
+    set_defaults(this, defaults, {
+        'target': document,
+        'focused': true,
+        'touchButton': 1
+    });
+
+    this._eventHandlers = {
+        'mousedown': this._handleMouseDown.bind(this),
+        'mouseup': this._handleMouseUp.bind(this),
+        'mousemove': this._handleMouseMove.bind(this),
+        'mousewheel': this._handleMouseWheel.bind(this),
+        'mousedisable': this._handleMouseDisable.bind(this)
+    };
+};
+
+Mouse.prototype = {
+    // private methods
+
+    _resetDoubleClickTimer: function () {
+        this._doubleClickTimer = null;
+    },
+
+    _handleMouseButton: function (e, down) {
+        if (!this._focused) { return; }
+
+        var pos = this._getMousePosition(e);
+
+        var bmask;
+        if (e.touches || e.changedTouches) {
+            // Touch device
+
+            // When two touches occur within 500 ms of each other and are
+            // close enough together a double click is triggered.
+            if (down == 1) {
+                if (this._doubleClickTimer === null) {
+                    this._lastTouchPos = pos;
+                } else {
+                    clearTimeout(this._doubleClickTimer);
+
+                    // When the distance between the two touches is small enough
+                    // force the position of the latter touch to the position of
+                    // the first.
+
+                    var xs = this._lastTouchPos.x - pos.x;
+                    var ys = this._lastTouchPos.y - pos.y;
+                    var d = Math.sqrt((xs * xs) + (ys * ys));
+
+                    // The goal is to trigger on a certain physical width, the
+                    // devicePixelRatio brings us a bit closer but is not optimal.
+                    var threshold = 20 * (window.devicePixelRatio || 1);
+                    if (d < threshold) {
+                        pos = this._lastTouchPos;
+                    }
+                }
+                this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
+            }
+            bmask = this._touchButton;
+            // If bmask is set
+        } else if (e.which) {
+            /* everything except IE */
+            bmask = 1 << e.button;
+        } else {
+            /* IE including 9 */
+            bmask = (e.button & 0x1) +      // Left
+                    (e.button & 0x2) * 2 +  // Right
+                    (e.button & 0x4) / 2;   // Middle
+        }
+
+        if (this._onMouseButton) {
+            Log.Debug("onMouseButton " + (down ? "down" : "up") +
+                       ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
+            this._onMouseButton(pos.x, pos.y, down, bmask);
+        }
+        stopEvent(e);
+    },
+
+    _handleMouseDown: function (e) {
+        // Touch events have implicit capture
+        if (e.type === "mousedown") {
+            setCapture(this._target);
+        }
+
+        this._handleMouseButton(e, 1);
+    },
+
+    _handleMouseUp: function (e) {
+        this._handleMouseButton(e, 0);
+    },
+
+    _handleMouseWheel: function (e) {
+        if (!this._focused) { return; }
+
+        var pos = this._getMousePosition(e);
+
+        if (this._onMouseButton) {
+            if (e.deltaX < 0) {
+                this._onMouseButton(pos.x, pos.y, 1, 1 << 5);
+                this._onMouseButton(pos.x, pos.y, 0, 1 << 5);
+            } else if (e.deltaX > 0) {
+                this._onMouseButton(pos.x, pos.y, 1, 1 << 6);
+                this._onMouseButton(pos.x, pos.y, 0, 1 << 6);
+            }
+
+            if (e.deltaY < 0) {
+                this._onMouseButton(pos.x, pos.y, 1, 1 << 3);
+                this._onMouseButton(pos.x, pos.y, 0, 1 << 3);
+            } else if (e.deltaY > 0) {
+                this._onMouseButton(pos.x, pos.y, 1, 1 << 4);
+                this._onMouseButton(pos.x, pos.y, 0, 1 << 4);
+            }
+        }
+
+        stopEvent(e);
+    },
+
+    _handleMouseMove: function (e) {
+        if (! this._focused) { return; }
+
+        var pos = this._getMousePosition(e);
+        if (this._onMouseMove) {
+            this._onMouseMove(pos.x, pos.y);
+        }
+        stopEvent(e);
+    },
+
+    _handleMouseDisable: function (e) {
+        if (!this._focused) { return; }
+
+        /*
+         * Stop propagation if inside canvas area
+         * Note: This is only needed for the 'click' event as it fails
+         *       to fire properly for the target element so we have
+         *       to listen on the document element instead.
+         */
+        if (e.target == this._target) {
+            stopEvent(e);
+        }
+    },
+
+    // Return coordinates relative to target
+    _getMousePosition: function(e) {
+        e = getPointerEvent(e);
+        var bounds = this._target.getBoundingClientRect();
+        var x, y;
+        // Clip to target bounds
+        if (e.clientX < bounds.left) {
+            x = 0;
+        } else if (e.clientX >= bounds.right) {
+            x = bounds.width - 1;
+        } else {
+            x = e.clientX - bounds.left;
+        }
+        if (e.clientY < bounds.top) {
+            y = 0;
+        } else if (e.clientY >= bounds.bottom) {
+            y = bounds.height - 1;
+        } else {
+            y = e.clientY - bounds.top;
+        }
+        return {x:x, y:y};
+    },
+
+    // Public methods
+    grab: function () {
+        var c = this._target;
+
+        if (isTouchDevice) {
+            c.addEventListener('touchstart', this._eventHandlers.mousedown);
+            c.addEventListener('touchend', this._eventHandlers.mouseup);
+            c.addEventListener('touchmove', this._eventHandlers.mousemove);
+        }
+        c.addEventListener('mousedown', this._eventHandlers.mousedown);
+        c.addEventListener('mouseup', this._eventHandlers.mouseup);
+        c.addEventListener('mousemove', this._eventHandlers.mousemove);
+        c.addEventListener('wheel', this._eventHandlers.mousewheel);
+
+        /* Prevent middle-click pasting (see above for why we bind to document) */
+        document.addEventListener('click', this._eventHandlers.mousedisable);
+
+        /* preventDefault() on mousedown doesn't stop this event for some
+           reason so we have to explicitly block it */
+        c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
+    },
+
+    ungrab: function () {
+        var c = this._target;
+
+        if (isTouchDevice) {
+            c.removeEventListener('touchstart', this._eventHandlers.mousedown);
+            c.removeEventListener('touchend', this._eventHandlers.mouseup);
+            c.removeEventListener('touchmove', this._eventHandlers.mousemove);
+        }
+        c.removeEventListener('mousedown', this._eventHandlers.mousedown);
+        c.removeEventListener('mouseup', this._eventHandlers.mouseup);
+        c.removeEventListener('mousemove', this._eventHandlers.mousemove);
+        c.removeEventListener('wheel', this._eventHandlers.mousewheel);
+
+        document.removeEventListener('click', this._eventHandlers.mousedisable);
+
+        c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
+    }
+};
+
+make_properties(Mouse, [
+    ['target',         'ro', 'dom'],   // DOM element that captures mouse input
+    ['focused',        'rw', 'bool'],  // Capture and send mouse clicks/movement
+
+    ['onMouseButton',  'rw', 'func'],  // Handler for mouse button click/release
+    ['onMouseMove',    'rw', 'func'],  // Handler for mouse movement
+    ['touchButton',    'rw', 'int']    // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
+]);
+
+export { Keyboard, Mouse };

+ 310 - 0
static/novnc/core/input/domkeytable.js

@@ -0,0 +1,310 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import KeyTable from "./keysym.js";
+
+/*
+ * Mapping between HTML key values and VNC/X11 keysyms for "special"
+ * keys that cannot be handled via their Unicode codepoint.
+ *
+ * See https://www.w3.org/TR/uievents-key/ for possible values.
+ */
+
+var DOMKeyTable = {};
+
+function addStandard(key, standard)
+{
+    if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
+    if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
+    DOMKeyTable[key] = [standard, standard, standard, standard];
+}
+
+function addLeftRight(key, left, right)
+{
+    if (left === undefined) throw "Undefined keysym for key \"" + key + "\"";
+    if (right === undefined) throw "Undefined keysym for key \"" + key + "\"";
+    if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
+    DOMKeyTable[key] = [left, left, right, left];
+}
+
+function addNumpad(key, standard, numpad)
+{
+    if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
+    if (numpad === undefined) throw "Undefined keysym for key \"" + key + "\"";
+    if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
+    DOMKeyTable[key] = [standard, standard, standard, numpad];
+}
+
+// 2.2. Modifier Keys
+
+addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
+addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
+addStandard("CapsLock", KeyTable.XK_Caps_Lock);
+addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
+// - Fn
+// - FnLock
+addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+addStandard("NumLock", KeyTable.XK_Num_Lock);
+addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
+addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
+addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+// - Symbol
+// - SymbolLock
+
+// 2.3. Whitespace Keys
+
+addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
+addStandard("Tab", KeyTable.XK_Tab);
+addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
+
+// 2.4. Navigation Keys
+
+addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
+addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
+addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
+addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
+addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
+addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
+addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
+addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
+
+// 2.5. Editing Keys
+
+addStandard("Backspace", KeyTable.XK_BackSpace);
+addStandard("Clear", KeyTable.XK_Clear);
+addStandard("Copy", KeyTable.XF86XK_Copy);
+// - CrSel
+addStandard("Cut", KeyTable.XF86XK_Cut);
+addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete);
+// - EraseEof
+// - ExSel
+addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert);
+addStandard("Paste", KeyTable.XF86XK_Paste);
+addStandard("Redo", KeyTable.XK_Redo);
+addStandard("Undo", KeyTable.XK_Undo);
+
+// 2.6. UI Keys
+
+// - Accept
+// - Again (could just be XK_Redo)
+// - Attn
+addStandard("Cancel", KeyTable.XK_Cancel);
+addStandard("ContextMenu", KeyTable.XK_Menu);
+addStandard("Escape", KeyTable.XK_Escape);
+addStandard("Execute", KeyTable.XK_Execute);
+addStandard("Find", KeyTable.XK_Find);
+addStandard("Help", KeyTable.XK_Help);
+addStandard("Pause", KeyTable.XK_Pause);
+// - Play
+// - Props
+addStandard("Select", KeyTable.XK_Select);
+addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
+addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
+
+// 2.7. Device Keys
+
+addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
+addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
+addStandard("Eject", KeyTable.XF86XK_Eject);
+addStandard("LogOff", KeyTable.XF86XK_LogOff);
+addStandard("Power", KeyTable.XF86XK_PowerOff);
+addStandard("PowerOff", KeyTable.XF86XK_PowerDown);
+addStandard("PrintScreen", KeyTable.XK_Print);
+addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
+addStandard("Standby", KeyTable.XF86XK_Standby);
+addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
+
+// 2.8. IME and Composition Keys
+
+addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
+addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
+addStandard("CodeInput", KeyTable.XK_Codeinput);
+addStandard("Compose", KeyTable.XK_Multi_key);
+addStandard("Convert", KeyTable.XK_Henkan);
+// - Dead
+// - FinalMode
+addStandard("GroupFirst", KeyTable.XK_ISO_First_Group);
+addStandard("GroupLast", KeyTable.XK_ISO_Last_Group);
+addStandard("GroupNext", KeyTable.XK_ISO_Next_Group);
+addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group);
+// - ModeChange (XK_Mode_switch is often used for AltGr)
+// - NextCandidate
+addStandard("NonConvert", KeyTable.XK_Muhenkan);
+addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
+// - Process
+addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
+addStandard("HangulMode", KeyTable.XK_Hangul);
+addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
+addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja);
+addStandard("Eisu", KeyTable.XK_Eisu_toggle);
+addStandard("Hankaku", KeyTable.XK_Hankaku);
+addStandard("Hiragana", KeyTable.XK_Hiragana);
+addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana);
+addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock
+addStandard("KanjiMode", KeyTable.XK_Kanji);
+addStandard("Katakana", KeyTable.XK_Katakana);
+addStandard("Romaji", KeyTable.XK_Romaji);
+addStandard("Zenkaku", KeyTable.XK_Zenkaku);
+addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku);
+
+// 2.9. General-Purpose Function Keys
+
+addStandard("F1", KeyTable.XK_F1);
+addStandard("F2", KeyTable.XK_F2);
+addStandard("F3", KeyTable.XK_F3);
+addStandard("F4", KeyTable.XK_F4);
+addStandard("F5", KeyTable.XK_F5);
+addStandard("F6", KeyTable.XK_F6);
+addStandard("F7", KeyTable.XK_F7);
+addStandard("F8", KeyTable.XK_F8);
+addStandard("F9", KeyTable.XK_F9);
+addStandard("F10", KeyTable.XK_F10);
+addStandard("F11", KeyTable.XK_F11);
+addStandard("F12", KeyTable.XK_F12);
+addStandard("F13", KeyTable.XK_F13);
+addStandard("F14", KeyTable.XK_F14);
+addStandard("F15", KeyTable.XK_F15);
+addStandard("F16", KeyTable.XK_F16);
+addStandard("F17", KeyTable.XK_F17);
+addStandard("F18", KeyTable.XK_F18);
+addStandard("F19", KeyTable.XK_F19);
+addStandard("F20", KeyTable.XK_F20);
+addStandard("F21", KeyTable.XK_F21);
+addStandard("F22", KeyTable.XK_F22);
+addStandard("F23", KeyTable.XK_F23);
+addStandard("F24", KeyTable.XK_F24);
+addStandard("F25", KeyTable.XK_F25);
+addStandard("F26", KeyTable.XK_F26);
+addStandard("F27", KeyTable.XK_F27);
+addStandard("F28", KeyTable.XK_F28);
+addStandard("F29", KeyTable.XK_F29);
+addStandard("F30", KeyTable.XK_F30);
+addStandard("F31", KeyTable.XK_F31);
+addStandard("F32", KeyTable.XK_F32);
+addStandard("F33", KeyTable.XK_F33);
+addStandard("F34", KeyTable.XK_F34);
+addStandard("F35", KeyTable.XK_F35);
+// - Soft1...
+
+// 2.10. Multimedia Keys
+
+// - ChannelDown
+// - ChannelUp
+addStandard("Close", KeyTable.XF86XK_Close);
+addStandard("MailForward", KeyTable.XF86XK_MailForward);
+addStandard("MailReply", KeyTable.XF86XK_Reply);
+addStandard("MainSend", KeyTable.XF86XK_Send);
+addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
+addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
+addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
+addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
+addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
+addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
+addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext);
+addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev);
+addStandard("New", KeyTable.XF86XK_New);
+addStandard("Open", KeyTable.XF86XK_Open);
+addStandard("Print", KeyTable.XK_Print);
+addStandard("Save", KeyTable.XF86XK_Save);
+addStandard("SpellCheck", KeyTable.XF86XK_Spell);
+
+// 2.11. Multimedia Numpad Keys
+
+// - Key11
+// - Key12
+
+// 2.12. Audio Keys
+
+// - AudioBalanceLeft
+// - AudioBalanceRight
+// - AudioBassDown
+// - AudioBassBoostDown
+// - AudioBassBoostToggle
+// - AudioBassBoostUp
+// - AudioBassUp
+// - AudioFaderFront
+// - AudioFaderRear
+// - AudioSurroundModeNext
+// - AudioTrebleDown
+// - AudioTrebleUp
+addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume);
+addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume);
+addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
+// - MicrophoneToggle
+// - MicrophoneVolumeDown
+// - MicrophoneVolumeUp
+addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
+
+// 2.13. Speech Keys
+
+// - SpeechCorrectionList
+// - SpeechInputToggle
+
+// 2.14. Application Keys
+
+addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
+addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
+addStandard("LaunchMail", KeyTable.XF86XK_Mail);
+addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
+addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
+addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
+addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
+addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
+addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
+addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
+addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
+addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
+
+// 2.15. Browser Keys
+
+addStandard("BrowserBack", KeyTable.XF86XK_Back);
+addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
+addStandard("BrowserForward", KeyTable.XF86XK_Forward);
+addStandard("BrowserHome", KeyTable.XF86XK_HomePage);
+addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
+addStandard("BrowserSearch", KeyTable.XF86XK_Search);
+addStandard("BrowserStop", KeyTable.XF86XK_Stop);
+
+// 2.16. Mobile Phone Keys
+
+// - A whole bunch...
+
+// 2.17. TV Keys
+
+// - A whole bunch...
+
+// 2.18. Media Controller Keys
+
+// - A whole bunch...
+addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
+addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack);
+addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay);
+addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen);
+addStandard("Subtitle", KeyTable.XF86XK_Subtitle);
+addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode);
+
+// Extra: Numpad
+
+addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal);
+addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add);
+addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract);
+addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply);
+addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide);
+addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal);
+addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator);
+addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0);
+addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1);
+addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2);
+addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3);
+addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4);
+addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5);
+addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6);
+addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7);
+addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8);
+addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9);
+
+export default DOMKeyTable;

+ 127 - 0
static/novnc/core/input/fixedkeys.js

@@ -0,0 +1,127 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*
+ * Fallback mapping between HTML key codes (physical keys) and
+ * HTML key values. This only works for keys that don't vary
+ * between layouts. We also omit those who manage fine by mapping the
+ * Unicode representation.
+ *
+ * See https://www.w3.org/TR/uievents-code/ for possible codes.
+ * See https://www.w3.org/TR/uievents-key/ for possible values.
+ */
+
+export default {
+
+// 3.1.1.1. Writing System Keys
+
+    'Backspace':        'Backspace',
+
+// 3.1.1.2. Functional Keys
+
+    'AltLeft':          'Alt',
+    'AltRight':         'Alt', // This could also be 'AltGraph'
+    'CapsLock':         'CapsLock',
+    'ContextMenu':      'ContextMenu',
+    'ControlLeft':      'Control',
+    'ControlRight':     'Control',
+    'Enter':            'Enter',
+    'MetaLeft':         'Meta',
+    'MetaRight':        'Meta',
+    'ShiftLeft':        'Shift',
+    'ShiftRight':       'Shift',
+    'Tab':              'Tab',
+    // FIXME: Japanese/Korean keys
+
+// 3.1.2. Control Pad Section
+
+    'Delete':           'Delete',
+    'End':              'End',
+    'Help':             'Help',
+    'Home':             'Home',
+    'Insert':           'Insert',
+    'PageDown':         'PageDown',
+    'PageUp':           'PageUp',
+
+// 3.1.3. Arrow Pad Section
+
+    'ArrowDown':        'ArrowDown',
+    'ArrowLeft':        'ArrowLeft',
+    'ArrowRight':       'ArrowRight',
+    'ArrowUp':          'ArrowUp',
+
+// 3.1.4. Numpad Section
+
+    'NumLock':          'NumLock',
+    'NumpadBackspace':  'Backspace',
+    'NumpadClear':      'Clear',
+
+// 3.1.5. Function Section
+
+    'Escape':           'Escape',
+    'F1':               'F1',
+    'F2':               'F2',
+    'F3':               'F3',
+    'F4':               'F4',
+    'F5':               'F5',
+    'F6':               'F6',
+    'F7':               'F7',
+    'F8':               'F8',
+    'F9':               'F9',
+    'F10':              'F10',
+    'F11':              'F11',
+    'F12':              'F12',
+    'F13':              'F13',
+    'F14':              'F14',
+    'F15':              'F15',
+    'F16':              'F16',
+    'F17':              'F17',
+    'F18':              'F18',
+    'F19':              'F19',
+    'F20':              'F20',
+    'F21':              'F21',
+    'F22':              'F22',
+    'F23':              'F23',
+    'F24':              'F24',
+    'F25':              'F25',
+    'F26':              'F26',
+    'F27':              'F27',
+    'F28':              'F28',
+    'F29':              'F29',
+    'F30':              'F30',
+    'F31':              'F31',
+    'F32':              'F32',
+    'F33':              'F33',
+    'F34':              'F34',
+    'F35':              'F35',
+    'PrintScreen':      'PrintScreen',
+    'ScrollLock':       'ScrollLock',
+    'Pause':            'Pause',
+
+// 3.1.6. Media Keys
+
+    'BrowserBack':      'BrowserBack',
+    'BrowserFavorites': 'BrowserFavorites',
+    'BrowserForward':   'BrowserForward',
+    'BrowserHome':      'BrowserHome',
+    'BrowserRefresh':   'BrowserRefresh',
+    'BrowserSearch':    'BrowserSearch',
+    'BrowserStop':      'BrowserStop',
+    'Eject':            'Eject',
+    'LaunchApp1':       'LaunchMyComputer',
+    'LaunchApp2':       'LaunchCalendar',
+    'LaunchMail':       'LaunchMail',
+    'MediaPlayPause':   'MediaPlay',
+    'MediaStop':        'MediaStop',
+    'MediaTrackNext':   'MediaTrackNext',
+    'MediaTrackPrevious': 'MediaTrackPrevious',
+    'Power':            'Power',
+    'Sleep':            'Sleep',
+    'AudioVolumeDown':  'AudioVolumeDown',
+    'AudioVolumeMute':  'AudioVolumeMute',
+    'AudioVolumeUp':    'AudioVolumeUp',
+    'WakeUp':           'WakeUp',
+};

+ 614 - 0
static/novnc/core/input/keysym.js

@@ -0,0 +1,614 @@
+export default {
+    XK_VoidSymbol:                  0xffffff, /* Void symbol */
+
+    XK_BackSpace:                   0xff08, /* Back space, back char */
+    XK_Tab:                         0xff09,
+    XK_Linefeed:                    0xff0a, /* Linefeed, LF */
+    XK_Clear:                       0xff0b,
+    XK_Return:                      0xff0d, /* Return, enter */
+    XK_Pause:                       0xff13, /* Pause, hold */
+    XK_Scroll_Lock:                 0xff14,
+    XK_Sys_Req:                     0xff15,
+    XK_Escape:                      0xff1b,
+    XK_Delete:                      0xffff, /* Delete, rubout */
+
+    /* International & multi-key character composition */
+
+    XK_Multi_key:                   0xff20, /* Multi-key character compose */
+    XK_Codeinput:                   0xff37,
+    XK_SingleCandidate:             0xff3c,
+    XK_MultipleCandidate:           0xff3d,
+    XK_PreviousCandidate:           0xff3e,
+
+    /* Japanese keyboard support */
+
+    XK_Kanji:                       0xff21, /* Kanji, Kanji convert */
+    XK_Muhenkan:                    0xff22, /* Cancel Conversion */
+    XK_Henkan_Mode:                 0xff23, /* Start/Stop Conversion */
+    XK_Henkan:                      0xff23, /* Alias for Henkan_Mode */
+    XK_Romaji:                      0xff24, /* to Romaji */
+    XK_Hiragana:                    0xff25, /* to Hiragana */
+    XK_Katakana:                    0xff26, /* to Katakana */
+    XK_Hiragana_Katakana:           0xff27, /* Hiragana/Katakana toggle */
+    XK_Zenkaku:                     0xff28, /* to Zenkaku */
+    XK_Hankaku:                     0xff29, /* to Hankaku */
+    XK_Zenkaku_Hankaku:             0xff2a, /* Zenkaku/Hankaku toggle */
+    XK_Touroku:                     0xff2b, /* Add to Dictionary */
+    XK_Massyo:                      0xff2c, /* Delete from Dictionary */
+    XK_Kana_Lock:                   0xff2d, /* Kana Lock */
+    XK_Kana_Shift:                  0xff2e, /* Kana Shift */
+    XK_Eisu_Shift:                  0xff2f, /* Alphanumeric Shift */
+    XK_Eisu_toggle:                 0xff30, /* Alphanumeric toggle */
+    XK_Kanji_Bangou:                0xff37, /* Codeinput */
+    XK_Zen_Koho:                    0xff3d, /* Multiple/All Candidate(s) */
+    XK_Mae_Koho:                    0xff3e, /* Previous Candidate */
+
+    /* Cursor control & motion */
+
+    XK_Home:                        0xff50,
+    XK_Left:                        0xff51, /* Move left, left arrow */
+    XK_Up:                          0xff52, /* Move up, up arrow */
+    XK_Right:                       0xff53, /* Move right, right arrow */
+    XK_Down:                        0xff54, /* Move down, down arrow */
+    XK_Prior:                       0xff55, /* Prior, previous */
+    XK_Page_Up:                     0xff55,
+    XK_Next:                        0xff56, /* Next */
+    XK_Page_Down:                   0xff56,
+    XK_End:                         0xff57, /* EOL */
+    XK_Begin:                       0xff58, /* BOL */
+
+
+    /* Misc functions */
+
+    XK_Select:                      0xff60, /* Select, mark */
+    XK_Print:                       0xff61,
+    XK_Execute:                     0xff62, /* Execute, run, do */
+    XK_Insert:                      0xff63, /* Insert, insert here */
+    XK_Undo:                        0xff65,
+    XK_Redo:                        0xff66, /* Redo, again */
+    XK_Menu:                        0xff67,
+    XK_Find:                        0xff68, /* Find, search */
+    XK_Cancel:                      0xff69, /* Cancel, stop, abort, exit */
+    XK_Help:                        0xff6a, /* Help */
+    XK_Break:                       0xff6b,
+    XK_Mode_switch:                 0xff7e, /* Character set switch */
+    XK_script_switch:               0xff7e, /* Alias for mode_switch */
+    XK_Num_Lock:                    0xff7f,
+
+    /* Keypad functions, keypad numbers cleverly chosen to map to ASCII */
+
+    XK_KP_Space:                    0xff80, /* Space */
+    XK_KP_Tab:                      0xff89,
+    XK_KP_Enter:                    0xff8d, /* Enter */
+    XK_KP_F1:                       0xff91, /* PF1, KP_A, ... */
+    XK_KP_F2:                       0xff92,
+    XK_KP_F3:                       0xff93,
+    XK_KP_F4:                       0xff94,
+    XK_KP_Home:                     0xff95,
+    XK_KP_Left:                     0xff96,
+    XK_KP_Up:                       0xff97,
+    XK_KP_Right:                    0xff98,
+    XK_KP_Down:                     0xff99,
+    XK_KP_Prior:                    0xff9a,
+    XK_KP_Page_Up:                  0xff9a,
+    XK_KP_Next:                     0xff9b,
+    XK_KP_Page_Down:                0xff9b,
+    XK_KP_End:                      0xff9c,
+    XK_KP_Begin:                    0xff9d,
+    XK_KP_Insert:                   0xff9e,
+    XK_KP_Delete:                   0xff9f,
+    XK_KP_Equal:                    0xffbd, /* Equals */
+    XK_KP_Multiply:                 0xffaa,
+    XK_KP_Add:                      0xffab,
+    XK_KP_Separator:                0xffac, /* Separator, often comma */
+    XK_KP_Subtract:                 0xffad,
+    XK_KP_Decimal:                  0xffae,
+    XK_KP_Divide:                   0xffaf,
+
+    XK_KP_0:                        0xffb0,
+    XK_KP_1:                        0xffb1,
+    XK_KP_2:                        0xffb2,
+    XK_KP_3:                        0xffb3,
+    XK_KP_4:                        0xffb4,
+    XK_KP_5:                        0xffb5,
+    XK_KP_6:                        0xffb6,
+    XK_KP_7:                        0xffb7,
+    XK_KP_8:                        0xffb8,
+    XK_KP_9:                        0xffb9,
+
+    /*
+     * Auxiliary functions; note the duplicate definitions for left and right
+     * function keys;  Sun keyboards and a few other manufacturers have such
+     * function key groups on the left and/or right sides of the keyboard.
+     * We've not found a keyboard with more than 35 function keys total.
+     */
+
+    XK_F1:                          0xffbe,
+    XK_F2:                          0xffbf,
+    XK_F3:                          0xffc0,
+    XK_F4:                          0xffc1,
+    XK_F5:                          0xffc2,
+    XK_F6:                          0xffc3,
+    XK_F7:                          0xffc4,
+    XK_F8:                          0xffc5,
+    XK_F9:                          0xffc6,
+    XK_F10:                         0xffc7,
+    XK_F11:                         0xffc8,
+    XK_L1:                          0xffc8,
+    XK_F12:                         0xffc9,
+    XK_L2:                          0xffc9,
+    XK_F13:                         0xffca,
+    XK_L3:                          0xffca,
+    XK_F14:                         0xffcb,
+    XK_L4:                          0xffcb,
+    XK_F15:                         0xffcc,
+    XK_L5:                          0xffcc,
+    XK_F16:                         0xffcd,
+    XK_L6:                          0xffcd,
+    XK_F17:                         0xffce,
+    XK_L7:                          0xffce,
+    XK_F18:                         0xffcf,
+    XK_L8:                          0xffcf,
+    XK_F19:                         0xffd0,
+    XK_L9:                          0xffd0,
+    XK_F20:                         0xffd1,
+    XK_L10:                         0xffd1,
+    XK_F21:                         0xffd2,
+    XK_R1:                          0xffd2,
+    XK_F22:                         0xffd3,
+    XK_R2:                          0xffd3,
+    XK_F23:                         0xffd4,
+    XK_R3:                          0xffd4,
+    XK_F24:                         0xffd5,
+    XK_R4:                          0xffd5,
+    XK_F25:                         0xffd6,
+    XK_R5:                          0xffd6,
+    XK_F26:                         0xffd7,
+    XK_R6:                          0xffd7,
+    XK_F27:                         0xffd8,
+    XK_R7:                          0xffd8,
+    XK_F28:                         0xffd9,
+    XK_R8:                          0xffd9,
+    XK_F29:                         0xffda,
+    XK_R9:                          0xffda,
+    XK_F30:                         0xffdb,
+    XK_R10:                         0xffdb,
+    XK_F31:                         0xffdc,
+    XK_R11:                         0xffdc,
+    XK_F32:                         0xffdd,
+    XK_R12:                         0xffdd,
+    XK_F33:                         0xffde,
+    XK_R13:                         0xffde,
+    XK_F34:                         0xffdf,
+    XK_R14:                         0xffdf,
+    XK_F35:                         0xffe0,
+    XK_R15:                         0xffe0,
+
+    /* Modifiers */
+
+    XK_Shift_L:                     0xffe1, /* Left shift */
+    XK_Shift_R:                     0xffe2, /* Right shift */
+    XK_Control_L:                   0xffe3, /* Left control */
+    XK_Control_R:                   0xffe4, /* Right control */
+    XK_Caps_Lock:                   0xffe5, /* Caps lock */
+    XK_Shift_Lock:                  0xffe6, /* Shift lock */
+
+    XK_Meta_L:                      0xffe7, /* Left meta */
+    XK_Meta_R:                      0xffe8, /* Right meta */
+    XK_Alt_L:                       0xffe9, /* Left alt */
+    XK_Alt_R:                       0xffea, /* Right alt */
+    XK_Super_L:                     0xffeb, /* Left super */
+    XK_Super_R:                     0xffec, /* Right super */
+    XK_Hyper_L:                     0xffed, /* Left hyper */
+    XK_Hyper_R:                     0xffee, /* Right hyper */
+
+    /*
+     * Keyboard (XKB) Extension function and modifier keys
+     * (from Appendix C of "The X Keyboard Extension: Protocol Specification")
+     * Byte 3 = 0xfe
+     */
+
+    XK_ISO_Level3_Shift:            0xfe03, /* AltGr */
+    XK_ISO_Next_Group:              0xfe08,
+    XK_ISO_Prev_Group:              0xfe0a,
+    XK_ISO_First_Group:             0xfe0c,
+    XK_ISO_Last_Group:              0xfe0e,
+
+    /*
+     * Latin 1
+     * (ISO/IEC 8859-1: Unicode U+0020..U+00FF)
+     * Byte 3: 0
+     */
+
+    XK_space:                       0x0020, /* U+0020 SPACE */
+    XK_exclam:                      0x0021, /* U+0021 EXCLAMATION MARK */
+    XK_quotedbl:                    0x0022, /* U+0022 QUOTATION MARK */
+    XK_numbersign:                  0x0023, /* U+0023 NUMBER SIGN */
+    XK_dollar:                      0x0024, /* U+0024 DOLLAR SIGN */
+    XK_percent:                     0x0025, /* U+0025 PERCENT SIGN */
+    XK_ampersand:                   0x0026, /* U+0026 AMPERSAND */
+    XK_apostrophe:                  0x0027, /* U+0027 APOSTROPHE */
+    XK_quoteright:                  0x0027, /* deprecated */
+    XK_parenleft:                   0x0028, /* U+0028 LEFT PARENTHESIS */
+    XK_parenright:                  0x0029, /* U+0029 RIGHT PARENTHESIS */
+    XK_asterisk:                    0x002a, /* U+002A ASTERISK */
+    XK_plus:                        0x002b, /* U+002B PLUS SIGN */
+    XK_comma:                       0x002c, /* U+002C COMMA */
+    XK_minus:                       0x002d, /* U+002D HYPHEN-MINUS */
+    XK_period:                      0x002e, /* U+002E FULL STOP */
+    XK_slash:                       0x002f, /* U+002F SOLIDUS */
+    XK_0:                           0x0030, /* U+0030 DIGIT ZERO */
+    XK_1:                           0x0031, /* U+0031 DIGIT ONE */
+    XK_2:                           0x0032, /* U+0032 DIGIT TWO */
+    XK_3:                           0x0033, /* U+0033 DIGIT THREE */
+    XK_4:                           0x0034, /* U+0034 DIGIT FOUR */
+    XK_5:                           0x0035, /* U+0035 DIGIT FIVE */
+    XK_6:                           0x0036, /* U+0036 DIGIT SIX */
+    XK_7:                           0x0037, /* U+0037 DIGIT SEVEN */
+    XK_8:                           0x0038, /* U+0038 DIGIT EIGHT */
+    XK_9:                           0x0039, /* U+0039 DIGIT NINE */
+    XK_colon:                       0x003a, /* U+003A COLON */
+    XK_semicolon:                   0x003b, /* U+003B SEMICOLON */
+    XK_less:                        0x003c, /* U+003C LESS-THAN SIGN */
+    XK_equal:                       0x003d, /* U+003D EQUALS SIGN */
+    XK_greater:                     0x003e, /* U+003E GREATER-THAN SIGN */
+    XK_question:                    0x003f, /* U+003F QUESTION MARK */
+    XK_at:                          0x0040, /* U+0040 COMMERCIAL AT */
+    XK_A:                           0x0041, /* U+0041 LATIN CAPITAL LETTER A */
+    XK_B:                           0x0042, /* U+0042 LATIN CAPITAL LETTER B */
+    XK_C:                           0x0043, /* U+0043 LATIN CAPITAL LETTER C */
+    XK_D:                           0x0044, /* U+0044 LATIN CAPITAL LETTER D */
+    XK_E:                           0x0045, /* U+0045 LATIN CAPITAL LETTER E */
+    XK_F:                           0x0046, /* U+0046 LATIN CAPITAL LETTER F */
+    XK_G:                           0x0047, /* U+0047 LATIN CAPITAL LETTER G */
+    XK_H:                           0x0048, /* U+0048 LATIN CAPITAL LETTER H */
+    XK_I:                           0x0049, /* U+0049 LATIN CAPITAL LETTER I */
+    XK_J:                           0x004a, /* U+004A LATIN CAPITAL LETTER J */
+    XK_K:                           0x004b, /* U+004B LATIN CAPITAL LETTER K */
+    XK_L:                           0x004c, /* U+004C LATIN CAPITAL LETTER L */
+    XK_M:                           0x004d, /* U+004D LATIN CAPITAL LETTER M */
+    XK_N:                           0x004e, /* U+004E LATIN CAPITAL LETTER N */
+    XK_O:                           0x004f, /* U+004F LATIN CAPITAL LETTER O */
+    XK_P:                           0x0050, /* U+0050 LATIN CAPITAL LETTER P */
+    XK_Q:                           0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
+    XK_R:                           0x0052, /* U+0052 LATIN CAPITAL LETTER R */
+    XK_S:                           0x0053, /* U+0053 LATIN CAPITAL LETTER S */
+    XK_T:                           0x0054, /* U+0054 LATIN CAPITAL LETTER T */
+    XK_U:                           0x0055, /* U+0055 LATIN CAPITAL LETTER U */
+    XK_V:                           0x0056, /* U+0056 LATIN CAPITAL LETTER V */
+    XK_W:                           0x0057, /* U+0057 LATIN CAPITAL LETTER W */
+    XK_X:                           0x0058, /* U+0058 LATIN CAPITAL LETTER X */
+    XK_Y:                           0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
+    XK_Z:                           0x005a, /* U+005A LATIN CAPITAL LETTER Z */
+    XK_bracketleft:                 0x005b, /* U+005B LEFT SQUARE BRACKET */
+    XK_backslash:                   0x005c, /* U+005C REVERSE SOLIDUS */
+    XK_bracketright:                0x005d, /* U+005D RIGHT SQUARE BRACKET */
+    XK_asciicircum:                 0x005e, /* U+005E CIRCUMFLEX ACCENT */
+    XK_underscore:                  0x005f, /* U+005F LOW LINE */
+    XK_grave:                       0x0060, /* U+0060 GRAVE ACCENT */
+    XK_quoteleft:                   0x0060, /* deprecated */
+    XK_a:                           0x0061, /* U+0061 LATIN SMALL LETTER A */
+    XK_b:                           0x0062, /* U+0062 LATIN SMALL LETTER B */
+    XK_c:                           0x0063, /* U+0063 LATIN SMALL LETTER C */
+    XK_d:                           0x0064, /* U+0064 LATIN SMALL LETTER D */
+    XK_e:                           0x0065, /* U+0065 LATIN SMALL LETTER E */
+    XK_f:                           0x0066, /* U+0066 LATIN SMALL LETTER F */
+    XK_g:                           0x0067, /* U+0067 LATIN SMALL LETTER G */
+    XK_h:                           0x0068, /* U+0068 LATIN SMALL LETTER H */
+    XK_i:                           0x0069, /* U+0069 LATIN SMALL LETTER I */
+    XK_j:                           0x006a, /* U+006A LATIN SMALL LETTER J */
+    XK_k:                           0x006b, /* U+006B LATIN SMALL LETTER K */
+    XK_l:                           0x006c, /* U+006C LATIN SMALL LETTER L */
+    XK_m:                           0x006d, /* U+006D LATIN SMALL LETTER M */
+    XK_n:                           0x006e, /* U+006E LATIN SMALL LETTER N */
+    XK_o:                           0x006f, /* U+006F LATIN SMALL LETTER O */
+    XK_p:                           0x0070, /* U+0070 LATIN SMALL LETTER P */
+    XK_q:                           0x0071, /* U+0071 LATIN SMALL LETTER Q */
+    XK_r:                           0x0072, /* U+0072 LATIN SMALL LETTER R */
+    XK_s:                           0x0073, /* U+0073 LATIN SMALL LETTER S */
+    XK_t:                           0x0074, /* U+0074 LATIN SMALL LETTER T */
+    XK_u:                           0x0075, /* U+0075 LATIN SMALL LETTER U */
+    XK_v:                           0x0076, /* U+0076 LATIN SMALL LETTER V */
+    XK_w:                           0x0077, /* U+0077 LATIN SMALL LETTER W */
+    XK_x:                           0x0078, /* U+0078 LATIN SMALL LETTER X */
+    XK_y:                           0x0079, /* U+0079 LATIN SMALL LETTER Y */
+    XK_z:                           0x007a, /* U+007A LATIN SMALL LETTER Z */
+    XK_braceleft:                   0x007b, /* U+007B LEFT CURLY BRACKET */
+    XK_bar:                         0x007c, /* U+007C VERTICAL LINE */
+    XK_braceright:                  0x007d, /* U+007D RIGHT CURLY BRACKET */
+    XK_asciitilde:                  0x007e, /* U+007E TILDE */
+
+    XK_nobreakspace:                0x00a0, /* U+00A0 NO-BREAK SPACE */
+    XK_exclamdown:                  0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */
+    XK_cent:                        0x00a2, /* U+00A2 CENT SIGN */
+    XK_sterling:                    0x00a3, /* U+00A3 POUND SIGN */
+    XK_currency:                    0x00a4, /* U+00A4 CURRENCY SIGN */
+    XK_yen:                         0x00a5, /* U+00A5 YEN SIGN */
+    XK_brokenbar:                   0x00a6, /* U+00A6 BROKEN BAR */
+    XK_section:                     0x00a7, /* U+00A7 SECTION SIGN */
+    XK_diaeresis:                   0x00a8, /* U+00A8 DIAERESIS */
+    XK_copyright:                   0x00a9, /* U+00A9 COPYRIGHT SIGN */
+    XK_ordfeminine:                 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */
+    XK_guillemotleft:               0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
+    XK_notsign:                     0x00ac, /* U+00AC NOT SIGN */
+    XK_hyphen:                      0x00ad, /* U+00AD SOFT HYPHEN */
+    XK_registered:                  0x00ae, /* U+00AE REGISTERED SIGN */
+    XK_macron:                      0x00af, /* U+00AF MACRON */
+    XK_degree:                      0x00b0, /* U+00B0 DEGREE SIGN */
+    XK_plusminus:                   0x00b1, /* U+00B1 PLUS-MINUS SIGN */
+    XK_twosuperior:                 0x00b2, /* U+00B2 SUPERSCRIPT TWO */
+    XK_threesuperior:               0x00b3, /* U+00B3 SUPERSCRIPT THREE */
+    XK_acute:                       0x00b4, /* U+00B4 ACUTE ACCENT */
+    XK_mu:                          0x00b5, /* U+00B5 MICRO SIGN */
+    XK_paragraph:                   0x00b6, /* U+00B6 PILCROW SIGN */
+    XK_periodcentered:              0x00b7, /* U+00B7 MIDDLE DOT */
+    XK_cedilla:                     0x00b8, /* U+00B8 CEDILLA */
+    XK_onesuperior:                 0x00b9, /* U+00B9 SUPERSCRIPT ONE */
+    XK_masculine:                   0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */
+    XK_guillemotright:              0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
+    XK_onequarter:                  0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */
+    XK_onehalf:                     0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */
+    XK_threequarters:               0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */
+    XK_questiondown:                0x00bf, /* U+00BF INVERTED QUESTION MARK */
+    XK_Agrave:                      0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */
+    XK_Aacute:                      0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */
+    XK_Acircumflex:                 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+    XK_Atilde:                      0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */
+    XK_Adiaeresis:                  0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */
+    XK_Aring:                       0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */
+    XK_AE:                          0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */
+    XK_Ccedilla:                    0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */
+    XK_Egrave:                      0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */
+    XK_Eacute:                      0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
+    XK_Ecircumflex:                 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+    XK_Ediaeresis:                  0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */
+    XK_Igrave:                      0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */
+    XK_Iacute:                      0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */
+    XK_Icircumflex:                 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+    XK_Idiaeresis:                  0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */
+    XK_ETH:                         0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */
+    XK_Eth:                         0x00d0, /* deprecated */
+    XK_Ntilde:                      0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */
+    XK_Ograve:                      0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */
+    XK_Oacute:                      0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */
+    XK_Ocircumflex:                 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+    XK_Otilde:                      0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */
+    XK_Odiaeresis:                  0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */
+    XK_multiply:                    0x00d7, /* U+00D7 MULTIPLICATION SIGN */
+    XK_Oslash:                      0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
+    XK_Ooblique:                    0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
+    XK_Ugrave:                      0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */
+    XK_Uacute:                      0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */
+    XK_Ucircumflex:                 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+    XK_Udiaeresis:                  0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */
+    XK_Yacute:                      0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */
+    XK_THORN:                       0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */
+    XK_Thorn:                       0x00de, /* deprecated */
+    XK_ssharp:                      0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */
+    XK_agrave:                      0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */
+    XK_aacute:                      0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */
+    XK_acircumflex:                 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */
+    XK_atilde:                      0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */
+    XK_adiaeresis:                  0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */
+    XK_aring:                       0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */
+    XK_ae:                          0x00e6, /* U+00E6 LATIN SMALL LETTER AE */
+    XK_ccedilla:                    0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */
+    XK_egrave:                      0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */
+    XK_eacute:                      0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
+    XK_ecircumflex:                 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */
+    XK_ediaeresis:                  0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */
+    XK_igrave:                      0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */
+    XK_iacute:                      0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */
+    XK_icircumflex:                 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */
+    XK_idiaeresis:                  0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */
+    XK_eth:                         0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */
+    XK_ntilde:                      0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */
+    XK_ograve:                      0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */
+    XK_oacute:                      0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */
+    XK_ocircumflex:                 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */
+    XK_otilde:                      0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */
+    XK_odiaeresis:                  0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */
+    XK_division:                    0x00f7, /* U+00F7 DIVISION SIGN */
+    XK_oslash:                      0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
+    XK_ooblique:                    0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
+    XK_ugrave:                      0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */
+    XK_uacute:                      0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */
+    XK_ucircumflex:                 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */
+    XK_udiaeresis:                  0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */
+    XK_yacute:                      0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
+    XK_thorn:                       0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
+    XK_ydiaeresis:                  0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
+
+    /*
+     * Korean
+     * Byte 3 = 0x0e
+     */
+
+    XK_Hangul:                      0xff31, /* Hangul start/stop(toggle) */
+    XK_Hangul_Hanja:                0xff34, /* Start Hangul->Hanja Conversion */
+    XK_Hangul_Jeonja:               0xff38, /* Jeonja mode */
+
+    /*
+     * XFree86 vendor specific keysyms.
+     *
+     * The XFree86 keysym range is 0x10080001 - 0x1008FFFF.
+     */
+
+    XF86XK_ModeLock:                0x1008FF01,
+    XF86XK_MonBrightnessUp:         0x1008FF02,
+    XF86XK_MonBrightnessDown:       0x1008FF03,
+    XF86XK_KbdLightOnOff:           0x1008FF04,
+    XF86XK_KbdBrightnessUp:         0x1008FF05,
+    XF86XK_KbdBrightnessDown:       0x1008FF06,
+    XF86XK_Standby:                 0x1008FF10,
+    XF86XK_AudioLowerVolume:        0x1008FF11,
+    XF86XK_AudioMute:               0x1008FF12,
+    XF86XK_AudioRaiseVolume:        0x1008FF13,
+    XF86XK_AudioPlay:               0x1008FF14,
+    XF86XK_AudioStop:               0x1008FF15,
+    XF86XK_AudioPrev:               0x1008FF16,
+    XF86XK_AudioNext:               0x1008FF17,
+    XF86XK_HomePage:                0x1008FF18,
+    XF86XK_Mail:                    0x1008FF19,
+    XF86XK_Start:                   0x1008FF1A,
+    XF86XK_Search:                  0x1008FF1B,
+    XF86XK_AudioRecord:             0x1008FF1C,
+    XF86XK_Calculator:              0x1008FF1D,
+    XF86XK_Memo:                    0x1008FF1E,
+    XF86XK_ToDoList:                0x1008FF1F,
+    XF86XK_Calendar:                0x1008FF20,
+    XF86XK_PowerDown:               0x1008FF21,
+    XF86XK_ContrastAdjust:          0x1008FF22,
+    XF86XK_RockerUp:                0x1008FF23,
+    XF86XK_RockerDown:              0x1008FF24,
+    XF86XK_RockerEnter:             0x1008FF25,
+    XF86XK_Back:                    0x1008FF26,
+    XF86XK_Forward:                 0x1008FF27,
+    XF86XK_Stop:                    0x1008FF28,
+    XF86XK_Refresh:                 0x1008FF29,
+    XF86XK_PowerOff:                0x1008FF2A,
+    XF86XK_WakeUp:                  0x1008FF2B,
+    XF86XK_Eject:                   0x1008FF2C,
+    XF86XK_ScreenSaver:             0x1008FF2D,
+    XF86XK_WWW:                     0x1008FF2E,
+    XF86XK_Sleep:                   0x1008FF2F,
+    XF86XK_Favorites:               0x1008FF30,
+    XF86XK_AudioPause:              0x1008FF31,
+    XF86XK_AudioMedia:              0x1008FF32,
+    XF86XK_MyComputer:              0x1008FF33,
+    XF86XK_VendorHome:              0x1008FF34,
+    XF86XK_LightBulb:               0x1008FF35,
+    XF86XK_Shop:                    0x1008FF36,
+    XF86XK_History:                 0x1008FF37,
+    XF86XK_OpenURL:                 0x1008FF38,
+    XF86XK_AddFavorite:             0x1008FF39,
+    XF86XK_HotLinks:                0x1008FF3A,
+    XF86XK_BrightnessAdjust:        0x1008FF3B,
+    XF86XK_Finance:                 0x1008FF3C,
+    XF86XK_Community:               0x1008FF3D,
+    XF86XK_AudioRewind:             0x1008FF3E,
+    XF86XK_BackForward:             0x1008FF3F,
+    XF86XK_Launch0:                 0x1008FF40,
+    XF86XK_Launch1:                 0x1008FF41,
+    XF86XK_Launch2:                 0x1008FF42,
+    XF86XK_Launch3:                 0x1008FF43,
+    XF86XK_Launch4:                 0x1008FF44,
+    XF86XK_Launch5:                 0x1008FF45,
+    XF86XK_Launch6:                 0x1008FF46,
+    XF86XK_Launch7:                 0x1008FF47,
+    XF86XK_Launch8:                 0x1008FF48,
+    XF86XK_Launch9:                 0x1008FF49,
+    XF86XK_LaunchA:                 0x1008FF4A,
+    XF86XK_LaunchB:                 0x1008FF4B,
+    XF86XK_LaunchC:                 0x1008FF4C,
+    XF86XK_LaunchD:                 0x1008FF4D,
+    XF86XK_LaunchE:                 0x1008FF4E,
+    XF86XK_LaunchF:                 0x1008FF4F,
+    XF86XK_ApplicationLeft:         0x1008FF50,
+    XF86XK_ApplicationRight:        0x1008FF51,
+    XF86XK_Book:                    0x1008FF52,
+    XF86XK_CD:                      0x1008FF53,
+    XF86XK_Calculater:              0x1008FF54,
+    XF86XK_Clear:                   0x1008FF55,
+    XF86XK_Close:                   0x1008FF56,
+    XF86XK_Copy:                    0x1008FF57,
+    XF86XK_Cut:                     0x1008FF58,
+    XF86XK_Display:                 0x1008FF59,
+    XF86XK_DOS:                     0x1008FF5A,
+    XF86XK_Documents:               0x1008FF5B,
+    XF86XK_Excel:                   0x1008FF5C,
+    XF86XK_Explorer:                0x1008FF5D,
+    XF86XK_Game:                    0x1008FF5E,
+    XF86XK_Go:                      0x1008FF5F,
+    XF86XK_iTouch:                  0x1008FF60,
+    XF86XK_LogOff:                  0x1008FF61,
+    XF86XK_Market:                  0x1008FF62,
+    XF86XK_Meeting:                 0x1008FF63,
+    XF86XK_MenuKB:                  0x1008FF65,
+    XF86XK_MenuPB:                  0x1008FF66,
+    XF86XK_MySites:                 0x1008FF67,
+    XF86XK_New:                     0x1008FF68,
+    XF86XK_News:                    0x1008FF69,
+    XF86XK_OfficeHome:              0x1008FF6A,
+    XF86XK_Open:                    0x1008FF6B,
+    XF86XK_Option:                  0x1008FF6C,
+    XF86XK_Paste:                   0x1008FF6D,
+    XF86XK_Phone:                   0x1008FF6E,
+    XF86XK_Q:                       0x1008FF70,
+    XF86XK_Reply:                   0x1008FF72,
+    XF86XK_Reload:                  0x1008FF73,
+    XF86XK_RotateWindows:           0x1008FF74,
+    XF86XK_RotationPB:              0x1008FF75,
+    XF86XK_RotationKB:              0x1008FF76,
+    XF86XK_Save:                    0x1008FF77,
+    XF86XK_ScrollUp:                0x1008FF78,
+    XF86XK_ScrollDown:              0x1008FF79,
+    XF86XK_ScrollClick:             0x1008FF7A,
+    XF86XK_Send:                    0x1008FF7B,
+    XF86XK_Spell:                   0x1008FF7C,
+    XF86XK_SplitScreen:             0x1008FF7D,
+    XF86XK_Support:                 0x1008FF7E,
+    XF86XK_TaskPane:                0x1008FF7F,
+    XF86XK_Terminal:                0x1008FF80,
+    XF86XK_Tools:                   0x1008FF81,
+    XF86XK_Travel:                  0x1008FF82,
+    XF86XK_UserPB:                  0x1008FF84,
+    XF86XK_User1KB:                 0x1008FF85,
+    XF86XK_User2KB:                 0x1008FF86,
+    XF86XK_Video:                   0x1008FF87,
+    XF86XK_WheelButton:             0x1008FF88,
+    XF86XK_Word:                    0x1008FF89,
+    XF86XK_Xfer:                    0x1008FF8A,
+    XF86XK_ZoomIn:                  0x1008FF8B,
+    XF86XK_ZoomOut:                 0x1008FF8C,
+    XF86XK_Away:                    0x1008FF8D,
+    XF86XK_Messenger:               0x1008FF8E,
+    XF86XK_WebCam:                  0x1008FF8F,
+    XF86XK_MailForward:             0x1008FF90,
+    XF86XK_Pictures:                0x1008FF91,
+    XF86XK_Music:                   0x1008FF92,
+    XF86XK_Battery:                 0x1008FF93,
+    XF86XK_Bluetooth:               0x1008FF94,
+    XF86XK_WLAN:                    0x1008FF95,
+    XF86XK_UWB:                     0x1008FF96,
+    XF86XK_AudioForward:            0x1008FF97,
+    XF86XK_AudioRepeat:             0x1008FF98,
+    XF86XK_AudioRandomPlay:         0x1008FF99,
+    XF86XK_Subtitle:                0x1008FF9A,
+    XF86XK_AudioCycleTrack:         0x1008FF9B,
+    XF86XK_CycleAngle:              0x1008FF9C,
+    XF86XK_FrameBack:               0x1008FF9D,
+    XF86XK_FrameForward:            0x1008FF9E,
+    XF86XK_Time:                    0x1008FF9F,
+    XF86XK_Select:                  0x1008FFA0,
+    XF86XK_View:                    0x1008FFA1,
+    XF86XK_TopMenu:                 0x1008FFA2,
+    XF86XK_Red:                     0x1008FFA3,
+    XF86XK_Green:                   0x1008FFA4,
+    XF86XK_Yellow:                  0x1008FFA5,
+    XF86XK_Blue:                    0x1008FFA6,
+    XF86XK_Suspend:                 0x1008FFA7,
+    XF86XK_Hibernate:               0x1008FFA8,
+    XF86XK_TouchpadToggle:          0x1008FFA9,
+    XF86XK_TouchpadOn:              0x1008FFB0,
+    XF86XK_TouchpadOff:             0x1008FFB1,
+    XF86XK_AudioMicMute:            0x1008FFB2,
+    XF86XK_Switch_VT_1:             0x1008FE01,
+    XF86XK_Switch_VT_2:             0x1008FE02,
+    XF86XK_Switch_VT_3:             0x1008FE03,
+    XF86XK_Switch_VT_4:             0x1008FE04,
+    XF86XK_Switch_VT_5:             0x1008FE05,
+    XF86XK_Switch_VT_6:             0x1008FE06,
+    XF86XK_Switch_VT_7:             0x1008FE07,
+    XF86XK_Switch_VT_8:             0x1008FE08,
+    XF86XK_Switch_VT_9:             0x1008FE09,
+    XF86XK_Switch_VT_10:            0x1008FE0A,
+    XF86XK_Switch_VT_11:            0x1008FE0B,
+    XF86XK_Switch_VT_12:            0x1008FE0C,
+    XF86XK_Ungrab:                  0x1008FE20,
+    XF86XK_ClearGrab:               0x1008FE21,
+    XF86XK_Next_VMode:              0x1008FE22,
+    XF86XK_Prev_VMode:              0x1008FE23,
+    XF86XK_LogWindowTree:           0x1008FE24,
+    XF86XK_LogGrabInfo:             0x1008FE25,
+};

+ 688 - 0
static/novnc/core/input/keysymdef.js

@@ -0,0 +1,688 @@
+/*
+ * Mapping from Unicode codepoints to X11/RFB keysyms
+ *
+ * This file was automatically generated from keysymdef.h
+ * DO NOT EDIT!
+ */
+
+/* Functions at the bottom */
+
+var codepoints = {
+    0x0100: 0x03c0, // XK_Amacron
+    0x0101: 0x03e0, // XK_amacron
+    0x0102: 0x01c3, // XK_Abreve
+    0x0103: 0x01e3, // XK_abreve
+    0x0104: 0x01a1, // XK_Aogonek
+    0x0105: 0x01b1, // XK_aogonek
+    0x0106: 0x01c6, // XK_Cacute
+    0x0107: 0x01e6, // XK_cacute
+    0x0108: 0x02c6, // XK_Ccircumflex
+    0x0109: 0x02e6, // XK_ccircumflex
+    0x010a: 0x02c5, // XK_Cabovedot
+    0x010b: 0x02e5, // XK_cabovedot
+    0x010c: 0x01c8, // XK_Ccaron
+    0x010d: 0x01e8, // XK_ccaron
+    0x010e: 0x01cf, // XK_Dcaron
+    0x010f: 0x01ef, // XK_dcaron
+    0x0110: 0x01d0, // XK_Dstroke
+    0x0111: 0x01f0, // XK_dstroke
+    0x0112: 0x03aa, // XK_Emacron
+    0x0113: 0x03ba, // XK_emacron
+    0x0116: 0x03cc, // XK_Eabovedot
+    0x0117: 0x03ec, // XK_eabovedot
+    0x0118: 0x01ca, // XK_Eogonek
+    0x0119: 0x01ea, // XK_eogonek
+    0x011a: 0x01cc, // XK_Ecaron
+    0x011b: 0x01ec, // XK_ecaron
+    0x011c: 0x02d8, // XK_Gcircumflex
+    0x011d: 0x02f8, // XK_gcircumflex
+    0x011e: 0x02ab, // XK_Gbreve
+    0x011f: 0x02bb, // XK_gbreve
+    0x0120: 0x02d5, // XK_Gabovedot
+    0x0121: 0x02f5, // XK_gabovedot
+    0x0122: 0x03ab, // XK_Gcedilla
+    0x0123: 0x03bb, // XK_gcedilla
+    0x0124: 0x02a6, // XK_Hcircumflex
+    0x0125: 0x02b6, // XK_hcircumflex
+    0x0126: 0x02a1, // XK_Hstroke
+    0x0127: 0x02b1, // XK_hstroke
+    0x0128: 0x03a5, // XK_Itilde
+    0x0129: 0x03b5, // XK_itilde
+    0x012a: 0x03cf, // XK_Imacron
+    0x012b: 0x03ef, // XK_imacron
+    0x012e: 0x03c7, // XK_Iogonek
+    0x012f: 0x03e7, // XK_iogonek
+    0x0130: 0x02a9, // XK_Iabovedot
+    0x0131: 0x02b9, // XK_idotless
+    0x0134: 0x02ac, // XK_Jcircumflex
+    0x0135: 0x02bc, // XK_jcircumflex
+    0x0136: 0x03d3, // XK_Kcedilla
+    0x0137: 0x03f3, // XK_kcedilla
+    0x0138: 0x03a2, // XK_kra
+    0x0139: 0x01c5, // XK_Lacute
+    0x013a: 0x01e5, // XK_lacute
+    0x013b: 0x03a6, // XK_Lcedilla
+    0x013c: 0x03b6, // XK_lcedilla
+    0x013d: 0x01a5, // XK_Lcaron
+    0x013e: 0x01b5, // XK_lcaron
+    0x0141: 0x01a3, // XK_Lstroke
+    0x0142: 0x01b3, // XK_lstroke
+    0x0143: 0x01d1, // XK_Nacute
+    0x0144: 0x01f1, // XK_nacute
+    0x0145: 0x03d1, // XK_Ncedilla
+    0x0146: 0x03f1, // XK_ncedilla
+    0x0147: 0x01d2, // XK_Ncaron
+    0x0148: 0x01f2, // XK_ncaron
+    0x014a: 0x03bd, // XK_ENG
+    0x014b: 0x03bf, // XK_eng
+    0x014c: 0x03d2, // XK_Omacron
+    0x014d: 0x03f2, // XK_omacron
+    0x0150: 0x01d5, // XK_Odoubleacute
+    0x0151: 0x01f5, // XK_odoubleacute
+    0x0152: 0x13bc, // XK_OE
+    0x0153: 0x13bd, // XK_oe
+    0x0154: 0x01c0, // XK_Racute
+    0x0155: 0x01e0, // XK_racute
+    0x0156: 0x03a3, // XK_Rcedilla
+    0x0157: 0x03b3, // XK_rcedilla
+    0x0158: 0x01d8, // XK_Rcaron
+    0x0159: 0x01f8, // XK_rcaron
+    0x015a: 0x01a6, // XK_Sacute
+    0x015b: 0x01b6, // XK_sacute
+    0x015c: 0x02de, // XK_Scircumflex
+    0x015d: 0x02fe, // XK_scircumflex
+    0x015e: 0x01aa, // XK_Scedilla
+    0x015f: 0x01ba, // XK_scedilla
+    0x0160: 0x01a9, // XK_Scaron
+    0x0161: 0x01b9, // XK_scaron
+    0x0162: 0x01de, // XK_Tcedilla
+    0x0163: 0x01fe, // XK_tcedilla
+    0x0164: 0x01ab, // XK_Tcaron
+    0x0165: 0x01bb, // XK_tcaron
+    0x0166: 0x03ac, // XK_Tslash
+    0x0167: 0x03bc, // XK_tslash
+    0x0168: 0x03dd, // XK_Utilde
+    0x0169: 0x03fd, // XK_utilde
+    0x016a: 0x03de, // XK_Umacron
+    0x016b: 0x03fe, // XK_umacron
+    0x016c: 0x02dd, // XK_Ubreve
+    0x016d: 0x02fd, // XK_ubreve
+    0x016e: 0x01d9, // XK_Uring
+    0x016f: 0x01f9, // XK_uring
+    0x0170: 0x01db, // XK_Udoubleacute
+    0x0171: 0x01fb, // XK_udoubleacute
+    0x0172: 0x03d9, // XK_Uogonek
+    0x0173: 0x03f9, // XK_uogonek
+    0x0178: 0x13be, // XK_Ydiaeresis
+    0x0179: 0x01ac, // XK_Zacute
+    0x017a: 0x01bc, // XK_zacute
+    0x017b: 0x01af, // XK_Zabovedot
+    0x017c: 0x01bf, // XK_zabovedot
+    0x017d: 0x01ae, // XK_Zcaron
+    0x017e: 0x01be, // XK_zcaron
+    0x0192: 0x08f6, // XK_function
+    0x01d2: 0x10001d1, // XK_Ocaron
+    0x02c7: 0x01b7, // XK_caron
+    0x02d8: 0x01a2, // XK_breve
+    0x02d9: 0x01ff, // XK_abovedot
+    0x02db: 0x01b2, // XK_ogonek
+    0x02dd: 0x01bd, // XK_doubleacute
+    0x0385: 0x07ae, // XK_Greek_accentdieresis
+    0x0386: 0x07a1, // XK_Greek_ALPHAaccent
+    0x0388: 0x07a2, // XK_Greek_EPSILONaccent
+    0x0389: 0x07a3, // XK_Greek_ETAaccent
+    0x038a: 0x07a4, // XK_Greek_IOTAaccent
+    0x038c: 0x07a7, // XK_Greek_OMICRONaccent
+    0x038e: 0x07a8, // XK_Greek_UPSILONaccent
+    0x038f: 0x07ab, // XK_Greek_OMEGAaccent
+    0x0390: 0x07b6, // XK_Greek_iotaaccentdieresis
+    0x0391: 0x07c1, // XK_Greek_ALPHA
+    0x0392: 0x07c2, // XK_Greek_BETA
+    0x0393: 0x07c3, // XK_Greek_GAMMA
+    0x0394: 0x07c4, // XK_Greek_DELTA
+    0x0395: 0x07c5, // XK_Greek_EPSILON
+    0x0396: 0x07c6, // XK_Greek_ZETA
+    0x0397: 0x07c7, // XK_Greek_ETA
+    0x0398: 0x07c8, // XK_Greek_THETA
+    0x0399: 0x07c9, // XK_Greek_IOTA
+    0x039a: 0x07ca, // XK_Greek_KAPPA
+    0x039b: 0x07cb, // XK_Greek_LAMDA
+    0x039c: 0x07cc, // XK_Greek_MU
+    0x039d: 0x07cd, // XK_Greek_NU
+    0x039e: 0x07ce, // XK_Greek_XI
+    0x039f: 0x07cf, // XK_Greek_OMICRON
+    0x03a0: 0x07d0, // XK_Greek_PI
+    0x03a1: 0x07d1, // XK_Greek_RHO
+    0x03a3: 0x07d2, // XK_Greek_SIGMA
+    0x03a4: 0x07d4, // XK_Greek_TAU
+    0x03a5: 0x07d5, // XK_Greek_UPSILON
+    0x03a6: 0x07d6, // XK_Greek_PHI
+    0x03a7: 0x07d7, // XK_Greek_CHI
+    0x03a8: 0x07d8, // XK_Greek_PSI
+    0x03a9: 0x07d9, // XK_Greek_OMEGA
+    0x03aa: 0x07a5, // XK_Greek_IOTAdieresis
+    0x03ab: 0x07a9, // XK_Greek_UPSILONdieresis
+    0x03ac: 0x07b1, // XK_Greek_alphaaccent
+    0x03ad: 0x07b2, // XK_Greek_epsilonaccent
+    0x03ae: 0x07b3, // XK_Greek_etaaccent
+    0x03af: 0x07b4, // XK_Greek_iotaaccent
+    0x03b0: 0x07ba, // XK_Greek_upsilonaccentdieresis
+    0x03b1: 0x07e1, // XK_Greek_alpha
+    0x03b2: 0x07e2, // XK_Greek_beta
+    0x03b3: 0x07e3, // XK_Greek_gamma
+    0x03b4: 0x07e4, // XK_Greek_delta
+    0x03b5: 0x07e5, // XK_Greek_epsilon
+    0x03b6: 0x07e6, // XK_Greek_zeta
+    0x03b7: 0x07e7, // XK_Greek_eta
+    0x03b8: 0x07e8, // XK_Greek_theta
+    0x03b9: 0x07e9, // XK_Greek_iota
+    0x03ba: 0x07ea, // XK_Greek_kappa
+    0x03bb: 0x07eb, // XK_Greek_lamda
+    0x03bc: 0x07ec, // XK_Greek_mu
+    0x03bd: 0x07ed, // XK_Greek_nu
+    0x03be: 0x07ee, // XK_Greek_xi
+    0x03bf: 0x07ef, // XK_Greek_omicron
+    0x03c0: 0x07f0, // XK_Greek_pi
+    0x03c1: 0x07f1, // XK_Greek_rho
+    0x03c2: 0x07f3, // XK_Greek_finalsmallsigma
+    0x03c3: 0x07f2, // XK_Greek_sigma
+    0x03c4: 0x07f4, // XK_Greek_tau
+    0x03c5: 0x07f5, // XK_Greek_upsilon
+    0x03c6: 0x07f6, // XK_Greek_phi
+    0x03c7: 0x07f7, // XK_Greek_chi
+    0x03c8: 0x07f8, // XK_Greek_psi
+    0x03c9: 0x07f9, // XK_Greek_omega
+    0x03ca: 0x07b5, // XK_Greek_iotadieresis
+    0x03cb: 0x07b9, // XK_Greek_upsilondieresis
+    0x03cc: 0x07b7, // XK_Greek_omicronaccent
+    0x03cd: 0x07b8, // XK_Greek_upsilonaccent
+    0x03ce: 0x07bb, // XK_Greek_omegaaccent
+    0x0401: 0x06b3, // XK_Cyrillic_IO
+    0x0402: 0x06b1, // XK_Serbian_DJE
+    0x0403: 0x06b2, // XK_Macedonia_GJE
+    0x0404: 0x06b4, // XK_Ukrainian_IE
+    0x0405: 0x06b5, // XK_Macedonia_DSE
+    0x0406: 0x06b6, // XK_Ukrainian_I
+    0x0407: 0x06b7, // XK_Ukrainian_YI
+    0x0408: 0x06b8, // XK_Cyrillic_JE
+    0x0409: 0x06b9, // XK_Cyrillic_LJE
+    0x040a: 0x06ba, // XK_Cyrillic_NJE
+    0x040b: 0x06bb, // XK_Serbian_TSHE
+    0x040c: 0x06bc, // XK_Macedonia_KJE
+    0x040e: 0x06be, // XK_Byelorussian_SHORTU
+    0x040f: 0x06bf, // XK_Cyrillic_DZHE
+    0x0410: 0x06e1, // XK_Cyrillic_A
+    0x0411: 0x06e2, // XK_Cyrillic_BE
+    0x0412: 0x06f7, // XK_Cyrillic_VE
+    0x0413: 0x06e7, // XK_Cyrillic_GHE
+    0x0414: 0x06e4, // XK_Cyrillic_DE
+    0x0415: 0x06e5, // XK_Cyrillic_IE
+    0x0416: 0x06f6, // XK_Cyrillic_ZHE
+    0x0417: 0x06fa, // XK_Cyrillic_ZE
+    0x0418: 0x06e9, // XK_Cyrillic_I
+    0x0419: 0x06ea, // XK_Cyrillic_SHORTI
+    0x041a: 0x06eb, // XK_Cyrillic_KA
+    0x041b: 0x06ec, // XK_Cyrillic_EL
+    0x041c: 0x06ed, // XK_Cyrillic_EM
+    0x041d: 0x06ee, // XK_Cyrillic_EN
+    0x041e: 0x06ef, // XK_Cyrillic_O
+    0x041f: 0x06f0, // XK_Cyrillic_PE
+    0x0420: 0x06f2, // XK_Cyrillic_ER
+    0x0421: 0x06f3, // XK_Cyrillic_ES
+    0x0422: 0x06f4, // XK_Cyrillic_TE
+    0x0423: 0x06f5, // XK_Cyrillic_U
+    0x0424: 0x06e6, // XK_Cyrillic_EF
+    0x0425: 0x06e8, // XK_Cyrillic_HA
+    0x0426: 0x06e3, // XK_Cyrillic_TSE
+    0x0427: 0x06fe, // XK_Cyrillic_CHE
+    0x0428: 0x06fb, // XK_Cyrillic_SHA
+    0x0429: 0x06fd, // XK_Cyrillic_SHCHA
+    0x042a: 0x06ff, // XK_Cyrillic_HARDSIGN
+    0x042b: 0x06f9, // XK_Cyrillic_YERU
+    0x042c: 0x06f8, // XK_Cyrillic_SOFTSIGN
+    0x042d: 0x06fc, // XK_Cyrillic_E
+    0x042e: 0x06e0, // XK_Cyrillic_YU
+    0x042f: 0x06f1, // XK_Cyrillic_YA
+    0x0430: 0x06c1, // XK_Cyrillic_a
+    0x0431: 0x06c2, // XK_Cyrillic_be
+    0x0432: 0x06d7, // XK_Cyrillic_ve
+    0x0433: 0x06c7, // XK_Cyrillic_ghe
+    0x0434: 0x06c4, // XK_Cyrillic_de
+    0x0435: 0x06c5, // XK_Cyrillic_ie
+    0x0436: 0x06d6, // XK_Cyrillic_zhe
+    0x0437: 0x06da, // XK_Cyrillic_ze
+    0x0438: 0x06c9, // XK_Cyrillic_i
+    0x0439: 0x06ca, // XK_Cyrillic_shorti
+    0x043a: 0x06cb, // XK_Cyrillic_ka
+    0x043b: 0x06cc, // XK_Cyrillic_el
+    0x043c: 0x06cd, // XK_Cyrillic_em
+    0x043d: 0x06ce, // XK_Cyrillic_en
+    0x043e: 0x06cf, // XK_Cyrillic_o
+    0x043f: 0x06d0, // XK_Cyrillic_pe
+    0x0440: 0x06d2, // XK_Cyrillic_er
+    0x0441: 0x06d3, // XK_Cyrillic_es
+    0x0442: 0x06d4, // XK_Cyrillic_te
+    0x0443: 0x06d5, // XK_Cyrillic_u
+    0x0444: 0x06c6, // XK_Cyrillic_ef
+    0x0445: 0x06c8, // XK_Cyrillic_ha
+    0x0446: 0x06c3, // XK_Cyrillic_tse
+    0x0447: 0x06de, // XK_Cyrillic_che
+    0x0448: 0x06db, // XK_Cyrillic_sha
+    0x0449: 0x06dd, // XK_Cyrillic_shcha
+    0x044a: 0x06df, // XK_Cyrillic_hardsign
+    0x044b: 0x06d9, // XK_Cyrillic_yeru
+    0x044c: 0x06d8, // XK_Cyrillic_softsign
+    0x044d: 0x06dc, // XK_Cyrillic_e
+    0x044e: 0x06c0, // XK_Cyrillic_yu
+    0x044f: 0x06d1, // XK_Cyrillic_ya
+    0x0451: 0x06a3, // XK_Cyrillic_io
+    0x0452: 0x06a1, // XK_Serbian_dje
+    0x0453: 0x06a2, // XK_Macedonia_gje
+    0x0454: 0x06a4, // XK_Ukrainian_ie
+    0x0455: 0x06a5, // XK_Macedonia_dse
+    0x0456: 0x06a6, // XK_Ukrainian_i
+    0x0457: 0x06a7, // XK_Ukrainian_yi
+    0x0458: 0x06a8, // XK_Cyrillic_je
+    0x0459: 0x06a9, // XK_Cyrillic_lje
+    0x045a: 0x06aa, // XK_Cyrillic_nje
+    0x045b: 0x06ab, // XK_Serbian_tshe
+    0x045c: 0x06ac, // XK_Macedonia_kje
+    0x045e: 0x06ae, // XK_Byelorussian_shortu
+    0x045f: 0x06af, // XK_Cyrillic_dzhe
+    0x0490: 0x06bd, // XK_Ukrainian_GHE_WITH_UPTURN
+    0x0491: 0x06ad, // XK_Ukrainian_ghe_with_upturn
+    0x05d0: 0x0ce0, // XK_hebrew_aleph
+    0x05d1: 0x0ce1, // XK_hebrew_bet
+    0x05d2: 0x0ce2, // XK_hebrew_gimel
+    0x05d3: 0x0ce3, // XK_hebrew_dalet
+    0x05d4: 0x0ce4, // XK_hebrew_he
+    0x05d5: 0x0ce5, // XK_hebrew_waw
+    0x05d6: 0x0ce6, // XK_hebrew_zain
+    0x05d7: 0x0ce7, // XK_hebrew_chet
+    0x05d8: 0x0ce8, // XK_hebrew_tet
+    0x05d9: 0x0ce9, // XK_hebrew_yod
+    0x05da: 0x0cea, // XK_hebrew_finalkaph
+    0x05db: 0x0ceb, // XK_hebrew_kaph
+    0x05dc: 0x0cec, // XK_hebrew_lamed
+    0x05dd: 0x0ced, // XK_hebrew_finalmem
+    0x05de: 0x0cee, // XK_hebrew_mem
+    0x05df: 0x0cef, // XK_hebrew_finalnun
+    0x05e0: 0x0cf0, // XK_hebrew_nun
+    0x05e1: 0x0cf1, // XK_hebrew_samech
+    0x05e2: 0x0cf2, // XK_hebrew_ayin
+    0x05e3: 0x0cf3, // XK_hebrew_finalpe
+    0x05e4: 0x0cf4, // XK_hebrew_pe
+    0x05e5: 0x0cf5, // XK_hebrew_finalzade
+    0x05e6: 0x0cf6, // XK_hebrew_zade
+    0x05e7: 0x0cf7, // XK_hebrew_qoph
+    0x05e8: 0x0cf8, // XK_hebrew_resh
+    0x05e9: 0x0cf9, // XK_hebrew_shin
+    0x05ea: 0x0cfa, // XK_hebrew_taw
+    0x060c: 0x05ac, // XK_Arabic_comma
+    0x061b: 0x05bb, // XK_Arabic_semicolon
+    0x061f: 0x05bf, // XK_Arabic_question_mark
+    0x0621: 0x05c1, // XK_Arabic_hamza
+    0x0622: 0x05c2, // XK_Arabic_maddaonalef
+    0x0623: 0x05c3, // XK_Arabic_hamzaonalef
+    0x0624: 0x05c4, // XK_Arabic_hamzaonwaw
+    0x0625: 0x05c5, // XK_Arabic_hamzaunderalef
+    0x0626: 0x05c6, // XK_Arabic_hamzaonyeh
+    0x0627: 0x05c7, // XK_Arabic_alef
+    0x0628: 0x05c8, // XK_Arabic_beh
+    0x0629: 0x05c9, // XK_Arabic_tehmarbuta
+    0x062a: 0x05ca, // XK_Arabic_teh
+    0x062b: 0x05cb, // XK_Arabic_theh
+    0x062c: 0x05cc, // XK_Arabic_jeem
+    0x062d: 0x05cd, // XK_Arabic_hah
+    0x062e: 0x05ce, // XK_Arabic_khah
+    0x062f: 0x05cf, // XK_Arabic_dal
+    0x0630: 0x05d0, // XK_Arabic_thal
+    0x0631: 0x05d1, // XK_Arabic_ra
+    0x0632: 0x05d2, // XK_Arabic_zain
+    0x0633: 0x05d3, // XK_Arabic_seen
+    0x0634: 0x05d4, // XK_Arabic_sheen
+    0x0635: 0x05d5, // XK_Arabic_sad
+    0x0636: 0x05d6, // XK_Arabic_dad
+    0x0637: 0x05d7, // XK_Arabic_tah
+    0x0638: 0x05d8, // XK_Arabic_zah
+    0x0639: 0x05d9, // XK_Arabic_ain
+    0x063a: 0x05da, // XK_Arabic_ghain
+    0x0640: 0x05e0, // XK_Arabic_tatweel
+    0x0641: 0x05e1, // XK_Arabic_feh
+    0x0642: 0x05e2, // XK_Arabic_qaf
+    0x0643: 0x05e3, // XK_Arabic_kaf
+    0x0644: 0x05e4, // XK_Arabic_lam
+    0x0645: 0x05e5, // XK_Arabic_meem
+    0x0646: 0x05e6, // XK_Arabic_noon
+    0x0647: 0x05e7, // XK_Arabic_ha
+    0x0648: 0x05e8, // XK_Arabic_waw
+    0x0649: 0x05e9, // XK_Arabic_alefmaksura
+    0x064a: 0x05ea, // XK_Arabic_yeh
+    0x064b: 0x05eb, // XK_Arabic_fathatan
+    0x064c: 0x05ec, // XK_Arabic_dammatan
+    0x064d: 0x05ed, // XK_Arabic_kasratan
+    0x064e: 0x05ee, // XK_Arabic_fatha
+    0x064f: 0x05ef, // XK_Arabic_damma
+    0x0650: 0x05f0, // XK_Arabic_kasra
+    0x0651: 0x05f1, // XK_Arabic_shadda
+    0x0652: 0x05f2, // XK_Arabic_sukun
+    0x0e01: 0x0da1, // XK_Thai_kokai
+    0x0e02: 0x0da2, // XK_Thai_khokhai
+    0x0e03: 0x0da3, // XK_Thai_khokhuat
+    0x0e04: 0x0da4, // XK_Thai_khokhwai
+    0x0e05: 0x0da5, // XK_Thai_khokhon
+    0x0e06: 0x0da6, // XK_Thai_khorakhang
+    0x0e07: 0x0da7, // XK_Thai_ngongu
+    0x0e08: 0x0da8, // XK_Thai_chochan
+    0x0e09: 0x0da9, // XK_Thai_choching
+    0x0e0a: 0x0daa, // XK_Thai_chochang
+    0x0e0b: 0x0dab, // XK_Thai_soso
+    0x0e0c: 0x0dac, // XK_Thai_chochoe
+    0x0e0d: 0x0dad, // XK_Thai_yoying
+    0x0e0e: 0x0dae, // XK_Thai_dochada
+    0x0e0f: 0x0daf, // XK_Thai_topatak
+    0x0e10: 0x0db0, // XK_Thai_thothan
+    0x0e11: 0x0db1, // XK_Thai_thonangmontho
+    0x0e12: 0x0db2, // XK_Thai_thophuthao
+    0x0e13: 0x0db3, // XK_Thai_nonen
+    0x0e14: 0x0db4, // XK_Thai_dodek
+    0x0e15: 0x0db5, // XK_Thai_totao
+    0x0e16: 0x0db6, // XK_Thai_thothung
+    0x0e17: 0x0db7, // XK_Thai_thothahan
+    0x0e18: 0x0db8, // XK_Thai_thothong
+    0x0e19: 0x0db9, // XK_Thai_nonu
+    0x0e1a: 0x0dba, // XK_Thai_bobaimai
+    0x0e1b: 0x0dbb, // XK_Thai_popla
+    0x0e1c: 0x0dbc, // XK_Thai_phophung
+    0x0e1d: 0x0dbd, // XK_Thai_fofa
+    0x0e1e: 0x0dbe, // XK_Thai_phophan
+    0x0e1f: 0x0dbf, // XK_Thai_fofan
+    0x0e20: 0x0dc0, // XK_Thai_phosamphao
+    0x0e21: 0x0dc1, // XK_Thai_moma
+    0x0e22: 0x0dc2, // XK_Thai_yoyak
+    0x0e23: 0x0dc3, // XK_Thai_rorua
+    0x0e24: 0x0dc4, // XK_Thai_ru
+    0x0e25: 0x0dc5, // XK_Thai_loling
+    0x0e26: 0x0dc6, // XK_Thai_lu
+    0x0e27: 0x0dc7, // XK_Thai_wowaen
+    0x0e28: 0x0dc8, // XK_Thai_sosala
+    0x0e29: 0x0dc9, // XK_Thai_sorusi
+    0x0e2a: 0x0dca, // XK_Thai_sosua
+    0x0e2b: 0x0dcb, // XK_Thai_hohip
+    0x0e2c: 0x0dcc, // XK_Thai_lochula
+    0x0e2d: 0x0dcd, // XK_Thai_oang
+    0x0e2e: 0x0dce, // XK_Thai_honokhuk
+    0x0e2f: 0x0dcf, // XK_Thai_paiyannoi
+    0x0e30: 0x0dd0, // XK_Thai_saraa
+    0x0e31: 0x0dd1, // XK_Thai_maihanakat
+    0x0e32: 0x0dd2, // XK_Thai_saraaa
+    0x0e33: 0x0dd3, // XK_Thai_saraam
+    0x0e34: 0x0dd4, // XK_Thai_sarai
+    0x0e35: 0x0dd5, // XK_Thai_saraii
+    0x0e36: 0x0dd6, // XK_Thai_saraue
+    0x0e37: 0x0dd7, // XK_Thai_sarauee
+    0x0e38: 0x0dd8, // XK_Thai_sarau
+    0x0e39: 0x0dd9, // XK_Thai_sarauu
+    0x0e3a: 0x0dda, // XK_Thai_phinthu
+    0x0e3f: 0x0ddf, // XK_Thai_baht
+    0x0e40: 0x0de0, // XK_Thai_sarae
+    0x0e41: 0x0de1, // XK_Thai_saraae
+    0x0e42: 0x0de2, // XK_Thai_sarao
+    0x0e43: 0x0de3, // XK_Thai_saraaimaimuan
+    0x0e44: 0x0de4, // XK_Thai_saraaimaimalai
+    0x0e45: 0x0de5, // XK_Thai_lakkhangyao
+    0x0e46: 0x0de6, // XK_Thai_maiyamok
+    0x0e47: 0x0de7, // XK_Thai_maitaikhu
+    0x0e48: 0x0de8, // XK_Thai_maiek
+    0x0e49: 0x0de9, // XK_Thai_maitho
+    0x0e4a: 0x0dea, // XK_Thai_maitri
+    0x0e4b: 0x0deb, // XK_Thai_maichattawa
+    0x0e4c: 0x0dec, // XK_Thai_thanthakhat
+    0x0e4d: 0x0ded, // XK_Thai_nikhahit
+    0x0e50: 0x0df0, // XK_Thai_leksun
+    0x0e51: 0x0df1, // XK_Thai_leknung
+    0x0e52: 0x0df2, // XK_Thai_leksong
+    0x0e53: 0x0df3, // XK_Thai_leksam
+    0x0e54: 0x0df4, // XK_Thai_leksi
+    0x0e55: 0x0df5, // XK_Thai_lekha
+    0x0e56: 0x0df6, // XK_Thai_lekhok
+    0x0e57: 0x0df7, // XK_Thai_lekchet
+    0x0e58: 0x0df8, // XK_Thai_lekpaet
+    0x0e59: 0x0df9, // XK_Thai_lekkao
+    0x2002: 0x0aa2, // XK_enspace
+    0x2003: 0x0aa1, // XK_emspace
+    0x2004: 0x0aa3, // XK_em3space
+    0x2005: 0x0aa4, // XK_em4space
+    0x2007: 0x0aa5, // XK_digitspace
+    0x2008: 0x0aa6, // XK_punctspace
+    0x2009: 0x0aa7, // XK_thinspace
+    0x200a: 0x0aa8, // XK_hairspace
+    0x2012: 0x0abb, // XK_figdash
+    0x2013: 0x0aaa, // XK_endash
+    0x2014: 0x0aa9, // XK_emdash
+    0x2015: 0x07af, // XK_Greek_horizbar
+    0x2017: 0x0cdf, // XK_hebrew_doublelowline
+    0x2018: 0x0ad0, // XK_leftsinglequotemark
+    0x2019: 0x0ad1, // XK_rightsinglequotemark
+    0x201a: 0x0afd, // XK_singlelowquotemark
+    0x201c: 0x0ad2, // XK_leftdoublequotemark
+    0x201d: 0x0ad3, // XK_rightdoublequotemark
+    0x201e: 0x0afe, // XK_doublelowquotemark
+    0x2020: 0x0af1, // XK_dagger
+    0x2021: 0x0af2, // XK_doubledagger
+    0x2022: 0x0ae6, // XK_enfilledcircbullet
+    0x2025: 0x0aaf, // XK_doubbaselinedot
+    0x2026: 0x0aae, // XK_ellipsis
+    0x2030: 0x0ad5, // XK_permille
+    0x2032: 0x0ad6, // XK_minutes
+    0x2033: 0x0ad7, // XK_seconds
+    0x2038: 0x0afc, // XK_caret
+    0x203e: 0x047e, // XK_overline
+    0x20a9: 0x0eff, // XK_Korean_Won
+    0x20ac: 0x20ac, // XK_EuroSign
+    0x2105: 0x0ab8, // XK_careof
+    0x2116: 0x06b0, // XK_numerosign
+    0x2117: 0x0afb, // XK_phonographcopyright
+    0x211e: 0x0ad4, // XK_prescription
+    0x2122: 0x0ac9, // XK_trademark
+    0x2153: 0x0ab0, // XK_onethird
+    0x2154: 0x0ab1, // XK_twothirds
+    0x2155: 0x0ab2, // XK_onefifth
+    0x2156: 0x0ab3, // XK_twofifths
+    0x2157: 0x0ab4, // XK_threefifths
+    0x2158: 0x0ab5, // XK_fourfifths
+    0x2159: 0x0ab6, // XK_onesixth
+    0x215a: 0x0ab7, // XK_fivesixths
+    0x215b: 0x0ac3, // XK_oneeighth
+    0x215c: 0x0ac4, // XK_threeeighths
+    0x215d: 0x0ac5, // XK_fiveeighths
+    0x215e: 0x0ac6, // XK_seveneighths
+    0x2190: 0x08fb, // XK_leftarrow
+    0x2191: 0x08fc, // XK_uparrow
+    0x2192: 0x08fd, // XK_rightarrow
+    0x2193: 0x08fe, // XK_downarrow
+    0x21d2: 0x08ce, // XK_implies
+    0x21d4: 0x08cd, // XK_ifonlyif
+    0x2202: 0x08ef, // XK_partialderivative
+    0x2207: 0x08c5, // XK_nabla
+    0x2218: 0x0bca, // XK_jot
+    0x221a: 0x08d6, // XK_radical
+    0x221d: 0x08c1, // XK_variation
+    0x221e: 0x08c2, // XK_infinity
+    0x2227: 0x08de, // XK_logicaland
+    0x2228: 0x08df, // XK_logicalor
+    0x2229: 0x08dc, // XK_intersection
+    0x222a: 0x08dd, // XK_union
+    0x222b: 0x08bf, // XK_integral
+    0x2234: 0x08c0, // XK_therefore
+    0x223c: 0x08c8, // XK_approximate
+    0x2243: 0x08c9, // XK_similarequal
+    0x2245: 0x1002248, // XK_approxeq
+    0x2260: 0x08bd, // XK_notequal
+    0x2261: 0x08cf, // XK_identical
+    0x2264: 0x08bc, // XK_lessthanequal
+    0x2265: 0x08be, // XK_greaterthanequal
+    0x2282: 0x08da, // XK_includedin
+    0x2283: 0x08db, // XK_includes
+    0x22a2: 0x0bfc, // XK_righttack
+    0x22a3: 0x0bdc, // XK_lefttack
+    0x22a4: 0x0bc2, // XK_downtack
+    0x22a5: 0x0bce, // XK_uptack
+    0x2308: 0x0bd3, // XK_upstile
+    0x230a: 0x0bc4, // XK_downstile
+    0x2315: 0x0afa, // XK_telephonerecorder
+    0x2320: 0x08a4, // XK_topintegral
+    0x2321: 0x08a5, // XK_botintegral
+    0x2395: 0x0bcc, // XK_quad
+    0x239b: 0x08ab, // XK_topleftparens
+    0x239d: 0x08ac, // XK_botleftparens
+    0x239e: 0x08ad, // XK_toprightparens
+    0x23a0: 0x08ae, // XK_botrightparens
+    0x23a1: 0x08a7, // XK_topleftsqbracket
+    0x23a3: 0x08a8, // XK_botleftsqbracket
+    0x23a4: 0x08a9, // XK_toprightsqbracket
+    0x23a6: 0x08aa, // XK_botrightsqbracket
+    0x23a8: 0x08af, // XK_leftmiddlecurlybrace
+    0x23ac: 0x08b0, // XK_rightmiddlecurlybrace
+    0x23b7: 0x08a1, // XK_leftradical
+    0x23ba: 0x09ef, // XK_horizlinescan1
+    0x23bb: 0x09f0, // XK_horizlinescan3
+    0x23bc: 0x09f2, // XK_horizlinescan7
+    0x23bd: 0x09f3, // XK_horizlinescan9
+    0x2409: 0x09e2, // XK_ht
+    0x240a: 0x09e5, // XK_lf
+    0x240b: 0x09e9, // XK_vt
+    0x240c: 0x09e3, // XK_ff
+    0x240d: 0x09e4, // XK_cr
+    0x2423: 0x0aac, // XK_signifblank
+    0x2424: 0x09e8, // XK_nl
+    0x2500: 0x08a3, // XK_horizconnector
+    0x2502: 0x08a6, // XK_vertconnector
+    0x250c: 0x08a2, // XK_topleftradical
+    0x2510: 0x09eb, // XK_uprightcorner
+    0x2514: 0x09ed, // XK_lowleftcorner
+    0x2518: 0x09ea, // XK_lowrightcorner
+    0x251c: 0x09f4, // XK_leftt
+    0x2524: 0x09f5, // XK_rightt
+    0x252c: 0x09f7, // XK_topt
+    0x2534: 0x09f6, // XK_bott
+    0x253c: 0x09ee, // XK_crossinglines
+    0x2592: 0x09e1, // XK_checkerboard
+    0x25aa: 0x0ae7, // XK_enfilledsqbullet
+    0x25ab: 0x0ae1, // XK_enopensquarebullet
+    0x25ac: 0x0adb, // XK_filledrectbullet
+    0x25ad: 0x0ae2, // XK_openrectbullet
+    0x25ae: 0x0adf, // XK_emfilledrect
+    0x25af: 0x0acf, // XK_emopenrectangle
+    0x25b2: 0x0ae8, // XK_filledtribulletup
+    0x25b3: 0x0ae3, // XK_opentribulletup
+    0x25b6: 0x0add, // XK_filledrighttribullet
+    0x25b7: 0x0acd, // XK_rightopentriangle
+    0x25bc: 0x0ae9, // XK_filledtribulletdown
+    0x25bd: 0x0ae4, // XK_opentribulletdown
+    0x25c0: 0x0adc, // XK_filledlefttribullet
+    0x25c1: 0x0acc, // XK_leftopentriangle
+    0x25c6: 0x09e0, // XK_soliddiamond
+    0x25cb: 0x0ace, // XK_emopencircle
+    0x25cf: 0x0ade, // XK_emfilledcircle
+    0x25e6: 0x0ae0, // XK_enopencircbullet
+    0x2606: 0x0ae5, // XK_openstar
+    0x260e: 0x0af9, // XK_telephone
+    0x2613: 0x0aca, // XK_signaturemark
+    0x261c: 0x0aea, // XK_leftpointer
+    0x261e: 0x0aeb, // XK_rightpointer
+    0x2640: 0x0af8, // XK_femalesymbol
+    0x2642: 0x0af7, // XK_malesymbol
+    0x2663: 0x0aec, // XK_club
+    0x2665: 0x0aee, // XK_heart
+    0x2666: 0x0aed, // XK_diamond
+    0x266d: 0x0af6, // XK_musicalflat
+    0x266f: 0x0af5, // XK_musicalsharp
+    0x2713: 0x0af3, // XK_checkmark
+    0x2717: 0x0af4, // XK_ballotcross
+    0x271d: 0x0ad9, // XK_latincross
+    0x2720: 0x0af0, // XK_maltesecross
+    0x27e8: 0x0abc, // XK_leftanglebracket
+    0x27e9: 0x0abe, // XK_rightanglebracket
+    0x3001: 0x04a4, // XK_kana_comma
+    0x3002: 0x04a1, // XK_kana_fullstop
+    0x300c: 0x04a2, // XK_kana_openingbracket
+    0x300d: 0x04a3, // XK_kana_closingbracket
+    0x309b: 0x04de, // XK_voicedsound
+    0x309c: 0x04df, // XK_semivoicedsound
+    0x30a1: 0x04a7, // XK_kana_a
+    0x30a2: 0x04b1, // XK_kana_A
+    0x30a3: 0x04a8, // XK_kana_i
+    0x30a4: 0x04b2, // XK_kana_I
+    0x30a5: 0x04a9, // XK_kana_u
+    0x30a6: 0x04b3, // XK_kana_U
+    0x30a7: 0x04aa, // XK_kana_e
+    0x30a8: 0x04b4, // XK_kana_E
+    0x30a9: 0x04ab, // XK_kana_o
+    0x30aa: 0x04b5, // XK_kana_O
+    0x30ab: 0x04b6, // XK_kana_KA
+    0x30ad: 0x04b7, // XK_kana_KI
+    0x30af: 0x04b8, // XK_kana_KU
+    0x30b1: 0x04b9, // XK_kana_KE
+    0x30b3: 0x04ba, // XK_kana_KO
+    0x30b5: 0x04bb, // XK_kana_SA
+    0x30b7: 0x04bc, // XK_kana_SHI
+    0x30b9: 0x04bd, // XK_kana_SU
+    0x30bb: 0x04be, // XK_kana_SE
+    0x30bd: 0x04bf, // XK_kana_SO
+    0x30bf: 0x04c0, // XK_kana_TA
+    0x30c1: 0x04c1, // XK_kana_CHI
+    0x30c3: 0x04af, // XK_kana_tsu
+    0x30c4: 0x04c2, // XK_kana_TSU
+    0x30c6: 0x04c3, // XK_kana_TE
+    0x30c8: 0x04c4, // XK_kana_TO
+    0x30ca: 0x04c5, // XK_kana_NA
+    0x30cb: 0x04c6, // XK_kana_NI
+    0x30cc: 0x04c7, // XK_kana_NU
+    0x30cd: 0x04c8, // XK_kana_NE
+    0x30ce: 0x04c9, // XK_kana_NO
+    0x30cf: 0x04ca, // XK_kana_HA
+    0x30d2: 0x04cb, // XK_kana_HI
+    0x30d5: 0x04cc, // XK_kana_FU
+    0x30d8: 0x04cd, // XK_kana_HE
+    0x30db: 0x04ce, // XK_kana_HO
+    0x30de: 0x04cf, // XK_kana_MA
+    0x30df: 0x04d0, // XK_kana_MI
+    0x30e0: 0x04d1, // XK_kana_MU
+    0x30e1: 0x04d2, // XK_kana_ME
+    0x30e2: 0x04d3, // XK_kana_MO
+    0x30e3: 0x04ac, // XK_kana_ya
+    0x30e4: 0x04d4, // XK_kana_YA
+    0x30e5: 0x04ad, // XK_kana_yu
+    0x30e6: 0x04d5, // XK_kana_YU
+    0x30e7: 0x04ae, // XK_kana_yo
+    0x30e8: 0x04d6, // XK_kana_YO
+    0x30e9: 0x04d7, // XK_kana_RA
+    0x30ea: 0x04d8, // XK_kana_RI
+    0x30eb: 0x04d9, // XK_kana_RU
+    0x30ec: 0x04da, // XK_kana_RE
+    0x30ed: 0x04db, // XK_kana_RO
+    0x30ef: 0x04dc, // XK_kana_WA
+    0x30f2: 0x04a6, // XK_kana_WO
+    0x30f3: 0x04dd, // XK_kana_N
+    0x30fb: 0x04a5, // XK_kana_conjunctive
+    0x30fc: 0x04b0, // XK_prolongedsound
+};
+
+export default {
+    lookup : function(u) {
+        // Latin-1 is one-to-one mapping
+        if ((u >= 0x20) && (u <= 0xff)) {
+            return u;
+        }
+
+        // Lookup table (fairly random)
+        var keysym = codepoints[u];
+        if (keysym !== undefined) {
+            return keysym;
+        }
+
+        // General mapping as final fallback
+        return 0x01000000 | u;
+    },
+};

+ 167 - 0
static/novnc/core/input/util.js

@@ -0,0 +1,167 @@
+import KeyTable from "./keysym.js";
+import keysyms from "./keysymdef.js";
+import vkeys from "./vkeys.js";
+import fixedkeys from "./fixedkeys.js";
+import DOMKeyTable from "./domkeytable.js";
+
+function isMac() {
+    return navigator && !!(/mac/i).exec(navigator.platform);
+}
+function isIE() {
+    return navigator && !!(/trident/i).exec(navigator.userAgent);
+}
+function isEdge() {
+    return navigator && !!(/edge/i).exec(navigator.userAgent);
+}
+
+// Get 'KeyboardEvent.code', handling legacy browsers
+export function getKeycode(evt){
+    // Are we getting proper key identifiers?
+    // (unfortunately Firefox and Chrome are crappy here and gives
+    // us an empty string on some platforms, rather than leaving it
+    // undefined)
+    if (evt.code) {
+        // Mozilla isn't fully in sync with the spec yet
+        switch (evt.code) {
+            case 'OSLeft': return 'MetaLeft';
+            case 'OSRight': return 'MetaRight';
+        }
+
+        return evt.code;
+    }
+
+    // The de-facto standard is to use Windows Virtual-Key codes
+    // in the 'keyCode' field for non-printable characters. However
+    // Webkit sets it to the same as charCode in 'keypress' events.
+    if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
+        var code = vkeys[evt.keyCode];
+
+        // macOS has messed up this code for some reason
+        if (isMac() && (code === 'ContextMenu')) {
+            code = 'MetaRight';
+        }
+
+        // The keyCode doesn't distinguish between left and right
+        // for the standard modifiers
+        if (evt.location === 2) {
+            switch (code) {
+                case 'ShiftLeft': return 'ShiftRight';
+                case 'ControlLeft': return 'ControlRight';
+                case 'AltLeft': return 'AltRight';
+            }
+        }
+
+        // Nor a bunch of the numpad keys
+        if (evt.location === 3) {
+            switch (code) {
+                case 'Delete': return 'NumpadDecimal';
+                case 'Insert': return 'Numpad0';
+                case 'End': return 'Numpad1';
+                case 'ArrowDown': return 'Numpad2';
+                case 'PageDown': return 'Numpad3';
+                case 'ArrowLeft': return 'Numpad4';
+                case 'ArrowRight': return 'Numpad6';
+                case 'Home': return 'Numpad7';
+                case 'ArrowUp': return 'Numpad8';
+                case 'PageUp': return 'Numpad9';
+                case 'Enter': return 'NumpadEnter';
+            }
+        }
+
+        return code;
+    }
+
+    return 'Unidentified';
+}
+
+// Get 'KeyboardEvent.key', handling legacy browsers
+export function getKey(evt) {
+    // Are we getting a proper key value?
+    if (evt.key !== undefined) {
+        // IE and Edge use some ancient version of the spec
+        // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
+        switch (evt.key) {
+            case 'Spacebar': return ' ';
+            case 'Esc': return 'Escape';
+            case 'Scroll': return 'ScrollLock';
+            case 'Win': return 'Meta';
+            case 'Apps': return 'ContextMenu';
+            case 'Up': return 'ArrowUp';
+            case 'Left': return 'ArrowLeft';
+            case 'Right': return 'ArrowRight';
+            case 'Down': return 'ArrowDown';
+            case 'Del': return 'Delete';
+            case 'Divide': return '/';
+            case 'Multiply': return '*';
+            case 'Subtract': return '-';
+            case 'Add': return '+';
+            case 'Decimal': return evt.char;
+        }
+
+        // Mozilla isn't fully in sync with the spec yet
+        switch (evt.key) {
+            case 'OS': return 'Meta';
+        }
+
+        // IE and Edge have broken handling of AltGraph so we cannot
+        // trust them for printable characters
+        if ((evt.key.length !== 1) || (!isIE() && !isEdge())) {
+            return evt.key;
+        }
+    }
+
+    // Try to deduce it based on the physical key
+    var code = getKeycode(evt);
+    if (code in fixedkeys) {
+        return fixedkeys[code];
+    }
+
+    // If that failed, then see if we have a printable character
+    if (evt.charCode) {
+        return String.fromCharCode(evt.charCode);
+    }
+
+    // At this point we have nothing left to go on
+    return 'Unidentified';
+}
+
+// Get the most reliable keysym value we can get from a key event
+export function getKeysym(evt){
+    var key = getKey(evt);
+
+    if (key === 'Unidentified') {
+        return null;
+    }
+
+    // First look up special keys
+    if (key in DOMKeyTable) {
+        var location = evt.location;
+
+        // Safari screws up location for the right cmd key
+        if ((key === 'Meta') && (location === 0)) {
+            location = 2;
+        }
+
+        if ((location === undefined) || (location > 3)) {
+            location = 0;
+        }
+
+        return DOMKeyTable[key][location];
+    }
+
+    // Now we need to look at the Unicode symbol instead
+
+    var codepoint;
+
+    // Special key? (FIXME: Should have been caught earlier)
+    if (key.length !== 1) {
+        return null;
+    }
+
+    codepoint = key.charCodeAt();
+    if (codepoint) {
+        return keysyms.lookup(codepoint);
+    }
+
+    return null;
+}

+ 116 - 0
static/novnc/core/input/vkeys.js

@@ -0,0 +1,116 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*
+ * Mapping between Microsoft® Windows® Virtual-Key codes and
+ * HTML key codes.
+ */
+
+export default {
+    0x08: 'Backspace',
+    0x09: 'Tab',
+    0x0a: 'NumpadClear',
+    0x0d: 'Enter',
+    0x10: 'ShiftLeft',
+    0x11: 'ControlLeft',
+    0x12: 'AltLeft',
+    0x13: 'Pause',
+    0x14: 'CapsLock',
+    0x15: 'Lang1',
+    0x19: 'Lang2',
+    0x1b: 'Escape',
+    0x1c: 'Convert',
+    0x1d: 'NonConvert',
+    0x20: 'Space',
+    0x21: 'PageUp',
+    0x22: 'PageDown',
+    0x23: 'End',
+    0x24: 'Home',
+    0x25: 'ArrowLeft',
+    0x26: 'ArrowUp',
+    0x27: 'ArrowRight',
+    0x28: 'ArrowDown',
+    0x29: 'Select',
+    0x2c: 'PrintScreen',
+    0x2d: 'Insert',
+    0x2e: 'Delete',
+    0x2f: 'Help',
+    0x30: 'Digit0',
+    0x31: 'Digit1',
+    0x32: 'Digit2',
+    0x33: 'Digit3',
+    0x34: 'Digit4',
+    0x35: 'Digit5',
+    0x36: 'Digit6',
+    0x37: 'Digit7',
+    0x38: 'Digit8',
+    0x39: 'Digit9',
+    0x5b: 'MetaLeft',
+    0x5c: 'MetaRight',
+    0x5d: 'ContextMenu',
+    0x5f: 'Sleep',
+    0x60: 'Numpad0',
+    0x61: 'Numpad1',
+    0x62: 'Numpad2',
+    0x63: 'Numpad3',
+    0x64: 'Numpad4',
+    0x65: 'Numpad5',
+    0x66: 'Numpad6',
+    0x67: 'Numpad7',
+    0x68: 'Numpad8',
+    0x69: 'Numpad9',
+    0x6a: 'NumpadMultiply',
+    0x6b: 'NumpadAdd',
+    0x6c: 'NumpadDecimal',
+    0x6d: 'NumpadSubtract',
+    0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows
+    0x6f: 'NumpadDivide',
+    0x70: 'F1',
+    0x71: 'F2',
+    0x72: 'F3',
+    0x73: 'F4',
+    0x74: 'F5',
+    0x75: 'F6',
+    0x76: 'F7',
+    0x77: 'F8',
+    0x78: 'F9',
+    0x79: 'F10',
+    0x7a: 'F11',
+    0x7b: 'F12',
+    0x7c: 'F13',
+    0x7d: 'F14',
+    0x7e: 'F15',
+    0x7f: 'F16',
+    0x80: 'F17',
+    0x81: 'F18',
+    0x82: 'F19',
+    0x83: 'F20',
+    0x84: 'F21',
+    0x85: 'F22',
+    0x86: 'F23',
+    0x87: 'F24',
+    0x90: 'NumLock',
+    0x91: 'ScrollLock',
+    0xa6: 'BrowserBack',
+    0xa7: 'BrowserForward',
+    0xa8: 'BrowserRefresh',
+    0xa9: 'BrowserStop',
+    0xaa: 'BrowserSearch',
+    0xab: 'BrowserFavorites',
+    0xac: 'BrowserHome',
+    0xad: 'AudioVolumeMute',
+    0xae: 'AudioVolumeDown',
+    0xaf: 'AudioVolumeUp',
+    0xb0: 'MediaTrackNext',
+    0xb1: 'MediaTrackPrevious',
+    0xb2: 'MediaStop',
+    0xb3: 'MediaPlayPause',
+    0xb4: 'LaunchMail',
+    0xb5: 'MediaSelect',
+    0xb6: 'LaunchApp1',
+    0xb7: 'LaunchApp2',
+    0xe1: 'AltRight', // Only when it is AltGraph
+};

+ 147 - 0
static/novnc/core/input/xtscancodes.js

@@ -0,0 +1,147 @@
+export default {
+    "Escape": 0x0001,
+    "Digit1": 0x0002,
+    "Digit2": 0x0003,
+    "Digit3": 0x0004,
+    "Digit4": 0x0005,
+    "Digit5": 0x0006,
+    "Digit6": 0x0007,
+    "Digit7": 0x0008,
+    "Digit8": 0x0009,
+    "Digit9": 0x000A,
+    "Digit0": 0x000B,
+    "Minus": 0x000C,
+    "Equal": 0x000D,
+    "Backspace": 0x000E,
+    "Tab": 0x000F,
+    "KeyQ": 0x0010,
+    "KeyW": 0x0011,
+    "KeyE": 0x0012,
+    "KeyR": 0x0013,
+    "KeyT": 0x0014,
+    "KeyY": 0x0015,
+    "KeyU": 0x0016,
+    "KeyI": 0x0017,
+    "KeyO": 0x0018,
+    "KeyP": 0x0019,
+    "BracketLeft": 0x001A,
+    "BracketRight": 0x001B,
+    "Enter": 0x001C,
+    "ControlLeft": 0x001D,
+    "KeyA": 0x001E,
+    "KeyS": 0x001F,
+    "KeyD": 0x0020,
+    "KeyF": 0x0021,
+    "KeyG": 0x0022,
+    "KeyH": 0x0023,
+    "KeyJ": 0x0024,
+    "KeyK": 0x0025,
+    "KeyL": 0x0026,
+    "Semicolon": 0x0027,
+    "Quote": 0x0028,
+    "Backquote": 0x0029,
+    "ShiftLeft": 0x002A,
+    "Backslash": 0x002B,
+    "KeyZ": 0x002C,
+    "KeyX": 0x002D,
+    "KeyC": 0x002E,
+    "KeyV": 0x002F,
+    "KeyB": 0x0030,
+    "KeyN": 0x0031,
+    "KeyM": 0x0032,
+    "Comma": 0x0033,
+    "Period": 0x0034,
+    "Slash": 0x0035,
+    "ShiftRight": 0x0036,
+    "NumpadMultiply": 0x0037,
+    "AltLeft": 0x0038,
+    "Space": 0x0039,
+    "CapsLock": 0x003A,
+    "F1": 0x003B,
+    "F2": 0x003C,
+    "F3": 0x003D,
+    "F4": 0x003E,
+    "F5": 0x003F,
+    "F6": 0x0040,
+    "F7": 0x0041,
+    "F8": 0x0042,
+    "F9": 0x0043,
+    "F10": 0x0044,
+    "Pause": 0xE045,
+    "ScrollLock": 0x0046,
+    "Numpad7": 0x0047,
+    "Numpad8": 0x0048,
+    "Numpad9": 0x0049,
+    "NumpadSubtract": 0x004A,
+    "Numpad4": 0x004B,
+    "Numpad5": 0x004C,
+    "Numpad6": 0x004D,
+    "NumpadAdd": 0x004E,
+    "Numpad1": 0x004F,
+    "Numpad2": 0x0050,
+    "Numpad3": 0x0051,
+    "Numpad0": 0x0052,
+    "NumpadDecimal": 0x0053,
+    "IntlBackslash": 0x0056,
+    "F11": 0x0057,
+    "F12": 0x0058,
+    "IntlYen": 0x007D,
+    "MediaTrackPrevious": 0xE010,
+    "MediaTrackNext": 0xE019,
+    "NumpadEnter": 0xE01C,
+    "ControlRight": 0xE01D,
+    "VolumeMute": 0xE020,
+    "MediaPlayPause": 0xE022,
+    "MediaStop": 0xE024,
+    "VolumeDown": 0xE02E,
+    "VolumeUp": 0xE030,
+    "BrowserHome": 0xE032,
+    "NumpadDivide": 0xE035,
+    "PrintScreen": 0xE037,
+    "AltRight": 0xE038,
+    "NumLock": 0x0045,
+    "Home": 0xE047,
+    "ArrowUp": 0xE048,
+    "PageUp": 0xE049,
+    "ArrowLeft": 0xE04B,
+    "ArrowRight": 0xE04D,
+    "End": 0xE04F,
+    "ArrowDown": 0xE050,
+    "PageDown": 0xE051,
+    "Insert": 0xE052,
+    "Delete": 0xE053,
+    "MetaLeft": 0xE05B,
+    "MetaRight": 0xE05C,
+    "ContextMenu": 0xE05D,
+    "BrowserSearch": 0xE065,
+    "BrowserFavorites": 0xE066,
+    "BrowserRefresh": 0xE067,
+    "BrowserStop": 0xE068,
+    "BrowserForward": 0xE069,
+    "BrowserBack": 0xE06A,
+    "NumpadComma": 0x007E,
+    "NumpadEqual": 0x0059,
+    "F13": 0x0064,
+    "F14": 0x0065,
+    "F15": 0x0066,
+    "F16": 0x0067,
+    "F17": 0x0068,
+    "F18": 0x0069,
+    "F19": 0x006A,
+    "F20": 0x006B,
+    "F21": 0x006C,
+    "F22": 0x006D,
+    "F23": 0x006E,
+    "F24": 0x0076,
+    "KanaMode": 0x0070,
+    "Lang2": 0x0071,
+    "Lang1": 0x0072,
+    "IntlRo": 0x0073,
+    "Convert": 0x0079,
+    "NonConvert": 0x007B,
+    "LaunchApp2": 0xE021,
+    "Power": 0xE05E,
+    "LaunchApp1": 0xE06B,
+    "LaunchMail": 0xE06C,
+    "MediaSelect": 0xE06D,
+};

+ 2420 - 0
static/novnc/core/rfb.js

@@ -0,0 +1,2420 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ * TIGHT decoder portion:
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
+ */
+
+import * as Log from './util/logging.js';
+import _ from './util/localization.js';
+import { decodeUTF8 } from './util/strings.js';
+import { set_defaults, make_properties } from './util/properties.js';
+import Display from "./display.js";
+import { Keyboard, Mouse } from "./input/devices.js";
+import Websock from "./websock.js";
+import Base64 from "./base64.js";
+import DES from "./des.js";
+import KeyTable from "./input/keysym.js";
+import XtScancode from "./input/xtscancodes.js";
+import Inflator from "./inflator.js";
+
+/*jslint white: false, browser: true */
+/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
+
+export default function RFB(defaults) {
+    "use strict";
+    if (!defaults) {
+        defaults = {};
+    }
+
+    this._rfb_host = '';
+    this._rfb_port = 5900;
+    this._rfb_password = '';
+    this._rfb_path = '';
+
+    this._rfb_connection_state = '';
+    this._rfb_init_state = '';
+    this._rfb_version = 0;
+    this._rfb_max_version = 3.8;
+    this._rfb_auth_scheme = '';
+    this._rfb_disconnect_reason = "";
+
+    this._rfb_tightvnc = false;
+    this._rfb_xvp_ver = 0;
+
+    // In preference order
+    this._encodings = [
+        ['COPYRECT',             0x01 ],
+        ['TIGHT',                0x07 ],
+        ['TIGHT_PNG',            -260 ],
+        ['HEXTILE',              0x05 ],
+        ['RRE',                  0x02 ],
+        ['RAW',                  0x00 ],
+
+        // Psuedo-encoding settings
+
+        //['JPEG_quality_lo',     -32 ],
+        ['JPEG_quality_med',      -26 ],
+        //['JPEG_quality_hi',     -23 ],
+        //['compress_lo',        -255 ],
+        ['compress_hi',          -254 ],
+        //['compress_max',       -247 ],
+
+        ['DesktopSize',          -223 ],
+        ['last_rect',            -224 ],
+        ['Cursor',               -239 ],
+        ['QEMUExtendedKeyEvent', -258 ],
+        ['ExtendedDesktopSize',  -308 ],
+        ['xvp',                  -309 ],
+        ['Fence',                -312 ],
+        ['ContinuousUpdates',    -313 ]
+    ];
+
+    this._encHandlers = {};
+    this._encNames = {};
+    this._encStats = {};
+
+    this._sock = null;              // Websock object
+    this._display = null;           // Display object
+    this._flushing = false;         // Display flushing state
+    this._keyboard = null;          // Keyboard input handler object
+    this._mouse = null;             // Mouse input handler object
+    this._disconnTimer = null;      // disconnection timer
+
+    this._supportsFence = false;
+
+    this._supportsContinuousUpdates = false;
+    this._enabledContinuousUpdates = false;
+
+    // Frame buffer update state
+    this._FBU = {
+        rects: 0,
+        subrects: 0,            // RRE
+        lines: 0,               // RAW
+        tiles: 0,               // HEXTILE
+        bytes: 0,
+        x: 0,
+        y: 0,
+        width: 0,
+        height: 0,
+        encoding: 0,
+        subencoding: -1,
+        background: null,
+        zlib: []                // TIGHT zlib streams
+    };
+
+    this._fb_width = 0;
+    this._fb_height = 0;
+    this._fb_name = "";
+
+    this._destBuff = null;
+    this._paletteBuff = new Uint8Array(1024);  // 256 * 4 (max palette size * max bytes-per-pixel)
+
+    this._rre_chunk_sz = 100;
+
+    this._timing = {
+        last_fbu: 0,
+        fbu_total: 0,
+        fbu_total_cnt: 0,
+        full_fbu_total: 0,
+        full_fbu_cnt: 0,
+
+        fbu_rt_start: 0,
+        fbu_rt_total: 0,
+        fbu_rt_cnt: 0,
+        pixels: 0
+    };
+
+    this._supportsSetDesktopSize = false;
+    this._screen_id = 0;
+    this._screen_flags = 0;
+
+    // Mouse state
+    this._mouse_buttonMask = 0;
+    this._mouse_arr = [];
+    this._viewportDragging = false;
+    this._viewportDragPos = {};
+    this._viewportHasMoved = false;
+
+    // QEMU Extended Key Event support - default to false
+    this._qemuExtKeyEventSupported = false;
+
+    // set the default value on user-facing properties
+    set_defaults(this, defaults, {
+        'target': 'null',                       // VNC display rendering Canvas object
+        'focusContainer': document,             // DOM element that captures keyboard input
+        'encrypt': false,                       // Use TLS/SSL/wss encryption
+        'local_cursor': false,                  // Request locally rendered cursor
+        'shared': true,                         // Request shared mode
+        'view_only': false,                     // Disable client mouse/keyboard
+        'xvp_password_sep': '@',                // Separator for XVP password fields
+        'disconnectTimeout': 3,                 // Time (s) to wait for disconnection
+        'wsProtocols': ['binary'],              // Protocols to use in the WebSocket connection
+        'repeaterID': '',                       // [UltraVNC] RepeaterID to connect to
+        'viewportDrag': false,                  // Move the viewport on mouse drags
+
+        // Callback functions
+        'onUpdateState': function () { },       // onUpdateState(rfb, state, oldstate): connection state change
+        'onNotification': function () { },      // onNotification(rfb, msg, level, options): notification for UI
+        'onDisconnected': function () { },      // onDisconnected(rfb, reason): disconnection finished
+        'onPasswordRequired': function () { },  // onPasswordRequired(rfb, msg): VNC password is required
+        'onClipboard': function () { },         // onClipboard(rfb, text): RFB clipboard contents received
+        'onBell': function () { },              // onBell(rfb): RFB Bell message received
+        'onFBUReceive': function () { },        // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
+        'onFBUComplete': function () { },       // onFBUComplete(rfb, fbu): RFB FBU received and processed
+        'onFBResize': function () { },          // onFBResize(rfb, width, height): frame buffer resized
+        'onDesktopName': function () { },       // onDesktopName(rfb, name): desktop name received
+        'onXvpInit': function () { }            // onXvpInit(version): XVP extensions active for this connection
+    });
+
+    // main setup
+    Log.Debug(">> RFB.constructor");
+
+    // populate encHandlers with bound versions
+    Object.keys(RFB.encodingHandlers).forEach(function (encName) {
+        this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
+    }.bind(this));
+
+    // Create lookup tables based on encoding number
+    for (var i = 0; i < this._encodings.length; i++) {
+        this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
+        this._encNames[this._encodings[i][1]] = this._encodings[i][0];
+        this._encStats[this._encodings[i][1]] = [0, 0];
+    }
+
+    // NB: nothing that needs explicit teardown should be done
+    // before this point, since this can throw an exception
+    try {
+        this._display = new Display({target: this._target,
+                                     onFlush: this._onFlush.bind(this)});
+    } catch (exc) {
+        Log.Error("Display exception: " + exc);
+        throw exc;
+    }
+
+    this._keyboard = new Keyboard({target: this._focusContainer,
+                                   onKeyEvent: this._handleKeyEvent.bind(this)});
+
+    this._mouse = new Mouse({target: this._target,
+                             onMouseButton: this._handleMouseButton.bind(this),
+                             onMouseMove: this._handleMouseMove.bind(this)});
+
+    this._sock = new Websock();
+    this._sock.on('message', this._handle_message.bind(this));
+    this._sock.on('open', function () {
+        if ((this._rfb_connection_state === 'connecting') &&
+            (this._rfb_init_state === '')) {
+            this._rfb_init_state = 'ProtocolVersion';
+            Log.Debug("Starting VNC handshake");
+        } else {
+            this._fail("Unexpected server connection");
+        }
+    }.bind(this));
+    this._sock.on('close', function (e) {
+        Log.Warn("WebSocket on-close event");
+        var msg = "";
+        if (e.code) {
+            msg = " (code: " + e.code;
+            if (e.reason) {
+                msg += ", reason: " + e.reason;
+            }
+            msg += ")";
+        }
+        switch (this._rfb_connection_state) {
+            case 'disconnecting':
+                this._updateConnectionState('disconnected');
+                break;
+            case 'connecting':
+                this._fail('Failed to connect to server', msg);
+                break;
+            case 'connected':
+                // Handle disconnects that were initiated server-side
+                this._updateConnectionState('disconnecting');
+                this._updateConnectionState('disconnected');
+                break;
+            case 'disconnected':
+                this._fail("Unexpected server disconnect",
+                           "Already disconnected: " + msg);
+                break;
+            default:
+                this._fail("Unexpected server disconnect",
+                           "Not in any state yet: " + msg);
+                break;
+        }
+        this._sock.off('close');
+    }.bind(this));
+    this._sock.on('error', function (e) {
+        Log.Warn("WebSocket on-error event");
+    });
+
+    this._init_vars();
+    this._cleanup();
+
+    var rmode = this._display.get_render_mode();
+    Log.Info("Using native WebSockets, render mode: " + rmode);
+
+    Log.Debug("<< RFB.constructor");
+};
+
+RFB.prototype = {
+    // Public methods
+    connect: function (host, port, password, path) {
+        this._rfb_host = host;
+        this._rfb_port = port;
+        this._rfb_password = (password !== undefined) ? password : "";
+        this._rfb_path = (path !== undefined) ? path : "";
+
+        if (!this._rfb_host || !this._rfb_port) {
+            return this._fail(
+                _("Must set host and port"));
+        }
+
+        this._rfb_init_state = '';
+        this._updateConnectionState('connecting');
+        return true;
+    },
+
+    disconnect: function () {
+        this._updateConnectionState('disconnecting');
+        this._sock.off('error');
+        this._sock.off('message');
+        this._sock.off('open');
+    },
+
+    sendPassword: function (passwd) {
+        this._rfb_password = passwd;
+        setTimeout(this._init_msg.bind(this), 0);
+    },
+
+    sendCtrlAltDel: function () {
+        if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
+        Log.Info("Sending Ctrl-Alt-Del");
+
+        this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+        this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+        this.sendKey(KeyTable.XK_Delete, "Delete", true);
+        this.sendKey(KeyTable.XK_Delete, "Delete", false);
+        this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+        this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+
+        return true;
+    },
+
+    xvpOp: function (ver, op) {
+        if (this._rfb_xvp_ver < ver) { return false; }
+        Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
+        this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
+        return true;
+    },
+
+    xvpShutdown: function () {
+        return this.xvpOp(1, 2);
+    },
+
+    xvpReboot: function () {
+        return this.xvpOp(1, 3);
+    },
+
+    xvpReset: function () {
+        return this.xvpOp(1, 4);
+    },
+
+    // Send a key press. If 'down' is not specified then send a down key
+    // followed by an up key.
+    sendKey: function (keysym, code, down) {
+        if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
+
+        if (down === undefined) {
+            this.sendKey(keysym, code, true);
+            this.sendKey(keysym, code, false);
+            return true;
+        }
+
+        if (this._qemuExtKeyEventSupported) {
+            var scancode = XtScancode[code];
+
+            if (scancode === undefined) {
+                Log.Error('Unable to find a xt scancode for code: ' + code);
+                // FIXME: not in the spec, but this is what
+                // gtk-vnc does
+                scancode = 0;
+            }
+
+            Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
+
+            RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
+        } else {
+            Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
+            RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
+        }
+
+        return true;
+    },
+
+    clipboardPasteFrom: function (text) {
+        if (this._rfb_connection_state !== 'connected' || this._view_only) { return; }
+        RFB.messages.clientCutText(this._sock, text);
+    },
+
+    // Requests a change of remote desktop size. This message is an extension
+    // and may only be sent if we have received an ExtendedDesktopSize message
+    requestDesktopSize: function (width, height) {
+        if (this._rfb_connection_state !== 'connected' ||
+            this._view_only) {
+            return false;
+        }
+
+        if (this._supportsSetDesktopSize) {
+            RFB.messages.setDesktopSize(this._sock, width, height,
+                                        this._screen_id, this._screen_flags);
+            this._sock.flush();
+            return true;
+        } else {
+            return false;
+        }
+    },
+
+
+    // Private methods
+
+    _connect: function () {
+        Log.Debug(">> RFB.connect");
+        this._init_vars();
+
+        var uri;
+        if (typeof UsingSocketIO !== 'undefined') {
+            uri = 'http';
+        } else {
+            uri = this._encrypt ? 'wss' : 'ws';
+        }
+
+        uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
+        Log.Info("connecting to " + uri);
+
+        try {
+            // WebSocket.onopen transitions to the RFB init states
+            this._sock.open(uri, this._wsProtocols);
+        } catch (e) {
+            if (e.name === 'SyntaxError') {
+                this._fail("Invalid host or port value given", e);
+            } else {
+                this._fail("Error while connecting", e);
+            }
+        }
+
+        Log.Debug("<< RFB.connect");
+    },
+
+    _disconnect: function () {
+        Log.Debug(">> RFB.disconnect");
+        this._cleanup();
+        this._sock.close();
+        this._print_stats();
+        Log.Debug("<< RFB.disconnect");
+    },
+
+    _init_vars: function () {
+        // reset state
+        this._FBU.rects        = 0;
+        this._FBU.subrects     = 0;  // RRE and HEXTILE
+        this._FBU.lines        = 0;  // RAW
+        this._FBU.tiles        = 0;  // HEXTILE
+        this._FBU.zlibs        = []; // TIGHT zlib encoders
+        this._mouse_buttonMask = 0;
+        this._mouse_arr        = [];
+        this._rfb_tightvnc     = false;
+
+        // Clear the per connection encoding stats
+        var i;
+        for (i = 0; i < this._encodings.length; i++) {
+            this._encStats[this._encodings[i][1]][0] = 0;
+        }
+
+        for (i = 0; i < 4; i++) {
+            this._FBU.zlibs[i] = new Inflator();
+        }
+    },
+
+    _print_stats: function () {
+        Log.Info("Encoding stats for this connection:");
+        var i, s;
+        for (i = 0; i < this._encodings.length; i++) {
+            s = this._encStats[this._encodings[i][1]];
+            if (s[0] + s[1] > 0) {
+                Log.Info("    " + this._encodings[i][0] + ": " + s[0] + " rects");
+            }
+        }
+
+        Log.Info("Encoding stats since page load:");
+        for (i = 0; i < this._encodings.length; i++) {
+            s = this._encStats[this._encodings[i][1]];
+            Log.Info("    " + this._encodings[i][0] + ": " + s[1] + " rects");
+        }
+    },
+
+    _cleanup: function () {
+        if (!this._view_only) { this._keyboard.ungrab(); }
+        if (!this._view_only) { this._mouse.ungrab(); }
+        this._display.defaultCursor();
+        if (Log.get_logging() !== 'debug') {
+            // Show noVNC logo on load and when disconnected, unless in
+            // debug mode
+            this._display.clear();
+        }
+    },
+
+    /*
+     * Connection states:
+     *   connecting
+     *   connected
+     *   disconnecting
+     *   disconnected - permanent state
+     */
+    _updateConnectionState: function (state) {
+        var oldstate = this._rfb_connection_state;
+
+        if (state === oldstate) {
+            Log.Debug("Already in state '" + state + "', ignoring");
+            return;
+        }
+
+        // The 'disconnected' state is permanent for each RFB object
+        if (oldstate === 'disconnected') {
+            Log.Error("Tried changing state of a disconnected RFB object");
+            return;
+        }
+
+        // Ensure proper transitions before doing anything
+        switch (state) {
+            case 'connected':
+                if (oldstate !== 'connecting') {
+                    Log.Error("Bad transition to connected state, " +
+                               "previous connection state: " + oldstate);
+                    return;
+                }
+                break;
+
+            case 'disconnected':
+                if (oldstate !== 'disconnecting') {
+                    Log.Error("Bad transition to disconnected state, " +
+                               "previous connection state: " + oldstate);
+                    return;
+                }
+                break;
+
+            case 'connecting':
+                if (oldstate !== '') {
+                    Log.Error("Bad transition to connecting state, " +
+                               "previous connection state: " + oldstate);
+                    return;
+                }
+                break;
+
+            case 'disconnecting':
+                if (oldstate !== 'connected' && oldstate !== 'connecting') {
+                    Log.Error("Bad transition to disconnecting state, " +
+                               "previous connection state: " + oldstate);
+                    return;
+                }
+                break;
+
+            default:
+                Log.Error("Unknown connection state: " + state);
+                return;
+        }
+
+        // State change actions
+
+        this._rfb_connection_state = state;
+        this._onUpdateState(this, state, oldstate);
+
+        var smsg = "New state '" + state + "', was '" + oldstate + "'.";
+        Log.Debug(smsg);
+
+        if (this._disconnTimer && state !== 'disconnecting') {
+            Log.Debug("Clearing disconnect timer");
+            clearTimeout(this._disconnTimer);
+            this._disconnTimer = null;
+
+            // make sure we don't get a double event
+            this._sock.off('close');
+        }
+
+        switch (state) {
+            case 'disconnected':
+                // Call onDisconnected callback after onUpdateState since
+                // we don't know if the UI only displays the latest message
+                if (this._rfb_disconnect_reason !== "") {
+                    this._onDisconnected(this, this._rfb_disconnect_reason);
+                } else {
+                    // No reason means clean disconnect
+                    this._onDisconnected(this);
+                }
+                break;
+
+            case 'connecting':
+                this._connect();
+                break;
+
+            case 'disconnecting':
+                this._disconnect();
+
+                this._disconnTimer = setTimeout(function () {
+                    this._rfb_disconnect_reason = _("Disconnect timeout");
+                    this._updateConnectionState('disconnected');
+                }.bind(this), this._disconnectTimeout * 1000);
+                break;
+        }
+    },
+
+    /* Print errors and disconnect
+     *
+     * The optional parameter 'details' is used for information that
+     * should be logged but not sent to the user interface.
+     */
+    _fail: function (msg, details) {
+        var fullmsg = msg;
+        if (typeof details !== 'undefined') {
+            fullmsg = msg + " (" + details + ")";
+        }
+        switch (this._rfb_connection_state) {
+            case 'disconnecting':
+                Log.Error("Failed when disconnecting: " + fullmsg);
+                break;
+            case 'connected':
+                Log.Error("Failed while connected: " + fullmsg);
+                break;
+            case 'connecting':
+                Log.Error("Failed when connecting: " + fullmsg);
+                break;
+            default:
+                Log.Error("RFB failure: " + fullmsg);
+                break;
+        }
+        this._rfb_disconnect_reason = msg; //This is sent to the UI
+
+        // Transition to disconnected without waiting for socket to close
+        this._updateConnectionState('disconnecting');
+        this._updateConnectionState('disconnected');
+
+        return false;
+    },
+
+    /*
+     * Send a notification to the UI. Valid levels are:
+     *   'normal'|'warn'|'error'
+     *
+     *   NOTE: Options could be added in the future.
+     *   NOTE: If this function is called multiple times, remember that the
+     *         interface could be only showing the latest notification.
+     */
+    _notification: function(msg, level, options) {
+        switch (level) {
+            case 'normal':
+            case 'warn':
+            case 'error':
+                Log.Debug("Notification[" + level + "]:" + msg);
+                break;
+            default:
+                Log.Error("Invalid notification level: " + level);
+                return;
+        }
+
+        if (options) {
+            this._onNotification(this, msg, level, options);
+        } else {
+            this._onNotification(this, msg, level);
+        }
+    },
+
+    _handle_message: function () {
+        if (this._sock.rQlen() === 0) {
+            Log.Warn("handle_message called on an empty receive queue");
+            return;
+        }
+
+        switch (this._rfb_connection_state) {
+            case 'disconnected':
+                Log.Error("Got data while disconnected");
+                break;
+            case 'connected':
+                while (true) {
+                    if (this._flushing) {
+                        break;
+                    }
+                    if (!this._normal_msg()) {
+                        break;
+                    }
+                    if (this._sock.rQlen() === 0) {
+                        break;
+                    }
+                }
+                break;
+            default:
+                this._init_msg();
+                break;
+        }
+    },
+
+    _handleKeyEvent: function (keysym, code, down) {
+        this.sendKey(keysym, code, down);
+    },
+
+    _handleMouseButton: function (x, y, down, bmask) {
+        if (down) {
+            this._mouse_buttonMask |= bmask;
+        } else {
+            this._mouse_buttonMask &= ~bmask;
+        }
+
+        if (this._viewportDrag) {
+            if (down && !this._viewportDragging) {
+                this._viewportDragging = true;
+                this._viewportDragPos = {'x': x, 'y': y};
+
+                // Skip sending mouse events
+                return;
+            } else {
+                this._viewportDragging = false;
+
+                // If the viewport didn't actually move, then treat as a mouse click event
+                // Send the button down event here, as the button up event is sent at the end of this function
+                if (!this._viewportHasMoved && !this._view_only) {
+                    RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask);
+                }
+                this._viewportHasMoved = false;
+            }
+        }
+
+        if (this._view_only) { return; } // View only, skip mouse events
+
+        if (this._rfb_connection_state !== 'connected') { return; }
+        RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+    },
+
+    _handleMouseMove: function (x, y) {
+        if (this._viewportDragging) {
+            var deltaX = this._viewportDragPos.x - x;
+            var deltaY = this._viewportDragPos.y - y;
+
+            // The goal is to trigger on a certain physical width, the
+            // devicePixelRatio brings us a bit closer but is not optimal.
+            var dragThreshold = 10 * (window.devicePixelRatio || 1);
+
+            if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
+                                           Math.abs(deltaY) > dragThreshold)) {
+                this._viewportHasMoved = true;
+
+                this._viewportDragPos = {'x': x, 'y': y};
+                this._display.viewportChangePos(deltaX, deltaY);
+            }
+
+            // Skip sending mouse events
+            return;
+        }
+
+        if (this._view_only) { return; } // View only, skip mouse events
+
+        if (this._rfb_connection_state !== 'connected') { return; }
+        RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+    },
+
+    // Message Handlers
+
+    _negotiate_protocol_version: function () {
+        if (this._sock.rQlen() < 12) {
+            return this._fail("Error while negotiating with server",
+                              "Incomplete protocol version");
+        }
+
+        var sversion = this._sock.rQshiftStr(12).substr(4, 7);
+        Log.Info("Server ProtocolVersion: " + sversion);
+        var is_repeater = 0;
+        switch (sversion) {
+            case "000.000":  // UltraVNC repeater
+                is_repeater = 1;
+                break;
+            case "003.003":
+            case "003.006":  // UltraVNC
+            case "003.889":  // Apple Remote Desktop
+                this._rfb_version = 3.3;
+                break;
+            case "003.007":
+                this._rfb_version = 3.7;
+                break;
+            case "003.008":
+            case "004.000":  // Intel AMT KVM
+            case "004.001":  // RealVNC 4.6
+            case "005.000":  // RealVNC 5.3
+                this._rfb_version = 3.8;
+                break;
+            default:
+                return this._fail("Unsupported server",
+                                  "Invalid server version: " + sversion);
+        }
+
+        if (is_repeater) {
+            var repeaterID = this._repeaterID;
+            while (repeaterID.length < 250) {
+                repeaterID += "\0";
+            }
+            this._sock.send_string(repeaterID);
+            return true;
+        }
+
+        if (this._rfb_version > this._rfb_max_version) {
+            this._rfb_version = this._rfb_max_version;
+        }
+
+        var cversion = "00" + parseInt(this._rfb_version, 10) +
+                       ".00" + ((this._rfb_version * 10) % 10);
+        this._sock.send_string("RFB " + cversion + "\n");
+        Log.Debug('Sent ProtocolVersion: ' + cversion);
+
+        this._rfb_init_state = 'Security';
+    },
+
+    _negotiate_security: function () {
+        // Polyfill since IE and PhantomJS doesn't have
+        // TypedArray.includes()
+        function includes(item, array) {
+            for (var i = 0; i < array.length; i++) {
+                if (array[i] === item) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        if (this._rfb_version >= 3.7) {
+            // Server sends supported list, client decides
+            var num_types = this._sock.rQshift8();
+            if (this._sock.rQwait("security type", num_types, 1)) { return false; }
+
+            if (num_types === 0) {
+                var strlen = this._sock.rQshift32();
+                var reason = this._sock.rQshiftStr(strlen);
+                return this._fail("Error while negotiating with server",
+                                  "Security failure: " + reason);
+            }
+
+            var types = this._sock.rQshiftBytes(num_types);
+            Log.Debug("Server security types: " + types);
+
+            // Look for each auth in preferred order
+            this._rfb_auth_scheme = 0;
+            if (includes(1, types)) {
+                this._rfb_auth_scheme = 1; // None
+            } else if (includes(22, types)) {
+                this._rfb_auth_scheme = 22; // XVP
+            } else if (includes(16, types)) {
+                this._rfb_auth_scheme = 16; // Tight
+            } else if (includes(2, types)) {
+                this._rfb_auth_scheme = 2; // VNC Auth
+            } else {
+                return this._fail("Unsupported server",
+                                  "Unsupported security types: " + types);
+            }
+
+            this._sock.send([this._rfb_auth_scheme]);
+        } else {
+            // Server decides
+            if (this._sock.rQwait("security scheme", 4)) { return false; }
+            this._rfb_auth_scheme = this._sock.rQshift32();
+        }
+
+        this._rfb_init_state = 'Authentication';
+        Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
+
+        return this._init_msg(); // jump to authentication
+    },
+
+    // authentication
+    _negotiate_xvp_auth: function () {
+        var xvp_sep = this._xvp_password_sep;
+        var xvp_auth = this._rfb_password.split(xvp_sep);
+        if (xvp_auth.length < 3) {
+            var msg = 'XVP credentials required (user' + xvp_sep +
+                'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
+            this._onPasswordRequired(this, msg);
+            return false;
+        }
+
+        var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
+                           String.fromCharCode(xvp_auth[1].length) +
+                           xvp_auth[0] +
+                           xvp_auth[1];
+        this._sock.send_string(xvp_auth_str);
+        this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
+        this._rfb_auth_scheme = 2;
+        return this._negotiate_authentication();
+    },
+
+    _negotiate_std_vnc_auth: function () {
+        if (this._rfb_password.length === 0) {
+            this._onPasswordRequired(this);
+            return false;
+        }
+
+        if (this._sock.rQwait("auth challenge", 16)) { return false; }
+
+        // TODO(directxman12): make genDES not require an Array
+        var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
+        var response = RFB.genDES(this._rfb_password, challenge);
+        this._sock.send(response);
+        this._rfb_init_state = "SecurityResult";
+        return true;
+    },
+
+    _negotiate_tight_tunnels: function (numTunnels) {
+        var clientSupportedTunnelTypes = {
+            0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
+        };
+        var serverSupportedTunnelTypes = {};
+        // receive tunnel capabilities
+        for (var i = 0; i < numTunnels; i++) {
+            var cap_code = this._sock.rQshift32();
+            var cap_vendor = this._sock.rQshiftStr(4);
+            var cap_signature = this._sock.rQshiftStr(8);
+            serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
+        }
+
+        // choose the notunnel type
+        if (serverSupportedTunnelTypes[0]) {
+            if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
+                serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
+                return this._fail("Unsupported server",
+                                  "Client's tunnel type had the incorrect " +
+                                  "vendor or signature");
+            }
+            this._sock.send([0, 0, 0, 0]);  // use NOTUNNEL
+            return false; // wait until we receive the sub auth count to continue
+        } else {
+            return this._fail("Unsupported server",
+                              "Server wanted tunnels, but doesn't support " +
+                              "the notunnel type");
+        }
+    },
+
+    _negotiate_tight_auth: function () {
+        if (!this._rfb_tightvnc) {  // first pass, do the tunnel negotiation
+            if (this._sock.rQwait("num tunnels", 4)) { return false; }
+            var numTunnels = this._sock.rQshift32();
+            if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
+
+            this._rfb_tightvnc = true;
+
+            if (numTunnels > 0) {
+                this._negotiate_tight_tunnels(numTunnels);
+                return false;  // wait until we receive the sub auth to continue
+            }
+        }
+
+        // second pass, do the sub-auth negotiation
+        if (this._sock.rQwait("sub auth count", 4)) { return false; }
+        var subAuthCount = this._sock.rQshift32();
+        if (subAuthCount === 0) {  // empty sub-auth list received means 'no auth' subtype selected
+            this._rfb_init_state = 'SecurityResult';
+            return true;
+        }
+
+        if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
+
+        var clientSupportedTypes = {
+            'STDVNOAUTH__': 1,
+            'STDVVNCAUTH_': 2
+        };
+
+        var serverSupportedTypes = [];
+
+        for (var i = 0; i < subAuthCount; i++) {
+            var capNum = this._sock.rQshift32();
+            var capabilities = this._sock.rQshiftStr(12);
+            serverSupportedTypes.push(capabilities);
+        }
+
+        for (var authType in clientSupportedTypes) {
+            if (serverSupportedTypes.indexOf(authType) != -1) {
+                this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
+
+                switch (authType) {
+                    case 'STDVNOAUTH__':  // no auth
+                        this._rfb_init_state = 'SecurityResult';
+                        return true;
+                    case 'STDVVNCAUTH_': // VNC auth
+                        this._rfb_auth_scheme = 2;
+                        return this._init_msg();
+                    default:
+                        return this._fail("Unsupported server",
+                                          "Unsupported tiny auth scheme: " +
+                                          authType);
+                }
+            }
+        }
+
+        return this._fail("Unsupported server",
+                          "No supported sub-auth types!");
+    },
+
+    _negotiate_authentication: function () {
+        switch (this._rfb_auth_scheme) {
+            case 0:  // connection failed
+                if (this._sock.rQwait("auth reason", 4)) { return false; }
+                var strlen = this._sock.rQshift32();
+                var reason = this._sock.rQshiftStr(strlen);
+                return this._fail("Authentication failure", reason);
+
+            case 1:  // no auth
+                if (this._rfb_version >= 3.8) {
+                    this._rfb_init_state = 'SecurityResult';
+                    return true;
+                }
+                this._rfb_init_state = 'ClientInitialisation';
+                return this._init_msg();
+
+            case 22:  // XVP auth
+                return this._negotiate_xvp_auth();
+
+            case 2:  // VNC authentication
+                return this._negotiate_std_vnc_auth();
+
+            case 16:  // TightVNC Security Type
+                return this._negotiate_tight_auth();
+
+            default:
+                return this._fail("Unsupported server",
+                                  "Unsupported auth scheme: " +
+                                  this._rfb_auth_scheme);
+        }
+    },
+
+    _handle_security_result: function () {
+        if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
+        switch (this._sock.rQshift32()) {
+            case 0:  // OK
+                this._rfb_init_state = 'ClientInitialisation';
+                Log.Debug('Authentication OK');
+                return this._init_msg();
+            case 1:  // failed
+                if (this._rfb_version >= 3.8) {
+                    var length = this._sock.rQshift32();
+                    if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
+                    var reason = this._sock.rQshiftStr(length);
+                    return this._fail("Authentication failure", reason);
+                } else {
+                    return this._fail("Authentication failure");
+                }
+                return false;
+            case 2:
+                return this._fail("Too many authentication attempts");
+            default:
+                return this._fail("Unsupported server",
+                                  "Unknown SecurityResult");
+        }
+    },
+
+    _negotiate_server_init: function () {
+        if (this._sock.rQwait("server initialization", 24)) { return false; }
+
+        /* Screen size */
+        this._fb_width  = this._sock.rQshift16();
+        this._fb_height = this._sock.rQshift16();
+        this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
+
+        /* PIXEL_FORMAT */
+        var bpp         = this._sock.rQshift8();
+        var depth       = this._sock.rQshift8();
+        var big_endian  = this._sock.rQshift8();
+        var true_color  = this._sock.rQshift8();
+
+        var red_max     = this._sock.rQshift16();
+        var green_max   = this._sock.rQshift16();
+        var blue_max    = this._sock.rQshift16();
+        var red_shift   = this._sock.rQshift8();
+        var green_shift = this._sock.rQshift8();
+        var blue_shift  = this._sock.rQshift8();
+        this._sock.rQskipBytes(3);  // padding
+
+        // NB(directxman12): we don't want to call any callbacks or print messages until
+        //                   *after* we're past the point where we could backtrack
+
+        /* Connection name/title */
+        var name_length = this._sock.rQshift32();
+        if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
+        this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
+
+        if (this._rfb_tightvnc) {
+            if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
+            // In TightVNC mode, ServerInit message is extended
+            var numServerMessages = this._sock.rQshift16();
+            var numClientMessages = this._sock.rQshift16();
+            var numEncodings = this._sock.rQshift16();
+            this._sock.rQskipBytes(2);  // padding
+
+            var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
+            if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
+
+            // we don't actually do anything with the capability information that TIGHT sends,
+            // so we just skip the all of this.
+
+            // TIGHT server message capabilities
+            this._sock.rQskipBytes(16 * numServerMessages);
+
+            // TIGHT client message capabilities
+            this._sock.rQskipBytes(16 * numClientMessages);
+
+            // TIGHT encoding capabilities
+            this._sock.rQskipBytes(16 * numEncodings);
+        }
+
+        // NB(directxman12): these are down here so that we don't run them multiple times
+        //                   if we backtrack
+        Log.Info("Screen: " + this._fb_width + "x" + this._fb_height +
+                  ", bpp: " + bpp + ", depth: " + depth +
+                  ", big_endian: " + big_endian +
+                  ", true_color: " + true_color +
+                  ", red_max: " + red_max +
+                  ", green_max: " + green_max +
+                  ", blue_max: " + blue_max +
+                  ", red_shift: " + red_shift +
+                  ", green_shift: " + green_shift +
+                  ", blue_shift: " + blue_shift);
+
+        if (big_endian !== 0) {
+            Log.Warn("Server native endian is not little endian");
+        }
+
+        if (red_shift !== 16) {
+            Log.Warn("Server native red-shift is not 16");
+        }
+
+        if (blue_shift !== 0) {
+            Log.Warn("Server native blue-shift is not 0");
+        }
+
+        // we're past the point where we could backtrack, so it's safe to call this
+        this._onDesktopName(this, this._fb_name);
+
+        this._display.resize(this._fb_width, this._fb_height);
+        this._onFBResize(this, this._fb_width, this._fb_height);
+
+        if (!this._view_only) { this._keyboard.grab(); }
+        if (!this._view_only) { this._mouse.grab(); }
+
+        RFB.messages.pixelFormat(this._sock, 4, 3, true);
+        RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor);
+        RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
+
+        this._timing.fbu_rt_start = (new Date()).getTime();
+        this._timing.pixels = 0;
+
+        this._updateConnectionState('connected');
+        return true;
+    },
+
+    /* RFB protocol initialization states:
+     *   ProtocolVersion
+     *   Security
+     *   Authentication
+     *   SecurityResult
+     *   ClientInitialization - not triggered by server message
+     *   ServerInitialization
+     */
+    _init_msg: function () {
+        switch (this._rfb_init_state) {
+            case 'ProtocolVersion':
+                return this._negotiate_protocol_version();
+
+            case 'Security':
+                return this._negotiate_security();
+
+            case 'Authentication':
+                return this._negotiate_authentication();
+
+            case 'SecurityResult':
+                return this._handle_security_result();
+
+            case 'ClientInitialisation':
+                this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
+                this._rfb_init_state = 'ServerInitialisation';
+                return true;
+
+            case 'ServerInitialisation':
+                return this._negotiate_server_init();
+
+            default:
+                return this._fail("Internal error", "Unknown init state: " +
+                                  this._rfb_init_state);
+        }
+    },
+
+    _handle_set_colour_map_msg: function () {
+        Log.Debug("SetColorMapEntries");
+
+        return this._fail("Protocol error", "Unexpected SetColorMapEntries message");
+    },
+
+    _handle_server_cut_text: function () {
+        Log.Debug("ServerCutText");
+
+        if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
+        this._sock.rQskipBytes(3);  // Padding
+        var length = this._sock.rQshift32();
+        if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
+
+        var text = this._sock.rQshiftStr(length);
+
+        if (this._view_only) { return true; }
+
+        this._onClipboard(this, text);
+
+        return true;
+    },
+
+    _handle_server_fence_msg: function() {
+        if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
+        this._sock.rQskipBytes(3); // Padding
+        var flags = this._sock.rQshift32();
+        var length = this._sock.rQshift8();
+
+        if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
+
+        if (length > 64) {
+            Log.Warn("Bad payload length (" + length + ") in fence response");
+            length = 64;
+        }
+
+        var payload = this._sock.rQshiftStr(length);
+
+        this._supportsFence = true;
+
+        /*
+         * Fence flags
+         *
+         *  (1<<0)  - BlockBefore
+         *  (1<<1)  - BlockAfter
+         *  (1<<2)  - SyncNext
+         *  (1<<31) - Request
+         */
+
+        if (!(flags & (1<<31))) {
+            return this._fail("Internal error",
+                              "Unexpected fence response");
+        }
+
+        // Filter out unsupported flags
+        // FIXME: support syncNext
+        flags &= (1<<0) | (1<<1);
+
+        // BlockBefore and BlockAfter are automatically handled by
+        // the fact that we process each incoming message
+        // synchronuosly.
+        RFB.messages.clientFence(this._sock, flags, payload);
+
+        return true;
+    },
+
+    _handle_xvp_msg: function () {
+        if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
+        this._sock.rQskip8();  // Padding
+        var xvp_ver = this._sock.rQshift8();
+        var xvp_msg = this._sock.rQshift8();
+
+        switch (xvp_msg) {
+            case 0:  // XVP_FAIL
+                Log.Error("Operation Failed");
+                this._notification("XVP Operation Failed", 'error');
+                break;
+            case 1:  // XVP_INIT
+                this._rfb_xvp_ver = xvp_ver;
+                Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
+                this._onXvpInit(this._rfb_xvp_ver);
+                break;
+            default:
+                this._fail("Unexpected server message",
+                           "Illegal server XVP message " + xvp_msg);
+                break;
+        }
+
+        return true;
+    },
+
+    _normal_msg: function () {
+        var msg_type;
+
+        if (this._FBU.rects > 0) {
+            msg_type = 0;
+        } else {
+            msg_type = this._sock.rQshift8();
+        }
+
+        switch (msg_type) {
+            case 0:  // FramebufferUpdate
+                var ret = this._framebufferUpdate();
+                if (ret && !this._enabledContinuousUpdates) {
+                    RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
+                                                 this._fb_width, this._fb_height);
+                }
+                return ret;
+
+            case 1:  // SetColorMapEntries
+                return this._handle_set_colour_map_msg();
+
+            case 2:  // Bell
+                Log.Debug("Bell");
+                this._onBell(this);
+                return true;
+
+            case 3:  // ServerCutText
+                return this._handle_server_cut_text();
+
+            case 150: // EndOfContinuousUpdates
+                var first = !(this._supportsContinuousUpdates);
+                this._supportsContinuousUpdates = true;
+                this._enabledContinuousUpdates = false;
+                if (first) {
+                    this._enabledContinuousUpdates = true;
+                    this._updateContinuousUpdates();
+                    Log.Info("Enabling continuous updates.");
+                } else {
+                    // FIXME: We need to send a framebufferupdaterequest here
+                    // if we add support for turning off continuous updates
+                }
+                return true;
+
+            case 248: // ServerFence
+                return this._handle_server_fence_msg();
+
+            case 250:  // XVP
+                return this._handle_xvp_msg();
+
+            default:
+                this._fail("Unexpected server message", "Type:" + msg_type);
+                Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
+                return true;
+        }
+    },
+
+    _onFlush: function() {
+        this._flushing = false;
+        // Resume processing
+        if (this._sock.rQlen() > 0) {
+            this._handle_message();
+        }
+    },
+
+    _framebufferUpdate: function () {
+        var ret = true;
+        var now;
+
+        if (this._FBU.rects === 0) {
+            if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
+            this._sock.rQskip8();  // Padding
+            this._FBU.rects = this._sock.rQshift16();
+            this._FBU.bytes = 0;
+            this._timing.cur_fbu = 0;
+            if (this._timing.fbu_rt_start > 0) {
+                now = (new Date()).getTime();
+                Log.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
+            }
+
+            // Make sure the previous frame is fully rendered first
+            // to avoid building up an excessive queue
+            if (this._display.pending()) {
+                this._flushing = true;
+                this._display.flush();
+                return false;
+            }
+        }
+
+        while (this._FBU.rects > 0) {
+            if (this._rfb_connection_state !== 'connected') { return false; }
+
+            if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
+            if (this._FBU.bytes === 0) {
+                if (this._sock.rQwait("rect header", 12)) { return false; }
+                /* New FramebufferUpdate */
+
+                var hdr = this._sock.rQshiftBytes(12);
+                this._FBU.x        = (hdr[0] << 8) + hdr[1];
+                this._FBU.y        = (hdr[2] << 8) + hdr[3];
+                this._FBU.width    = (hdr[4] << 8) + hdr[5];
+                this._FBU.height   = (hdr[6] << 8) + hdr[7];
+                this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
+                                              (hdr[10] << 8) + hdr[11], 10);
+
+                this._onFBUReceive(this,
+                    {'x': this._FBU.x, 'y': this._FBU.y,
+                     'width': this._FBU.width, 'height': this._FBU.height,
+                     'encoding': this._FBU.encoding,
+                     'encodingName': this._encNames[this._FBU.encoding]});
+
+                if (!this._encNames[this._FBU.encoding]) {
+                    this._fail("Unexpected server message",
+                               "Unsupported encoding " +
+                               this._FBU.encoding);
+                    return false;
+                }
+            }
+
+            this._timing.last_fbu = (new Date()).getTime();
+
+            ret = this._encHandlers[this._FBU.encoding]();
+
+            now = (new Date()).getTime();
+            this._timing.cur_fbu += (now - this._timing.last_fbu);
+
+            if (ret) {
+                this._encStats[this._FBU.encoding][0]++;
+                this._encStats[this._FBU.encoding][1]++;
+                this._timing.pixels += this._FBU.width * this._FBU.height;
+            }
+
+            if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
+                if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
+                    this._timing.fbu_rt_start > 0) {
+                    this._timing.full_fbu_total += this._timing.cur_fbu;
+                    this._timing.full_fbu_cnt++;
+                    Log.Info("Timing of full FBU, curr: " +
+                              this._timing.cur_fbu + ", total: " +
+                              this._timing.full_fbu_total + ", cnt: " +
+                              this._timing.full_fbu_cnt + ", avg: " +
+                              (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
+                }
+
+                if (this._timing.fbu_rt_start > 0) {
+                    var fbu_rt_diff = now - this._timing.fbu_rt_start;
+                    this._timing.fbu_rt_total += fbu_rt_diff;
+                    this._timing.fbu_rt_cnt++;
+                    Log.Info("full FBU round-trip, cur: " +
+                              fbu_rt_diff + ", total: " +
+                              this._timing.fbu_rt_total + ", cnt: " +
+                              this._timing.fbu_rt_cnt + ", avg: " +
+                              (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
+                    this._timing.fbu_rt_start = 0;
+                }
+            }
+
+            if (!ret) { return ret; }  // need more data
+        }
+
+        this._display.flip();
+
+        this._onFBUComplete(this,
+                {'x': this._FBU.x, 'y': this._FBU.y,
+                 'width': this._FBU.width, 'height': this._FBU.height,
+                 'encoding': this._FBU.encoding,
+                 'encodingName': this._encNames[this._FBU.encoding]});
+
+        return true;  // We finished this FBU
+    },
+
+    _updateContinuousUpdates: function() {
+        if (!this._enabledContinuousUpdates) { return; }
+
+        RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
+                                             this._fb_width, this._fb_height);
+    }
+};
+
+make_properties(RFB, [
+    ['target', 'wo', 'dom'],                // VNC display rendering Canvas object
+    ['focusContainer', 'wo', 'dom'],        // DOM element that captures keyboard input
+    ['encrypt', 'rw', 'bool'],              // Use TLS/SSL/wss encryption
+    ['local_cursor', 'rw', 'bool'],         // Request locally rendered cursor
+    ['shared', 'rw', 'bool'],               // Request shared mode
+    ['view_only', 'rw', 'bool'],            // Disable client mouse/keyboard
+    ['xvp_password_sep', 'rw', 'str'],      // Separator for XVP password fields
+    ['disconnectTimeout', 'rw', 'int'],     // Time (s) to wait for disconnection
+    ['wsProtocols', 'rw', 'arr'],           // Protocols to use in the WebSocket connection
+    ['repeaterID', 'rw', 'str'],            // [UltraVNC] RepeaterID to connect to
+    ['viewportDrag', 'rw', 'bool'],         // Move the viewport on mouse drags
+
+    // Callback functions
+    ['onUpdateState', 'rw', 'func'],        // onUpdateState(rfb, state, oldstate): connection state change
+    ['onNotification', 'rw', 'func'],       // onNotification(rfb, msg, level, options): notification for the UI
+    ['onDisconnected', 'rw', 'func'],       // onDisconnected(rfb, reason): disconnection finished
+    ['onPasswordRequired', 'rw', 'func'],   // onPasswordRequired(rfb, msg): VNC password is required
+    ['onClipboard', 'rw', 'func'],          // onClipboard(rfb, text): RFB clipboard contents received
+    ['onBell', 'rw', 'func'],               // onBell(rfb): RFB Bell message received
+    ['onFBUReceive', 'rw', 'func'],         // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
+    ['onFBUComplete', 'rw', 'func'],        // onFBUComplete(rfb, fbu): RFB FBU received and processed
+    ['onFBResize', 'rw', 'func'],           // onFBResize(rfb, width, height): frame buffer resized
+    ['onDesktopName', 'rw', 'func'],        // onDesktopName(rfb, name): desktop name received
+    ['onXvpInit', 'rw', 'func']             // onXvpInit(version): XVP extensions active for this connection
+]);
+
+RFB.prototype.set_local_cursor = function (cursor) {
+    if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
+        this._local_cursor = false;
+        this._display.disableLocalCursor(); //Only show server-side cursor
+    } else {
+        if (this._display.get_cursor_uri()) {
+            this._local_cursor = true;
+        } else {
+            Log.Warn("Browser does not support local cursor");
+            this._display.disableLocalCursor();
+        }
+    }
+
+    // Need to send an updated list of encodings if we are connected
+    if (this._rfb_connection_state === "connected") {
+        RFB.messages.clientEncodings(this._sock, this._encodings, cursor);
+    }
+};
+
+RFB.prototype.set_view_only = function (view_only) {
+    this._view_only = view_only;
+
+    if (this._rfb_connection_state === "connecting" ||
+        this._rfb_connection_state === "connected") {
+        if (view_only) {
+            this._keyboard.ungrab();
+            this._mouse.ungrab();
+        } else {
+            this._keyboard.grab();
+            this._mouse.grab();
+        }
+    }
+};
+
+RFB.prototype.get_display = function () { return this._display; };
+RFB.prototype.get_keyboard = function () { return this._keyboard; };
+RFB.prototype.get_mouse = function () { return this._mouse; };
+
+// Class Methods
+RFB.messages = {
+    keyEvent: function (sock, keysym, down) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 4;  // msg-type
+        buff[offset + 1] = down;
+
+        buff[offset + 2] = 0;
+        buff[offset + 3] = 0;
+
+        buff[offset + 4] = (keysym >> 24);
+        buff[offset + 5] = (keysym >> 16);
+        buff[offset + 6] = (keysym >> 8);
+        buff[offset + 7] = keysym;
+
+        sock._sQlen += 8;
+        sock.flush();
+    },
+
+    QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) {
+        function getRFBkeycode(xt_scancode) {
+            var upperByte = (keycode >> 8);
+            var lowerByte = (keycode & 0x00ff);
+            if (upperByte === 0xe0 && lowerByte < 0x7f) {
+                lowerByte = lowerByte | 0x80;
+                return lowerByte;
+            }
+            return xt_scancode;
+        }
+
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 255; // msg-type
+        buff[offset + 1] = 0; // sub msg-type
+
+        buff[offset + 2] = (down >> 8);
+        buff[offset + 3] = down;
+
+        buff[offset + 4] = (keysym >> 24);
+        buff[offset + 5] = (keysym >> 16);
+        buff[offset + 6] = (keysym >> 8);
+        buff[offset + 7] = keysym;
+
+        var RFBkeycode = getRFBkeycode(keycode);
+
+        buff[offset + 8] = (RFBkeycode >> 24);
+        buff[offset + 9] = (RFBkeycode >> 16);
+        buff[offset + 10] = (RFBkeycode >> 8);
+        buff[offset + 11] = RFBkeycode;
+
+        sock._sQlen += 12;
+        sock.flush();
+    },
+
+    pointerEvent: function (sock, x, y, mask) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 5; // msg-type
+
+        buff[offset + 1] = mask;
+
+        buff[offset + 2] = x >> 8;
+        buff[offset + 3] = x;
+
+        buff[offset + 4] = y >> 8;
+        buff[offset + 5] = y;
+
+        sock._sQlen += 6;
+        sock.flush();
+    },
+
+    // TODO(directxman12): make this unicode compatible?
+    clientCutText: function (sock, text) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 6; // msg-type
+
+        buff[offset + 1] = 0; // padding
+        buff[offset + 2] = 0; // padding
+        buff[offset + 3] = 0; // padding
+
+        var n = text.length;
+
+        buff[offset + 4] = n >> 24;
+        buff[offset + 5] = n >> 16;
+        buff[offset + 6] = n >> 8;
+        buff[offset + 7] = n;
+
+        for (var i = 0; i < n; i++) {
+            buff[offset + 8 + i] =  text.charCodeAt(i);
+        }
+
+        sock._sQlen += 8 + n;
+        sock.flush();
+    },
+
+    setDesktopSize: function (sock, width, height, id, flags) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 251;              // msg-type
+        buff[offset + 1] = 0;            // padding
+        buff[offset + 2] = width >> 8;   // width
+        buff[offset + 3] = width;
+        buff[offset + 4] = height >> 8;  // height
+        buff[offset + 5] = height;
+
+        buff[offset + 6] = 1;            // number-of-screens
+        buff[offset + 7] = 0;            // padding
+
+        // screen array
+        buff[offset + 8] = id >> 24;     // id
+        buff[offset + 9] = id >> 16;
+        buff[offset + 10] = id >> 8;
+        buff[offset + 11] = id;
+        buff[offset + 12] = 0;           // x-position
+        buff[offset + 13] = 0;
+        buff[offset + 14] = 0;           // y-position
+        buff[offset + 15] = 0;
+        buff[offset + 16] = width >> 8;  // width
+        buff[offset + 17] = width;
+        buff[offset + 18] = height >> 8; // height
+        buff[offset + 19] = height;
+        buff[offset + 20] = flags >> 24; // flags
+        buff[offset + 21] = flags >> 16;
+        buff[offset + 22] = flags >> 8;
+        buff[offset + 23] = flags;
+
+        sock._sQlen += 24;
+        sock.flush();
+    },
+
+    clientFence: function (sock, flags, payload) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 248; // msg-type
+
+        buff[offset + 1] = 0; // padding
+        buff[offset + 2] = 0; // padding
+        buff[offset + 3] = 0; // padding
+
+        buff[offset + 4] = flags >> 24; // flags
+        buff[offset + 5] = flags >> 16;
+        buff[offset + 6] = flags >> 8;
+        buff[offset + 7] = flags;
+
+        var n = payload.length;
+
+        buff[offset + 8] = n; // length
+
+        for (var i = 0; i < n; i++) {
+            buff[offset + 9 + i] = payload.charCodeAt(i);
+        }
+
+        sock._sQlen += 9 + n;
+        sock.flush();
+    },
+
+    enableContinuousUpdates: function (sock, enable, x, y, width, height) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 150;             // msg-type
+        buff[offset + 1] = enable;      // enable-flag
+
+        buff[offset + 2] = x >> 8;      // x
+        buff[offset + 3] = x;
+        buff[offset + 4] = y >> 8;      // y
+        buff[offset + 5] = y;
+        buff[offset + 6] = width >> 8;  // width
+        buff[offset + 7] = width;
+        buff[offset + 8] = height >> 8; // height
+        buff[offset + 9] = height;
+
+        sock._sQlen += 10;
+        sock.flush();
+    },
+
+    pixelFormat: function (sock, bpp, depth, true_color) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 0;  // msg-type
+
+        buff[offset + 1] = 0; // padding
+        buff[offset + 2] = 0; // padding
+        buff[offset + 3] = 0; // padding
+
+        buff[offset + 4] = bpp * 8;             // bits-per-pixel
+        buff[offset + 5] = depth * 8;           // depth
+        buff[offset + 6] = 0;                   // little-endian
+        buff[offset + 7] = true_color ? 1 : 0;  // true-color
+
+        buff[offset + 8] = 0;    // red-max
+        buff[offset + 9] = 255;  // red-max
+
+        buff[offset + 10] = 0;   // green-max
+        buff[offset + 11] = 255; // green-max
+
+        buff[offset + 12] = 0;   // blue-max
+        buff[offset + 13] = 255; // blue-max
+
+        buff[offset + 14] = 16;  // red-shift
+        buff[offset + 15] = 8;   // green-shift
+        buff[offset + 16] = 0;   // blue-shift
+
+        buff[offset + 17] = 0;   // padding
+        buff[offset + 18] = 0;   // padding
+        buff[offset + 19] = 0;   // padding
+
+        sock._sQlen += 20;
+        sock.flush();
+    },
+
+    clientEncodings: function (sock, encodings, local_cursor) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        buff[offset] = 2; // msg-type
+        buff[offset + 1] = 0; // padding
+
+        // offset + 2 and offset + 3 are encoding count
+
+        var i, j = offset + 4, cnt = 0;
+        for (i = 0; i < encodings.length; i++) {
+            if (encodings[i][0] === "Cursor" && !local_cursor) {
+                Log.Debug("Skipping Cursor pseudo-encoding");
+            } else {
+                var enc = encodings[i][1];
+                buff[j] = enc >> 24;
+                buff[j + 1] = enc >> 16;
+                buff[j + 2] = enc >> 8;
+                buff[j + 3] = enc;
+
+                j += 4;
+                cnt++;
+            }
+        }
+
+        buff[offset + 2] = cnt >> 8;
+        buff[offset + 3] = cnt;
+
+        sock._sQlen += j - offset;
+        sock.flush();
+    },
+
+    fbUpdateRequest: function (sock, incremental, x, y, w, h) {
+        var buff = sock._sQ;
+        var offset = sock._sQlen;
+
+        if (typeof(x) === "undefined") { x = 0; }
+        if (typeof(y) === "undefined") { y = 0; }
+
+        buff[offset] = 3;  // msg-type
+        buff[offset + 1] = incremental ? 1 : 0;
+
+        buff[offset + 2] = (x >> 8) & 0xFF;
+        buff[offset + 3] = x & 0xFF;
+
+        buff[offset + 4] = (y >> 8) & 0xFF;
+        buff[offset + 5] = y & 0xFF;
+
+        buff[offset + 6] = (w >> 8) & 0xFF;
+        buff[offset + 7] = w & 0xFF;
+
+        buff[offset + 8] = (h >> 8) & 0xFF;
+        buff[offset + 9] = h & 0xFF;
+
+        sock._sQlen += 10;
+        sock.flush();
+    }
+};
+
+RFB.genDES = function (password, challenge) {
+    var passwd = [];
+    for (var i = 0; i < password.length; i++) {
+        passwd.push(password.charCodeAt(i));
+    }
+    return (new DES(passwd)).encrypt(challenge);
+};
+
+RFB.encodingHandlers = {
+    RAW: function () {
+        if (this._FBU.lines === 0) {
+            this._FBU.lines = this._FBU.height;
+        }
+
+        this._FBU.bytes = this._FBU.width * 4;  // at least a line
+        if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
+        var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
+        var curr_height = Math.min(this._FBU.lines,
+                                   Math.floor(this._sock.rQlen() / (this._FBU.width * 4)));
+        this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
+                                curr_height, this._sock.get_rQ(),
+                                this._sock.get_rQi());
+        this._sock.rQskipBytes(this._FBU.width * curr_height * 4);
+        this._FBU.lines -= curr_height;
+
+        if (this._FBU.lines > 0) {
+            this._FBU.bytes = this._FBU.width * 4;  // At least another line
+        } else {
+            this._FBU.rects--;
+            this._FBU.bytes = 0;
+        }
+
+        return true;
+    },
+
+    COPYRECT: function () {
+        this._FBU.bytes = 4;
+        if (this._sock.rQwait("COPYRECT", 4)) { return false; }
+        this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
+                                this._FBU.x, this._FBU.y, this._FBU.width,
+                                this._FBU.height);
+
+        this._FBU.rects--;
+        this._FBU.bytes = 0;
+        return true;
+    },
+
+    RRE: function () {
+        var color;
+        if (this._FBU.subrects === 0) {
+            this._FBU.bytes = 4 + 4;
+            if (this._sock.rQwait("RRE", 4 + 4)) { return false; }
+            this._FBU.subrects = this._sock.rQshift32();
+            color = this._sock.rQshiftBytes(4);  // Background
+            this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
+        }
+
+        while (this._FBU.subrects > 0 && this._sock.rQlen() >= (4 + 8)) {
+            color = this._sock.rQshiftBytes(4);
+            var x = this._sock.rQshift16();
+            var y = this._sock.rQshift16();
+            var width = this._sock.rQshift16();
+            var height = this._sock.rQshift16();
+            this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
+            this._FBU.subrects--;
+        }
+
+        if (this._FBU.subrects > 0) {
+            var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
+            this._FBU.bytes = (4 + 8) * chunk;
+        } else {
+            this._FBU.rects--;
+            this._FBU.bytes = 0;
+        }
+
+        return true;
+    },
+
+    HEXTILE: function () {
+        var rQ = this._sock.get_rQ();
+        var rQi = this._sock.get_rQi();
+
+        if (this._FBU.tiles === 0) {
+            this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
+            this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
+            this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
+            this._FBU.tiles = this._FBU.total_tiles;
+        }
+
+        while (this._FBU.tiles > 0) {
+            this._FBU.bytes = 1;
+            if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
+            var subencoding = rQ[rQi];  // Peek
+            if (subencoding > 30) {  // Raw
+                this._fail("Unexpected server message",
+                           "Illegal hextile subencoding: " + subencoding);
+                return false;
+            }
+
+            var subrects = 0;
+            var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
+            var tile_x = curr_tile % this._FBU.tiles_x;
+            var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
+            var x = this._FBU.x + tile_x * 16;
+            var y = this._FBU.y + tile_y * 16;
+            var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
+            var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
+
+            // Figure out how much we are expecting
+            if (subencoding & 0x01) {  // Raw
+                this._FBU.bytes += w * h * 4;
+            } else {
+                if (subencoding & 0x02) {  // Background
+                    this._FBU.bytes += 4;
+                }
+                if (subencoding & 0x04) {  // Foreground
+                    this._FBU.bytes += 4;
+                }
+                if (subencoding & 0x08) {  // AnySubrects
+                    this._FBU.bytes++;  // Since we aren't shifting it off
+                    if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
+                    subrects = rQ[rQi + this._FBU.bytes - 1];  // Peek
+                    if (subencoding & 0x10) {  // SubrectsColoured
+                        this._FBU.bytes += subrects * (4 + 2);
+                    } else {
+                        this._FBU.bytes += subrects * 2;
+                    }
+                }
+            }
+
+            if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
+
+            // We know the encoding and have a whole tile
+            this._FBU.subencoding = rQ[rQi];
+            rQi++;
+            if (this._FBU.subencoding === 0) {
+                if (this._FBU.lastsubencoding & 0x01) {
+                    // Weird: ignore blanks are RAW
+                    Log.Debug("     Ignoring blank after RAW");
+                } else {
+                    this._display.fillRect(x, y, w, h, this._FBU.background);
+                }
+            } else if (this._FBU.subencoding & 0x01) {  // Raw
+                this._display.blitImage(x, y, w, h, rQ, rQi);
+                rQi += this._FBU.bytes - 1;
+            } else {
+                if (this._FBU.subencoding & 0x02) {  // Background
+                    this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+                    rQi += 4;
+                }
+                if (this._FBU.subencoding & 0x04) {  // Foreground
+                    this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+                    rQi += 4;
+                }
+
+                this._display.startTile(x, y, w, h, this._FBU.background);
+                if (this._FBU.subencoding & 0x08) {  // AnySubrects
+                    subrects = rQ[rQi];
+                    rQi++;
+
+                    for (var s = 0; s < subrects; s++) {
+                        var color;
+                        if (this._FBU.subencoding & 0x10) {  // SubrectsColoured
+                            color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+                            rQi += 4;
+                        } else {
+                            color = this._FBU.foreground;
+                        }
+                        var xy = rQ[rQi];
+                        rQi++;
+                        var sx = (xy >> 4);
+                        var sy = (xy & 0x0f);
+
+                        var wh = rQ[rQi];
+                        rQi++;
+                        var sw = (wh >> 4) + 1;
+                        var sh = (wh & 0x0f) + 1;
+
+                        this._display.subTile(sx, sy, sw, sh, color);
+                    }
+                }
+                this._display.finishTile();
+            }
+            this._sock.set_rQi(rQi);
+            this._FBU.lastsubencoding = this._FBU.subencoding;
+            this._FBU.bytes = 0;
+            this._FBU.tiles--;
+        }
+
+        if (this._FBU.tiles === 0) {
+            this._FBU.rects--;
+        }
+
+        return true;
+    },
+
+    getTightCLength: function (arr) {
+        var header = 1, data = 0;
+        data += arr[0] & 0x7f;
+        if (arr[0] & 0x80) {
+            header++;
+            data += (arr[1] & 0x7f) << 7;
+            if (arr[1] & 0x80) {
+                header++;
+                data += arr[2] << 14;
+            }
+        }
+        return [header, data];
+    },
+
+    display_tight: function (isTightPNG) {
+        this._FBU.bytes = 1;  // compression-control byte
+        if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
+
+        var checksum = function (data) {
+            var sum = 0;
+            for (var i = 0; i < data.length; i++) {
+                sum += data[i];
+                if (sum > 65536) sum -= 65536;
+            }
+            return sum;
+        };
+
+        var resetStreams = 0;
+        var streamId = -1;
+        var decompress = function (data, expected) {
+            for (var i = 0; i < 4; i++) {
+                if ((resetStreams >> i) & 1) {
+                    this._FBU.zlibs[i].reset();
+                    Log.Info("Reset zlib stream " + i);
+                }
+            }
+
+            //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
+            var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
+            /*if (uncompressed.status !== 0) {
+                Log.Error("Invalid data in zlib stream");
+            }*/
+
+            //return uncompressed.data;
+            return uncompressed;
+        }.bind(this);
+
+        var indexedToRGBX2Color = function (data, palette, width, height) {
+            // Convert indexed (palette based) image data to RGB
+            // TODO: reduce number of calculations inside loop
+            var dest = this._destBuff;
+            var w = Math.floor((width + 7) / 8);
+            var w1 = Math.floor(width / 8);
+
+            /*for (var y = 0; y < height; y++) {
+                var b, x, dp, sp;
+                var yoffset = y * width;
+                var ybitoffset = y * w;
+                var xoffset, targetbyte;
+                for (x = 0; x < w1; x++) {
+                    xoffset = yoffset + x * 8;
+                    targetbyte = data[ybitoffset + x];
+                    for (b = 7; b >= 0; b--) {
+                        dp = (xoffset + 7 - b) * 3;
+                        sp = (targetbyte >> b & 1) * 3;
+                        dest[dp] = palette[sp];
+                        dest[dp + 1] = palette[sp + 1];
+                        dest[dp + 2] = palette[sp + 2];
+                    }
+                }
+
+                xoffset = yoffset + x * 8;
+                targetbyte = data[ybitoffset + x];
+                for (b = 7; b >= 8 - width % 8; b--) {
+                    dp = (xoffset + 7 - b) * 3;
+                    sp = (targetbyte >> b & 1) * 3;
+                    dest[dp] = palette[sp];
+                    dest[dp + 1] = palette[sp + 1];
+                    dest[dp + 2] = palette[sp + 2];
+                }
+            }*/
+
+            for (var y = 0; y < height; y++) {
+                var b, x, dp, sp;
+                for (x = 0; x < w1; x++) {
+                    for (b = 7; b >= 0; b--) {
+                        dp = (y * width + x * 8 + 7 - b) * 4;
+                        sp = (data[y * w + x] >> b & 1) * 3;
+                        dest[dp] = palette[sp];
+                        dest[dp + 1] = palette[sp + 1];
+                        dest[dp + 2] = palette[sp + 2];
+                        dest[dp + 3] = 255;
+                    }
+                }
+
+                for (b = 7; b >= 8 - width % 8; b--) {
+                    dp = (y * width + x * 8 + 7 - b) * 4;
+                    sp = (data[y * w + x] >> b & 1) * 3;
+                    dest[dp] = palette[sp];
+                    dest[dp + 1] = palette[sp + 1];
+                    dest[dp + 2] = palette[sp + 2];
+                    dest[dp + 3] = 255;
+                }
+            }
+
+            return dest;
+        }.bind(this);
+
+        var indexedToRGBX = function (data, palette, width, height) {
+            // Convert indexed (palette based) image data to RGB
+            var dest = this._destBuff;
+            var total = width * height * 4;
+            for (var i = 0, j = 0; i < total; i += 4, j++) {
+                var sp = data[j] * 3;
+                dest[i] = palette[sp];
+                dest[i + 1] = palette[sp + 1];
+                dest[i + 2] = palette[sp + 2];
+                dest[i + 3] = 255;
+            }
+
+            return dest;
+        }.bind(this);
+
+        var rQi = this._sock.get_rQi();
+        var rQ = this._sock.rQwhole();
+        var cmode, data;
+        var cl_header, cl_data;
+
+        var handlePalette = function () {
+            var numColors = rQ[rQi + 2] + 1;
+            var paletteSize = numColors * 3;
+            this._FBU.bytes += paletteSize;
+            if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
+
+            var bpp = (numColors <= 2) ? 1 : 8;
+            var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
+            var raw = false;
+            if (rowSize * this._FBU.height < 12) {
+                raw = true;
+                cl_header = 0;
+                cl_data = rowSize * this._FBU.height;
+                //clength = [0, rowSize * this._FBU.height];
+            } else {
+                // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
+                var cl_offset = rQi + 3 + paletteSize;
+                cl_header = 1;
+                cl_data = 0;
+                cl_data += rQ[cl_offset] & 0x7f;
+                if (rQ[cl_offset] & 0x80) {
+                    cl_header++;
+                    cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
+                    if (rQ[cl_offset + 1] & 0x80) {
+                        cl_header++;
+                        cl_data += rQ[cl_offset + 2] << 14;
+                    }
+                }
+                // end inline getTightCLength
+            }
+
+            this._FBU.bytes += cl_header + cl_data;
+            if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+            // Shift ctl, filter id, num colors, palette entries, and clength off
+            this._sock.rQskipBytes(3);
+            //var palette = this._sock.rQshiftBytes(paletteSize);
+            this._sock.rQshiftTo(this._paletteBuff, paletteSize);
+            this._sock.rQskipBytes(cl_header);
+
+            if (raw) {
+                data = this._sock.rQshiftBytes(cl_data);
+            } else {
+                data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
+            }
+
+            // Convert indexed (palette based) image data to RGB
+            var rgbx;
+            if (numColors == 2) {
+                rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
+                this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
+            } else {
+                rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
+                this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
+            }
+
+
+            return true;
+        }.bind(this);
+
+        var handleCopy = function () {
+            var raw = false;
+            var uncompressedSize = this._FBU.width * this._FBU.height * 3;
+            if (uncompressedSize < 12) {
+                raw = true;
+                cl_header = 0;
+                cl_data = uncompressedSize;
+            } else {
+                // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
+                var cl_offset = rQi + 1;
+                cl_header = 1;
+                cl_data = 0;
+                cl_data += rQ[cl_offset] & 0x7f;
+                if (rQ[cl_offset] & 0x80) {
+                    cl_header++;
+                    cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
+                    if (rQ[cl_offset + 1] & 0x80) {
+                        cl_header++;
+                        cl_data += rQ[cl_offset + 2] << 14;
+                    }
+                }
+                // end inline getTightCLength
+            }
+            this._FBU.bytes = 1 + cl_header + cl_data;
+            if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+            // Shift ctl, clength off
+            this._sock.rQshiftBytes(1 + cl_header);
+
+            if (raw) {
+                data = this._sock.rQshiftBytes(cl_data);
+            } else {
+                data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
+            }
+
+            this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
+
+            return true;
+        }.bind(this);
+
+        var ctl = this._sock.rQpeek8();
+
+        // Keep tight reset bits
+        resetStreams = ctl & 0xF;
+
+        // Figure out filter
+        ctl = ctl >> 4;
+        streamId = ctl & 0x3;
+
+        if (ctl === 0x08)       cmode = "fill";
+        else if (ctl === 0x09)  cmode = "jpeg";
+        else if (ctl === 0x0A)  cmode = "png";
+        else if (ctl & 0x04)    cmode = "filter";
+        else if (ctl < 0x04)    cmode = "copy";
+        else return this._fail("Unexpected server message",
+                               "Illegal tight compression received, " +
+                               "ctl: " + ctl);
+
+        if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
+            return this._fail("Unexpected server message",
+                              "filter/copy received in tightPNG mode");
+        }
+
+        switch (cmode) {
+            // fill use depth because TPIXELs drop the padding byte
+            case "fill":  // TPIXEL
+                this._FBU.bytes += 3;
+                break;
+            case "jpeg":  // max clength
+                this._FBU.bytes += 3;
+                break;
+            case "png":  // max clength
+                this._FBU.bytes += 3;
+                break;
+            case "filter":  // filter id + num colors if palette
+                this._FBU.bytes += 2;
+                break;
+            case "copy":
+                break;
+        }
+
+        if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+        // Determine FBU.bytes
+        switch (cmode) {
+            case "fill":
+                // skip ctl byte
+                this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false);
+                this._sock.rQskipBytes(4);
+                break;
+            case "png":
+            case "jpeg":
+                // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
+                var cl_offset = rQi + 1;
+                cl_header = 1;
+                cl_data = 0;
+                cl_data += rQ[cl_offset] & 0x7f;
+                if (rQ[cl_offset] & 0x80) {
+                    cl_header++;
+                    cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
+                    if (rQ[cl_offset + 1] & 0x80) {
+                        cl_header++;
+                        cl_data += rQ[cl_offset + 2] << 14;
+                    }
+                }
+                // end inline getTightCLength
+                this._FBU.bytes = 1 + cl_header + cl_data;  // ctl + clength size + jpeg-data
+                if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+                // We have everything, render it
+                this._sock.rQskipBytes(1 + cl_header);  // shift off clt + compact length
+                data = this._sock.rQshiftBytes(cl_data);
+                this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data);
+                break;
+            case "filter":
+                var filterId = rQ[rQi + 1];
+                if (filterId === 1) {
+                    if (!handlePalette()) { return false; }
+                } else {
+                    // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
+                    // Filter 2, Gradient is valid but not use if jpeg is enabled
+                    this._fail("Unexpected server message",
+                               "Unsupported tight subencoding received, " +
+                               "filter: " + filterId);
+                }
+                break;
+            case "copy":
+                if (!handleCopy()) { return false; }
+                break;
+        }
+
+
+        this._FBU.bytes = 0;
+        this._FBU.rects--;
+
+        return true;
+    },
+
+    TIGHT: function () { return this._encHandlers.display_tight(false); },
+    TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
+
+    last_rect: function () {
+        this._FBU.rects = 0;
+        return true;
+    },
+
+    handle_FB_resize: function () {
+        this._fb_width = this._FBU.width;
+        this._fb_height = this._FBU.height;
+        this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
+        this._display.resize(this._fb_width, this._fb_height);
+        this._onFBResize(this, this._fb_width, this._fb_height);
+        this._timing.fbu_rt_start = (new Date()).getTime();
+        this._updateContinuousUpdates();
+
+        this._FBU.bytes = 0;
+        this._FBU.rects -= 1;
+        return true;
+    },
+
+    ExtendedDesktopSize: function () {
+        this._FBU.bytes = 1;
+        if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
+
+        this._supportsSetDesktopSize = true;
+        var number_of_screens = this._sock.rQpeek8();
+
+        this._FBU.bytes = 4 + (number_of_screens * 16);
+        if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
+
+        this._sock.rQskipBytes(1);  // number-of-screens
+        this._sock.rQskipBytes(3);  // padding
+
+        for (var i = 0; i < number_of_screens; i += 1) {
+            // Save the id and flags of the first screen
+            if (i === 0) {
+                this._screen_id = this._sock.rQshiftBytes(4);    // id
+                this._sock.rQskipBytes(2);                       // x-position
+                this._sock.rQskipBytes(2);                       // y-position
+                this._sock.rQskipBytes(2);                       // width
+                this._sock.rQskipBytes(2);                       // height
+                this._screen_flags = this._sock.rQshiftBytes(4); // flags
+            } else {
+                this._sock.rQskipBytes(16);
+            }
+        }
+
+        /*
+         * The x-position indicates the reason for the change:
+         *
+         *  0 - server resized on its own
+         *  1 - this client requested the resize
+         *  2 - another client requested the resize
+         */
+
+        // We need to handle errors when we requested the resize.
+        if (this._FBU.x === 1 && this._FBU.y !== 0) {
+            var msg = "";
+            // The y-position indicates the status code from the server
+            switch (this._FBU.y) {
+            case 1:
+                msg = "Resize is administratively prohibited";
+                break;
+            case 2:
+                msg = "Out of resources";
+                break;
+            case 3:
+                msg = "Invalid screen layout";
+                break;
+            default:
+                msg = "Unknown reason";
+                break;
+            }
+            this._notification("Server did not accept the resize request: "
+                               + msg, 'normal');
+            return true;
+        }
+
+        this._encHandlers.handle_FB_resize();
+        return true;
+    },
+
+    DesktopSize: function () {
+        this._encHandlers.handle_FB_resize();
+        return true;
+    },
+
+    Cursor: function () {
+        Log.Debug(">> set_cursor");
+        var x = this._FBU.x;  // hotspot-x
+        var y = this._FBU.y;  // hotspot-y
+        var w = this._FBU.width;
+        var h = this._FBU.height;
+
+        var pixelslength = w * h * 4;
+        var masklength = Math.floor((w + 7) / 8) * h;
+
+        this._FBU.bytes = pixelslength + masklength;
+        if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
+
+        this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
+                                   this._sock.rQshiftBytes(masklength),
+                                   x, y, w, h);
+
+        this._FBU.bytes = 0;
+        this._FBU.rects--;
+
+        Log.Debug("<< set_cursor");
+        return true;
+    },
+
+    QEMUExtendedKeyEvent: function () {
+        this._FBU.rects--;
+
+        var keyboardEvent = document.createEvent("keyboardEvent");
+        if (keyboardEvent.code !== undefined) {
+            this._qemuExtKeyEventSupported = true;
+        }
+    },
+
+    JPEG_quality_lo: function () {
+        Log.Error("Server sent jpeg_quality pseudo-encoding");
+    },
+
+    compress_lo: function () {
+        Log.Error("Server sent compress level pseudo-encoding");
+    }
+};

+ 53 - 0
static/novnc/core/util/browsers.js

@@ -0,0 +1,53 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+import * as Log from './logging.js';
+
+// Touch detection
+export var isTouchDevice = ('ontouchstart' in document.documentElement) ||
+                                 // requried for Chrome debugger
+                                 (document.ontouchstart !== undefined) ||
+                                 // required for MS Surface
+                                 (navigator.maxTouchPoints > 0) ||
+                                 (navigator.msMaxTouchPoints > 0);
+window.addEventListener('touchstart', function onFirstTouch() {
+    isTouchDevice = true;
+    window.removeEventListener('touchstart', onFirstTouch, false);
+}, false);
+
+var _cursor_uris_supported = null;
+
+export function browserSupportsCursorURIs () {
+    if (_cursor_uris_supported === null) {
+        try {
+            var target = document.createElement('canvas');
+            target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
+
+            if (target.style.cursor) {
+                Log.Info("Data URI scheme cursor supported");
+                _cursor_uris_supported = true;
+            } else {
+                Log.Warn("Data URI scheme cursor not supported");
+                _cursor_uris_supported = false;
+            }
+        } catch (exc) {
+            Log.Error("Data URI scheme cursor test exception: " + exc);
+            _cursor_uris_supported = false;
+        }
+    }
+
+    return _cursor_uris_supported;
+};
+
+export function _forceCursorURIs(enabled) {
+    if (enabled === undefined || enabled) {
+        _cursor_uris_supported = true;
+    } else {
+        _cursor_uris_supported = false;
+    }
+}

+ 140 - 0
static/novnc/core/util/events.js

@@ -0,0 +1,140 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Cross-browser event and position routines
+ */
+
+import * as Log from './logging.js';
+
+export function getPointerEvent (e) {
+    return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
+};
+
+export function stopEvent (e) {
+    e.stopPropagation();
+    e.preventDefault();
+};
+
+// Emulate Element.setCapture() when not supported
+var _captureRecursion = false;
+var _captureElem = null;
+function _captureProxy(e) {
+    // Recursion protection as we'll see our own event
+    if (_captureRecursion) return;
+
+    // Clone the event as we cannot dispatch an already dispatched event
+    var newEv = new e.constructor(e.type, e);
+
+    _captureRecursion = true;
+    _captureElem.dispatchEvent(newEv);
+    _captureRecursion = false;
+
+    // Avoid double events
+    e.stopPropagation();
+
+    // Respect the wishes of the redirected event handlers
+    if (newEv.defaultPrevented) {
+        e.preventDefault();
+    }
+
+    // Implicitly release the capture on button release
+    if (e.type === "mouseup") {
+        releaseCapture();
+    }
+};
+
+// Follow cursor style of target element
+function _captureElemChanged() {
+    var captureElem = document.getElementById("noVNC_mouse_capture_elem");
+    captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
+};
+var _captureObserver = new MutationObserver(_captureElemChanged);
+
+var _captureIndex = 0;
+
+export function setCapture (elem) {
+    if (elem.setCapture) {
+
+        elem.setCapture();
+
+        // IE releases capture on 'click' events which might not trigger
+        elem.addEventListener('mouseup', releaseCapture);
+
+    } else {
+        // Release any existing capture in case this method is
+        // called multiple times without coordination
+        releaseCapture();
+
+        var captureElem = document.getElementById("noVNC_mouse_capture_elem");
+
+        if (captureElem === null) {
+            captureElem = document.createElement("div");
+            captureElem.id = "noVNC_mouse_capture_elem";
+            captureElem.style.position = "fixed";
+            captureElem.style.top = "0px";
+            captureElem.style.left = "0px";
+            captureElem.style.width = "100%";
+            captureElem.style.height = "100%";
+            captureElem.style.zIndex = 10000;
+            captureElem.style.display = "none";
+            document.body.appendChild(captureElem);
+
+            // This is to make sure callers don't get confused by having
+            // our blocking element as the target
+            captureElem.addEventListener('contextmenu', _captureProxy);
+
+            captureElem.addEventListener('mousemove', _captureProxy);
+            captureElem.addEventListener('mouseup', _captureProxy);
+        }
+
+        _captureElem = elem;
+        _captureIndex++;
+
+        // Track cursor and get initial cursor
+        _captureObserver.observe(elem, {attributes:true});
+        _captureElemChanged();
+
+        captureElem.style.display = "";
+
+        // We listen to events on window in order to keep tracking if it
+        // happens to leave the viewport
+        window.addEventListener('mousemove', _captureProxy);
+        window.addEventListener('mouseup', _captureProxy);
+    }
+};
+
+export function releaseCapture () {
+    if (document.releaseCapture) {
+
+        document.releaseCapture();
+
+    } else {
+        if (!_captureElem) {
+            return;
+        }
+
+        // There might be events already queued, so we need to wait for
+        // them to flush. E.g. contextmenu in Microsoft Edge
+        window.setTimeout(function(expected) {
+            // Only clear it if it's the expected grab (i.e. no one
+            // else has initiated a new grab)
+            if (_captureIndex === expected) {
+                _captureElem = null;
+            }
+        }, 0, _captureIndex);
+
+        _captureObserver.disconnect();
+
+        var captureElem = document.getElementById("noVNC_mouse_capture_elem");
+        captureElem.style.display = "none";
+
+        window.removeEventListener('mousemove', _captureProxy);
+        window.removeEventListener('mouseup', _captureProxy);
+    }
+};

+ 170 - 0
static/novnc/core/util/localization.js

@@ -0,0 +1,170 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Localization Utilities
+ */
+
+export function Localizer() {
+    // Currently configured language
+    this.language = 'en';
+
+    // Current dictionary of translations
+    this.dictionary = undefined;
+}
+
+Localizer.prototype = {
+    // Configure suitable language based on user preferences
+    setup: function (supportedLanguages) {
+        var userLanguages;
+
+        this.language = 'en'; // Default: US English
+
+        /*
+         * Navigator.languages only available in Chrome (32+) and FireFox (32+)
+         * Fall back to navigator.language for other browsers
+         */
+        if (typeof window.navigator.languages == 'object') {
+            userLanguages = window.navigator.languages;
+        } else {
+            userLanguages = [navigator.language || navigator.userLanguage];
+        }
+
+        for (var i = 0;i < userLanguages.length;i++) {
+            var userLang = userLanguages[i];
+            userLang = userLang.toLowerCase();
+            userLang = userLang.replace("_", "-");
+            userLang = userLang.split("-");
+
+            // Built-in default?
+            if ((userLang[0] === 'en') &&
+                ((userLang[1] === undefined) || (userLang[1] === 'us'))) {
+                return;
+            }
+
+            // First pass: perfect match
+            for (var j = 0;j < supportedLanguages.length;j++) {
+                var supLang = supportedLanguages[j];
+                supLang = supLang.toLowerCase();
+                supLang = supLang.replace("_", "-");
+                supLang = supLang.split("-");
+
+                if (userLang[0] !== supLang[0])
+                    continue;
+                if (userLang[1] !== supLang[1])
+                    continue;
+
+                this.language = supportedLanguages[j];
+                return;
+            }
+
+            // Second pass: fallback
+            for (var j = 0;j < supportedLanguages.length;j++) {
+                supLang = supportedLanguages[j];
+                supLang = supLang.toLowerCase();
+                supLang = supLang.replace("_", "-");
+                supLang = supLang.split("-");
+
+                if (userLang[0] !== supLang[0])
+                    continue;
+                if (supLang[1] !== undefined)
+                    continue;
+
+                this.language = supportedLanguages[j];
+                return;
+            }
+        }
+    },
+
+    // Retrieve localised text
+    get: function (id) {
+        if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
+            return this.dictionary[id];
+        } else {
+            return id;
+        }
+    },
+
+    // Traverses the DOM and translates relevant fields
+    // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
+    translateDOM: function () {
+        var self = this;
+        function process(elem, enabled) {
+            function isAnyOf(searchElement, items) {
+                return items.indexOf(searchElement) !== -1;
+            }
+
+            function translateAttribute(elem, attr) {
+                var str = elem.getAttribute(attr);
+                str = self.get(str);
+                elem.setAttribute(attr, str);
+            }
+
+            function translateTextNode(node) {
+                var str = node.data.trim();
+                str = self.get(str);
+                node.data = str;
+            }
+
+            if (elem.hasAttribute("translate")) {
+                if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
+                    enabled = true;
+                } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
+                    enabled = false;
+                }
+            }
+
+            if (enabled) {
+                if (elem.hasAttribute("abbr") &&
+                    elem.tagName === "TH") {
+                    translateAttribute(elem, "abbr");
+                }
+                if (elem.hasAttribute("alt") &&
+                    isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
+                    translateAttribute(elem, "alt");
+                }
+                if (elem.hasAttribute("download") &&
+                    isAnyOf(elem.tagName, ["A", "AREA"])) {
+                    translateAttribute(elem, "download");
+                }
+                if (elem.hasAttribute("label") &&
+                    isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
+                                   "OPTION", "TRACK"])) {
+                    translateAttribute(elem, "label");
+                }
+                // FIXME: Should update "lang"
+                if (elem.hasAttribute("placeholder") &&
+                    isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
+                    translateAttribute(elem, "placeholder");
+                }
+                if (elem.hasAttribute("title")) {
+                    translateAttribute(elem, "title");
+                }
+                if (elem.hasAttribute("value") &&
+                    elem.tagName === "INPUT" &&
+                    isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
+                    translateAttribute(elem, "value");
+                }
+            }
+
+            for (var i = 0;i < elem.childNodes.length;i++) {
+                var node = elem.childNodes[i];
+                if (node.nodeType === node.ELEMENT_NODE) {
+                    process(node, enabled);
+                } else if (node.nodeType === node.TEXT_NODE && enabled) {
+                    translateTextNode(node);
+                }
+            }
+        }
+
+        process(document.body, true);
+    },
+}
+
+export var l10n = new Localizer();
+export default l10n.get.bind(l10n);

+ 53 - 0
static/novnc/core/util/logging.js

@@ -0,0 +1,53 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Logging/debug routines
+ */
+
+var _log_level = 'warn';
+
+var Debug = function (msg) {};
+var Info = function (msg) {};
+var Warn = function (msg) {};
+var Error = function (msg) {};
+
+export function init_logging (level) {
+    if (typeof level === 'undefined') {
+        level = _log_level;
+    } else {
+        _log_level = level;
+    }
+
+    Debug = Info = Warn = Error = function (msg) {};
+    if (typeof window.console !== "undefined") {
+        /* jshint -W086 */
+        switch (level) {
+            case 'debug':
+                Debug = console.debug.bind(window.console);
+            case 'info':
+                Info  = console.info.bind(window.console);
+            case 'warn':
+                Warn  = console.warn.bind(window.console);
+            case 'error':
+                Error = console.error.bind(window.console);
+            case 'none':
+                break;
+            default:
+                throw new Error("invalid logging type '" + level + "'");
+        }
+        /* jshint +W086 */
+    }
+};
+export function get_logging () {
+    return _log_level;
+};
+export { Debug, Info, Warn, Error };
+
+// Initialize logging level
+init_logging();

+ 138 - 0
static/novnc/core/util/properties.js

@@ -0,0 +1,138 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Getter/Setter Creation Utilities
+ */
+
+import * as Log from './logging.js';
+
+function make_property (proto, name, mode, type) {
+    "use strict";
+
+    var getter;
+    if (type === 'arr') {
+        getter = function (idx) {
+            if (typeof idx !== 'undefined') {
+                return this['_' + name][idx];
+            } else {
+                return this['_' + name];
+            }
+        };
+    } else {
+        getter = function () {
+            return this['_' + name];
+        };
+    }
+
+    var make_setter = function (process_val) {
+        if (process_val) {
+            return function (val, idx) {
+                if (typeof idx !== 'undefined') {
+                    this['_' + name][idx] = process_val(val);
+                } else {
+                    this['_' + name] = process_val(val);
+                }
+            };
+        } else {
+            return function (val, idx) {
+                if (typeof idx !== 'undefined') {
+                    this['_' + name][idx] = val;
+                } else {
+                    this['_' + name] = val;
+                }
+            };
+        }
+    };
+
+    var setter;
+    if (type === 'bool') {
+        setter = make_setter(function (val) {
+            if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
+                return false;
+            } else {
+                return true;
+            }
+        });
+    } else if (type === 'int') {
+        setter = make_setter(function (val) { return parseInt(val, 10); });
+    } else if (type === 'float') {
+        setter = make_setter(parseFloat);
+    } else if (type === 'str') {
+        setter = make_setter(String);
+    } else if (type === 'func') {
+        setter = make_setter(function (val) {
+            if (!val) {
+                return function () {};
+            } else {
+                return val;
+            }
+        });
+    } else if (type === 'arr' || type === 'dom' || type == 'raw') {
+        setter = make_setter();
+    } else {
+        throw new Error('Unknown property type ' + type);  // some sanity checking
+    }
+
+    // set the getter
+    if (typeof proto['get_' + name] === 'undefined') {
+        proto['get_' + name] = getter;
+    }
+
+    // set the setter if needed
+    if (typeof proto['set_' + name] === 'undefined') {
+        if (mode === 'rw') {
+            proto['set_' + name] = setter;
+        } else if (mode === 'wo') {
+            proto['set_' + name] = function (val, idx) {
+                if (typeof this['_' + name] !== 'undefined') {
+                    throw new Error(name + " can only be set once");
+                }
+                setter.call(this, val, idx);
+            };
+        }
+    }
+
+    // make a special setter that we can use in set defaults
+    proto['_raw_set_' + name] = function (val, idx) {
+        setter.call(this, val, idx);
+        //delete this['_init_set_' + name];  // remove it after use
+    };
+};
+
+export function make_properties (constructor, arr) {
+    "use strict";
+    for (var i = 0; i < arr.length; i++) {
+        make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
+    }
+};
+
+export function set_defaults (obj, conf, defaults) {
+    var defaults_keys = Object.keys(defaults);
+    var conf_keys = Object.keys(conf);
+    var keys_obj = {};
+    var i;
+    for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
+    for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
+    var keys = Object.keys(keys_obj);
+
+    for (i = 0; i < keys.length; i++) {
+        var setter = obj['_raw_set_' + keys[i]];
+        if (!setter) {
+          Log.Warn('Invalid property ' + keys[i]);
+          continue;
+        }
+
+        if (keys[i] in conf) {
+            setter.call(obj, conf[keys[i]]);
+        } else {
+            setter.call(obj, defaults[keys[i]]);
+        }
+    }
+};
+

+ 15 - 0
static/novnc/core/util/strings.js

@@ -0,0 +1,15 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * Decode from UTF-8
+ */
+export function decodeUTF8 (utf8string) {
+    "use strict";
+    return decodeURIComponent(escape(utf8string));
+};

+ 351 - 0
static/novnc/core/websock.js

@@ -0,0 +1,351 @@
+/*
+ * Websock: high-performance binary WebSockets
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * Websock is similar to the standard WebSocket object but with extra
+ * buffer handling.
+ *
+ * Websock has built-in receive queue buffering; the message event
+ * does not contain actual data but is simply a notification that
+ * there is new data available. Several rQ* methods are available to
+ * read binary data off of the receive queue.
+ */
+
+import * as Log from './util/logging.js';
+
+/*jslint browser: true, bitwise: true */
+/*global Util*/
+
+export default function Websock() {
+    "use strict";
+
+    this._websocket = null;  // WebSocket object
+
+    this._rQi = 0;           // Receive queue index
+    this._rQlen = 0;         // Next write position in the receive queue
+    this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
+    this._rQmax = this._rQbufferSize / 8;
+    // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
+    this._rQ = null; // Receive queue
+
+    this._sQbufferSize = 1024 * 10;  // 10 KiB
+    // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
+    this._sQlen = 0;
+    this._sQ = null;  // Send queue
+
+    this._eventHandlers = {
+        'message': function () {},
+        'open': function () {},
+        'close': function () {},
+        'error': function () {}
+    };
+};
+
+// this has performance issues in some versions Chromium, and
+// doesn't gain a tremendous amount of performance increase in Firefox
+// at the moment.  It may be valuable to turn it on in the future.
+var ENABLE_COPYWITHIN = false;
+
+var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024;  // 40 MiB
+
+var typedArrayToString = (function () {
+    // This is only for PhantomJS, which doesn't like apply-ing
+    // with Typed Arrays
+    try {
+        var arr = new Uint8Array([1, 2, 3]);
+        String.fromCharCode.apply(null, arr);
+        return function (a) { return String.fromCharCode.apply(null, a); };
+    } catch (ex) {
+        return function (a) {
+            return String.fromCharCode.apply(
+                null, Array.prototype.slice.call(a));
+        };
+    }
+})();
+
+Websock.prototype = {
+    // Getters and Setters
+    get_sQ: function () {
+        return this._sQ;
+    },
+
+    get_rQ: function () {
+        return this._rQ;
+    },
+
+    get_rQi: function () {
+        return this._rQi;
+    },
+
+    set_rQi: function (val) {
+        this._rQi = val;
+    },
+
+    // Receive Queue
+    rQlen: function () {
+        return this._rQlen - this._rQi;
+    },
+
+    rQpeek8: function () {
+        return this._rQ[this._rQi];
+    },
+
+    rQshift8: function () {
+        return this._rQ[this._rQi++];
+    },
+
+    rQskip8: function () {
+        this._rQi++;
+    },
+
+    rQskipBytes: function (num) {
+        this._rQi += num;
+    },
+
+    // TODO(directxman12): test performance with these vs a DataView
+    rQshift16: function () {
+        return (this._rQ[this._rQi++] << 8) +
+               this._rQ[this._rQi++];
+    },
+
+    rQshift32: function () {
+        return (this._rQ[this._rQi++] << 24) +
+               (this._rQ[this._rQi++] << 16) +
+               (this._rQ[this._rQi++] << 8) +
+               this._rQ[this._rQi++];
+    },
+
+    rQshiftStr: function (len) {
+        if (typeof(len) === 'undefined') { len = this.rQlen(); }
+        var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
+        this._rQi += len;
+        return typedArrayToString(arr);
+    },
+
+    rQshiftBytes: function (len) {
+        if (typeof(len) === 'undefined') { len = this.rQlen(); }
+        this._rQi += len;
+        return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
+    },
+
+    rQshiftTo: function (target, len) {
+        if (len === undefined) { len = this.rQlen(); }
+        // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
+        target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
+        this._rQi += len;
+    },
+
+    rQwhole: function () {
+        return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
+    },
+
+    rQslice: function (start, end) {
+        if (end) {
+            return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
+        } else {
+            return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
+        }
+    },
+
+    // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
+    // to be available in the receive queue. Return true if we need to
+    // wait (and possibly print a debug message), otherwise false.
+    rQwait: function (msg, num, goback) {
+        var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
+        if (rQlen < num) {
+            if (goback) {
+                if (this._rQi < goback) {
+                    throw new Error("rQwait cannot backup " + goback + " bytes");
+                }
+                this._rQi -= goback;
+            }
+            return true; // true means need more data
+        }
+        return false;
+    },
+
+    // Send Queue
+
+    flush: function () {
+        if (this._websocket.bufferedAmount !== 0) {
+            Log.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
+        }
+
+        if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
+            this._websocket.send(this._encode_message());
+            this._sQlen = 0;
+        }
+    },
+
+    send: function (arr) {
+        this._sQ.set(arr, this._sQlen);
+        this._sQlen += arr.length;
+        this.flush();
+    },
+
+    send_string: function (str) {
+        this.send(str.split('').map(function (chr) {
+            return chr.charCodeAt(0);
+        }));
+    },
+
+    // Event Handlers
+    off: function (evt) {
+        this._eventHandlers[evt] = function () {};
+    },
+
+    on: function (evt, handler) {
+        this._eventHandlers[evt] = handler;
+    },
+
+    _allocate_buffers: function () {
+        this._rQ = new Uint8Array(this._rQbufferSize);
+        this._sQ = new Uint8Array(this._sQbufferSize);
+    },
+
+    init: function () {
+        this._allocate_buffers();
+        this._rQi = 0;
+        this._websocket = null;
+    },
+
+    open: function (uri, protocols) {
+        var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
+        this.init();
+
+        this._websocket = new WebSocket(uri, protocols);
+        this._websocket.binaryType = 'arraybuffer';
+
+        this._websocket.onmessage = this._recv_message.bind(this);
+        this._websocket.onopen = (function () {
+            Log.Debug('>> WebSock.onopen');
+            if (this._websocket.protocol) {
+                Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
+            }
+
+            this._eventHandlers.open();
+            Log.Debug("<< WebSock.onopen");
+        }).bind(this);
+        this._websocket.onclose = (function (e) {
+            Log.Debug(">> WebSock.onclose");
+            this._eventHandlers.close(e);
+            Log.Debug("<< WebSock.onclose");
+        }).bind(this);
+        this._websocket.onerror = (function (e) {
+            Log.Debug(">> WebSock.onerror: " + e);
+            this._eventHandlers.error(e);
+            Log.Debug("<< WebSock.onerror: " + e);
+        }).bind(this);
+    },
+
+    close: function () {
+        if (this._websocket) {
+            if ((this._websocket.readyState === WebSocket.OPEN) ||
+                    (this._websocket.readyState === WebSocket.CONNECTING)) {
+                Log.Info("Closing WebSocket connection");
+                this._websocket.close();
+            }
+
+            this._websocket.onmessage = function (e) { return; };
+        }
+    },
+
+    // private methods
+    _encode_message: function () {
+        // Put in a binary arraybuffer
+        // according to the spec, you can send ArrayBufferViews with the send method
+        return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
+    },
+
+    _expand_compact_rQ: function (min_fit) {
+        var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
+        if (resizeNeeded) {
+            if (!min_fit) {
+                // just double the size if we need to do compaction
+                this._rQbufferSize *= 2;
+            } else {
+                // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
+                this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
+            }
+        }
+
+        // we don't want to grow unboundedly
+        if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
+            this._rQbufferSize = MAX_RQ_GROW_SIZE;
+            if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
+                throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
+            }
+        }
+
+        if (resizeNeeded) {
+            var old_rQbuffer = this._rQ.buffer;
+            this._rQmax = this._rQbufferSize / 8;
+            this._rQ = new Uint8Array(this._rQbufferSize);
+            this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
+        } else {
+            if (ENABLE_COPYWITHIN) {
+                this._rQ.copyWithin(0, this._rQi);
+            } else {
+                this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
+            }
+        }
+
+        this._rQlen = this._rQlen - this._rQi;
+        this._rQi = 0;
+    },
+
+    _decode_message: function (data) {
+        // push arraybuffer values onto the end
+        var u8 = new Uint8Array(data);
+        if (u8.length > this._rQbufferSize - this._rQlen) {
+            this._expand_compact_rQ(u8.length);
+        }
+        this._rQ.set(u8, this._rQlen);
+        this._rQlen += u8.length;
+    },
+
+    _recv_message: function (e) {
+        try {
+            this._decode_message(e.data);
+            if (this.rQlen() > 0) {
+                this._eventHandlers.message();
+                // Compact the receive queue
+                if (this._rQlen == this._rQi) {
+                    this._rQlen = 0;
+                    this._rQi = 0;
+                } else if (this._rQlen > this._rQmax) {
+                    this._expand_compact_rQ();
+                }
+            } else {
+                Log.Debug("Ignoring empty message");
+            }
+        } catch (exc) {
+            var exception_str = "";
+            if (exc.name) {
+                exception_str += "\n    name: " + exc.name + "\n";
+                exception_str += "    message: " + exc.message + "\n";
+            }
+
+            if (typeof exc.description !== 'undefined') {
+                exception_str += "    description: " + exc.description + "\n";
+            }
+
+            if (typeof exc.stack !== 'undefined') {
+                exception_str += exc.stack;
+            }
+
+            if (exception_str.length > 0) {
+                Log.Error("recv_message, caught exception: " + exception_str);
+            } else {
+                Log.Error("recv_message, caught exception: " + exc);
+            }
+
+            if (typeof exc.name !== 'undefined') {
+                this._eventHandlers.error(exc.name + ": " + exc.message);
+            } else {
+                this._eventHandlers.error(exc);
+            }
+        }
+    }
+};

+ 0 - 0
static/novnc/vendor/browser-es-module-loader/.npmignore


+ 15 - 0
static/novnc/vendor/browser-es-module-loader/README.md

@@ -0,0 +1,15 @@
+Custom Browser ES Module Loader
+===============================
+
+This is a module loader using babel and the ES Module Loader polyfill.
+It's based heavily on
+https://github.com/ModuleLoader/browser-es-module-loader, but uses
+WebWorkers to compile the modules in the background.
+
+To generate, run `rollup -c` in this directory, and then run `browserify
+src/babel-worker.js > dist/babel-worker.js`.
+
+LICENSE
+-------
+
+MIT

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 7027 - 0
static/novnc/vendor/browser-es-module-loader/dist/babel-worker.js


+ 1429 - 0
static/novnc/vendor/browser-es-module-loader/dist/browser-es-module-loader.js

@@ -0,0 +1,1429 @@
+(function (global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+	typeof define === 'function' && define.amd ? define(factory) :
+	(global.BrowserESModuleLoader = factory());
+}(this, (function () { 'use strict';
+
+/*
+ * Environment
+ */
+var isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
+var isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
+var isWindows = typeof process !== 'undefined' && typeof process.platform === 'string' && process.platform.match(/^win/);
+
+var envGlobal = typeof self !== 'undefined' ? self : global;
+/*
+ * Simple Symbol() shim
+ */
+var hasSymbol = typeof Symbol !== 'undefined';
+function createSymbol (name) {
+  return hasSymbol ? Symbol() : '@@' + name;
+}
+
+
+
+
+
+/*
+ * Environment baseURI
+ */
+var baseURI;
+
+// environent baseURI detection
+if (typeof document != 'undefined' && document.getElementsByTagName) {
+  baseURI = document.baseURI;
+
+  if (!baseURI) {
+    var bases = document.getElementsByTagName('base');
+    baseURI = bases[0] && bases[0].href || window.location.href;
+  }
+}
+else if (typeof location != 'undefined') {
+  baseURI = location.href;
+}
+
+// sanitize out the hash and querystring
+if (baseURI) {
+  baseURI = baseURI.split('#')[0].split('?')[0];
+  var slashIndex = baseURI.lastIndexOf('/');
+  if (slashIndex !== -1)
+    baseURI = baseURI.substr(0, slashIndex + 1);
+}
+else if (typeof process !== 'undefined' && process.cwd) {
+  baseURI = 'file://' + (isWindows ? '/' : '') + process.cwd();
+  if (isWindows)
+    baseURI = baseURI.replace(/\\/g, '/');
+}
+else {
+  throw new TypeError('No environment baseURI');
+}
+
+// ensure baseURI has trailing "/"
+if (baseURI[baseURI.length - 1] !== '/')
+  baseURI += '/';
+
+/*
+ * LoaderError with chaining for loader stacks
+ */
+var errArgs = new Error(0, '_').fileName == '_';
+function LoaderError__Check_error_message_for_loader_stack (childErr, newMessage) {
+  // Convert file:/// URLs to paths in Node
+  if (!isBrowser)
+    newMessage = newMessage.replace(isWindows ? /file:\/\/\//g : /file:\/\//g, '');
+
+  var message = (childErr.message || childErr) + '\n  ' + newMessage;
+
+  var err;
+  if (errArgs && childErr.fileName)
+    err = new Error(message, childErr.fileName, childErr.lineNumber);
+  else
+    err = new Error(message);
+
+
+  var stack = childErr.originalErr ? childErr.originalErr.stack : childErr.stack;
+
+  if (isNode)
+    // node doesn't show the message otherwise
+    err.stack = message + '\n  ' + stack;
+  else
+    err.stack = stack;
+
+  err.originalErr = childErr.originalErr || childErr;
+
+  return err;
+}
+
+var resolvedPromise = Promise.resolve();
+
+/*
+ * Simple Array values shim
+ */
+function arrayValues (arr) {
+  if (arr.values)
+    return arr.values();
+
+  if (typeof Symbol === 'undefined' || !Symbol.iterator)
+    throw new Error('Symbol.iterator not supported in this browser');
+
+  var iterable = {};
+  iterable[Symbol.iterator] = function () {
+    var keys = Object.keys(arr);
+    var keyIndex = 0;
+    return {
+      next: function () {
+        if (keyIndex < keys.length)
+          return {
+            value: arr[keys[keyIndex++]],
+            done: false
+          };
+        else
+          return {
+            value: undefined,
+            done: true
+          };
+      }
+    };
+  };
+  return iterable;
+}
+
+/*
+ * 3. Reflect.Loader
+ *
+ * We skip the entire native internal pipeline, just providing the bare API
+ */
+// 3.1.1
+function Loader () {
+  this.registry = new Registry();
+}
+// 3.3.1
+Loader.prototype.constructor = Loader;
+
+function ensureInstantiated (module) {
+  if (!(module instanceof ModuleNamespace))
+    throw new TypeError('Module instantiation did not return a valid namespace object.');
+  return module;
+}
+
+// 3.3.2
+Loader.prototype.import = function (key, parent) {
+  if (typeof key !== 'string')
+    throw new TypeError('Loader import method must be passed a module key string');
+  // custom resolveInstantiate combined hook for better perf
+  var loader = this;
+  return resolvedPromise
+  .then(function () {
+    return loader[RESOLVE_INSTANTIATE](key, parent);
+  })
+  .then(ensureInstantiated)
+  //.then(Module.evaluate)
+  .catch(function (err) {
+    throw LoaderError__Check_error_message_for_loader_stack(err, 'Loading ' + key + (parent ? ' from ' + parent : ''));
+  });
+};
+// 3.3.3
+var RESOLVE = Loader.resolve = createSymbol('resolve');
+
+/*
+ * Combined resolve / instantiate hook
+ *
+ * Not in current reduced spec, but necessary to separate RESOLVE from RESOLVE + INSTANTIATE as described
+ * in the spec notes of this repo to ensure that loader.resolve doesn't instantiate when not wanted.
+ *
+ * We implement RESOLVE_INSTANTIATE as a single hook instead of a separate INSTANTIATE in order to avoid
+ * the need for double registry lookups as a performance optimization.
+ */
+var RESOLVE_INSTANTIATE = Loader.resolveInstantiate = createSymbol('resolveInstantiate');
+
+// default resolveInstantiate is just to call resolve and then get from the registry
+// this provides compatibility for the resolveInstantiate optimization
+Loader.prototype[RESOLVE_INSTANTIATE] = function (key, parent) {
+  var loader = this;
+  return loader.resolve(key, parent)
+  .then(function (resolved) {
+    return loader.registry.get(resolved);
+  });
+};
+
+function ensureResolution (resolvedKey) {
+  if (resolvedKey === undefined)
+    throw new RangeError('No resolution found.');
+  return resolvedKey;
+}
+
+Loader.prototype.resolve = function (key, parent) {
+  var loader = this;
+  return resolvedPromise
+  .then(function() {
+    return loader[RESOLVE](key, parent);
+  })
+  .then(ensureResolution)
+  .catch(function (err) {
+    throw LoaderError__Check_error_message_for_loader_stack(err, 'Resolving ' + key + (parent ? ' to ' + parent : ''));
+  });
+};
+
+// 3.3.4 (import without evaluate)
+// this is not documented because the use of deferred evaluation as in Module.evaluate is not
+// documented, as it is not considered a stable feature to be encouraged
+// Loader.prototype.load may well be deprecated if this stays disabled
+/* Loader.prototype.load = function (key, parent) {
+  return Promise.resolve(this[RESOLVE_INSTANTIATE](key, parent || this.key))
+  .catch(function (err) {
+    throw addToError(err, 'Loading ' + key + (parent ? ' from ' + parent : ''));
+  });
+}; */
+
+/*
+ * 4. Registry
+ *
+ * Instead of structuring through a Map, just use a dictionary object
+ * We throw for construction attempts so this doesn't affect the public API
+ *
+ * Registry has been adjusted to use Namespace objects over ModuleStatus objects
+ * as part of simplifying loader API implementation
+ */
+var iteratorSupport = typeof Symbol !== 'undefined' && Symbol.iterator;
+var REGISTRY = createSymbol('registry');
+function Registry() {
+  this[REGISTRY] = {};
+  this._registry = REGISTRY;
+}
+// 4.4.1
+if (iteratorSupport) {
+  // 4.4.2
+  Registry.prototype[Symbol.iterator] = function () {
+    return this.entries()[Symbol.iterator]();
+  };
+
+  // 4.4.3
+  Registry.prototype.entries = function () {
+    var registry = this[REGISTRY];
+    return arrayValues(Object.keys(registry).map(function (key) {
+      return [key, registry[key]];
+    }));
+  };
+}
+
+// 4.4.4
+Registry.prototype.keys = function () {
+  return arrayValues(Object.keys(this[REGISTRY]));
+};
+// 4.4.5
+Registry.prototype.values = function () {
+  var registry = this[REGISTRY];
+  return arrayValues(Object.keys(registry).map(function (key) {
+    return registry[key];
+  }));
+};
+// 4.4.6
+Registry.prototype.get = function (key) {
+  return this[REGISTRY][key];
+};
+// 4.4.7
+Registry.prototype.set = function (key, namespace) {
+  if (!(namespace instanceof ModuleNamespace))
+    throw new Error('Registry must be set with an instance of Module Namespace');
+  this[REGISTRY][key] = namespace;
+  return this;
+};
+// 4.4.8
+Registry.prototype.has = function (key) {
+  return Object.hasOwnProperty.call(this[REGISTRY], key);
+};
+// 4.4.9
+Registry.prototype.delete = function (key) {
+  if (Object.hasOwnProperty.call(this[REGISTRY], key)) {
+    delete this[REGISTRY][key];
+    return true;
+  }
+  return false;
+};
+
+/*
+ * Simple ModuleNamespace Exotic object based on a baseObject
+ * We export this for allowing a fast-path for module namespace creation over Module descriptors
+ */
+// var EVALUATE = createSymbol('evaluate');
+var BASE_OBJECT = createSymbol('baseObject');
+
+// 8.3.1 Reflect.Module
+/*
+ * Best-effort simplified non-spec implementation based on
+ * a baseObject referenced via getters.
+ *
+ * Allows:
+ *
+ *   loader.registry.set('x', new Module({ default: 'x' }));
+ *
+ * Optional evaluation function provides experimental Module.evaluate
+ * support for non-executed modules in registry.
+ */
+function ModuleNamespace (baseObject/*, evaluate*/) {
+  Object.defineProperty(this, BASE_OBJECT, {
+    value: baseObject
+  });
+
+  // evaluate defers namespace population
+  /* if (evaluate) {
+    Object.defineProperty(this, EVALUATE, {
+      value: evaluate,
+      configurable: true,
+      writable: true
+    });
+  }
+  else { */
+    Object.keys(baseObject).forEach(extendNamespace, this);
+  //}
+}
+// 8.4.2
+ModuleNamespace.prototype = Object.create(null);
+
+if (typeof Symbol !== 'undefined' && Symbol.toStringTag)
+  Object.defineProperty(ModuleNamespace.prototype, Symbol.toStringTag, {
+    value: 'Module'
+  });
+
+function extendNamespace (key) {
+  Object.defineProperty(this, key, {
+    enumerable: true,
+    get: function () {
+      return this[BASE_OBJECT][key];
+    }
+  });
+}
+
+/* function doEvaluate (evaluate, context) {
+  try {
+    evaluate.call(context);
+  }
+  catch (e) {
+    return e;
+  }
+}
+
+// 8.4.1 Module.evaluate... not documented or used because this is potentially unstable
+Module.evaluate = function (ns) {
+  var evaluate = ns[EVALUATE];
+  if (evaluate) {
+    ns[EVALUATE] = undefined;
+    var err = doEvaluate(evaluate);
+    if (err) {
+      // cache the error
+      ns[EVALUATE] = function () {
+        throw err;
+      };
+      throw err;
+    }
+    Object.keys(ns[BASE_OBJECT]).forEach(extendNamespace, ns);
+  }
+  // make chainable
+  return ns;
+}; */
+
+/*
+ * Optimized URL normalization assuming a syntax-valid URL parent
+ */
+function throwResolveError (relUrl, parentUrl) {
+  throw new RangeError('Unable to resolve "' + relUrl + '" to ' + parentUrl);
+}
+function resolveIfNotPlain (relUrl, parentUrl) {
+  relUrl = relUrl.trim();
+  var parentProtocol = parentUrl && parentUrl.substr(0, parentUrl.indexOf(':') + 1);
+
+  var firstChar = relUrl[0];
+  var secondChar = relUrl[1];
+
+  // protocol-relative
+  if (firstChar === '/' && secondChar === '/') {
+    if (!parentProtocol)
+      throwResolveError(relUrl, parentUrl);
+    return parentProtocol + relUrl;
+  }
+  // relative-url
+  else if (firstChar === '.' && (secondChar === '/' || secondChar === '.' && (relUrl[2] === '/' || relUrl.length === 2) || relUrl.length === 1)
+      || firstChar === '/') {
+    var parentIsPlain = !parentProtocol || parentUrl[parentProtocol.length] !== '/';
+
+    // read pathname from parent if a URL
+    // pathname taken to be part after leading "/"
+    var pathname;
+    if (parentIsPlain) {
+      // resolving to a plain parent -> skip standard URL prefix, and treat entire parent as pathname
+      if (parentUrl === undefined)
+        throwResolveError(relUrl, parentUrl);
+      pathname = parentUrl;
+    }
+    else if (parentUrl[parentProtocol.length + 1] === '/') {
+      // resolving to a :// so we need to read out the auth and host
+      if (parentProtocol !== 'file:') {
+        pathname = parentUrl.substr(parentProtocol.length + 2);
+        pathname = pathname.substr(pathname.indexOf('/') + 1);
+      }
+      else {
+        pathname = parentUrl.substr(8);
+      }
+    }
+    else {
+      // resolving to :/ so pathname is the /... part
+      pathname = parentUrl.substr(parentProtocol.length + 1);
+    }
+
+    if (firstChar === '/') {
+      if (parentIsPlain)
+        throwResolveError(relUrl, parentUrl);
+      else
+        return parentUrl.substr(0, parentUrl.length - pathname.length - 1) + relUrl;
+    }
+
+    // join together and split for removal of .. and . segments
+    // looping the string instead of anything fancy for perf reasons
+    // '../../../../../z' resolved to 'x/y' is just 'z' regardless of parentIsPlain
+    var segmented = pathname.substr(0, pathname.lastIndexOf('/') + 1) + relUrl;
+
+    var output = [];
+    var segmentIndex = undefined;
+
+    for (var i = 0; i < segmented.length; i++) {
+      // busy reading a segment - only terminate on '/'
+      if (segmentIndex !== undefined) {
+        if (segmented[i] === '/') {
+          output.push(segmented.substr(segmentIndex, i - segmentIndex + 1));
+          segmentIndex = undefined;
+        }
+        continue;
+      }
+
+      // new segment - check if it is relative
+      if (segmented[i] === '.') {
+        // ../ segment
+        if (segmented[i + 1] === '.' && (segmented[i + 2] === '/' || i === segmented.length - 2)) {
+          output.pop();
+          i += 2;
+        }
+        // ./ segment
+        else if (segmented[i + 1] === '/' || i === segmented.length - 1) {
+          i += 1;
+        }
+        else {
+          // the start of a new segment as below
+          segmentIndex = i;
+          continue;
+        }
+
+        // this is the plain URI backtracking error (../, package:x -> error)
+        if (parentIsPlain && output.length === 0)
+          throwResolveError(relUrl, parentUrl);
+
+        // trailing . or .. segment
+        if (i === segmented.length)
+          output.push('');
+        continue;
+      }
+
+      // it is the start of a new segment
+      segmentIndex = i;
+    }
+    // finish reading out the last segment
+    if (segmentIndex !== undefined)
+      output.push(segmented.substr(segmentIndex, segmented.length - segmentIndex));
+
+    return parentUrl.substr(0, parentUrl.length - pathname.length) + output.join('');
+  }
+
+  // sanitizes and verifies (by returning undefined if not a valid URL-like form)
+  // Windows filepath compatibility is an added convenience here
+  var protocolIndex = relUrl.indexOf(':');
+  if (protocolIndex !== -1) {
+    if (isNode) {
+      // C:\x becomes file:///c:/x (we don't support C|\x)
+      if (relUrl[1] === ':' && relUrl[2] === '\\' && relUrl[0].match(/[a-z]/i))
+        return 'file:///' + relUrl.replace(/\\/g, '/');
+    }
+    return relUrl;
+  }
+}
+
+/*
+ * Register Loader
+ *
+ * Builds directly on top of loader polyfill to provide:
+ * - loader.register support
+ * - hookable higher-level resolve
+ * - instantiate hook returning a ModuleNamespace or undefined for es module loading
+ * - loader error behaviour as in HTML and loader specs, clearing failed modules from registration cache synchronously
+ * - build tracing support by providing a .trace=true and .loads object format
+ */
+
+var REGISTER_INTERNAL = createSymbol('register-internal');
+
+function RegisterLoader$1 () {
+  Loader.call(this);
+
+  var registryDelete = this.registry.delete;
+  this.registry.delete = function (key) {
+    var deleted = registryDelete.call(this, key);
+
+    // also delete from register registry if linked
+    if (records.hasOwnProperty(key) && !records[key].linkRecord)
+      delete records[key];
+
+    return deleted;
+  };
+
+  var records = {};
+
+  this[REGISTER_INTERNAL] = {
+    // last anonymous System.register call
+    lastRegister: undefined,
+    // in-flight es module load records
+    records: records
+  };
+
+  // tracing
+  this.trace = false;
+}
+
+RegisterLoader$1.prototype = Object.create(Loader.prototype);
+RegisterLoader$1.prototype.constructor = RegisterLoader$1;
+
+var INSTANTIATE = RegisterLoader$1.instantiate = createSymbol('instantiate');
+
+// default normalize is the WhatWG style normalizer
+RegisterLoader$1.prototype[RegisterLoader$1.resolve = Loader.resolve] = function (key, parentKey) {
+  return resolveIfNotPlain(key, parentKey || baseURI);
+};
+
+RegisterLoader$1.prototype[INSTANTIATE] = function (key, processAnonRegister) {};
+
+// once evaluated, the linkRecord is set to undefined leaving just the other load record properties
+// this allows tracking new binding listeners for es modules through importerSetters
+// for dynamic modules, the load record is removed entirely.
+function createLoadRecord (state, key, registration) {
+  return state.records[key] = {
+    key: key,
+
+    // defined System.register cache
+    registration: registration,
+
+    // module namespace object
+    module: undefined,
+
+    // es-only
+    // this sticks around so new module loads can listen to binding changes
+    // for already-loaded modules by adding themselves to their importerSetters
+    importerSetters: undefined,
+
+    // in-flight linking record
+    linkRecord: {
+      // promise for instantiated
+      instantiatePromise: undefined,
+      dependencies: undefined,
+      execute: undefined,
+      executingRequire: false,
+
+      // underlying module object bindings
+      moduleObj: undefined,
+
+      // es only, also indicates if es or not
+      setters: undefined,
+
+      // promise for instantiated dependencies (dependencyInstantiations populated)
+      depsInstantiatePromise: undefined,
+      // will be the array of dependency load record or a module namespace
+      dependencyInstantiations: undefined,
+
+      // indicates if the load and all its dependencies are instantiated and linked
+      // but not yet executed
+      // mostly just a performance shortpath to avoid rechecking the promises above
+      linked: false,
+
+      error: undefined
+      // NB optimization and way of ensuring module objects in setters
+      // indicates setters which should run pre-execution of that dependency
+      // setters is then just for completely executed module objects
+      // alternatively we just pass the partially filled module objects as
+      // arguments into the execute function
+      // hoisted: undefined
+    }
+  };
+}
+
+RegisterLoader$1.prototype[Loader.resolveInstantiate] = function (key, parentKey) {
+  var loader = this;
+  var state = this[REGISTER_INTERNAL];
+  var registry = loader.registry[loader.registry._registry];
+
+  return resolveInstantiate(loader, key, parentKey, registry, state)
+  .then(function (instantiated) {
+    if (instantiated instanceof ModuleNamespace)
+      return instantiated;
+
+    // if already beaten to linked, return
+    if (instantiated.module)
+      return instantiated.module;
+
+    // resolveInstantiate always returns a load record with a link record and no module value
+    if (instantiated.linkRecord.linked)
+      return ensureEvaluate(loader, instantiated, instantiated.linkRecord, registry, state, undefined);
+
+    return instantiateDeps(loader, instantiated, instantiated.linkRecord, registry, state, [instantiated])
+    .then(function () {
+      return ensureEvaluate(loader, instantiated, instantiated.linkRecord, registry, state, undefined);
+    })
+    .catch(function (err) {
+      clearLoadErrors(loader, instantiated);
+      throw err;
+    });
+  });
+};
+
+function resolveInstantiate (loader, key, parentKey, registry, state) {
+  // normalization shortpath for already-normalized key
+  // could add a plain name filter, but doesn't yet seem necessary for perf
+  var module = registry[key];
+  if (module)
+    return Promise.resolve(module);
+
+  var load = state.records[key];
+
+  // already linked but not in main registry is ignored
+  if (load && !load.module)
+    return instantiate(loader, load, load.linkRecord, registry, state);
+
+  return loader.resolve(key, parentKey)
+  .then(function (resolvedKey) {
+    // main loader registry always takes preference
+    module = registry[resolvedKey];
+    if (module)
+      return module;
+
+    load = state.records[resolvedKey];
+
+    // already has a module value but not already in the registry (load.module)
+    // means it was removed by registry.delete, so we should
+    // disgard the current load record creating a new one over it
+    // but keep any existing registration
+    if (!load || load.module)
+      load = createLoadRecord(state, resolvedKey, load && load.registration);
+
+    var link = load.linkRecord;
+    if (!link)
+      return load;
+
+    return instantiate(loader, load, link, registry, state);
+  });
+}
+
+function createProcessAnonRegister (loader, load, state) {
+  return function () {
+    var lastRegister = state.lastRegister;
+
+    if (!lastRegister)
+      return !!load.registration;
+
+    state.lastRegister = undefined;
+    load.registration = lastRegister;
+
+    return true;
+  };
+}
+
+function instantiate (loader, load, link, registry, state) {
+  return link.instantiatePromise || (link.instantiatePromise =
+  // if there is already an existing registration, skip running instantiate
+  (load.registration ? Promise.resolve() : Promise.resolve().then(function () {
+    state.lastRegister = undefined;
+    return loader[INSTANTIATE](load.key, loader[INSTANTIATE].length > 1 && createProcessAnonRegister(loader, load, state));
+  }))
+  .then(function (instantiation) {
+    // direct module return from instantiate -> we're done
+    if (instantiation !== undefined) {
+      if (!(instantiation instanceof ModuleNamespace))
+        throw new TypeError('Instantiate did not return a valid Module object.');
+
+      delete state.records[load.key];
+      if (loader.trace)
+        traceLoad(loader, load, link);
+      return registry[load.key] = instantiation;
+    }
+
+    // run the cached loader.register declaration if there is one
+    var registration = load.registration;
+    // clear to allow new registrations for future loads (combined with registry delete)
+    load.registration = undefined;
+    if (!registration)
+      throw new TypeError('Module instantiation did not call an anonymous or correctly named System.register.');
+
+    link.dependencies = registration[0];
+
+    load.importerSetters = [];
+
+    link.moduleObj = {};
+
+    // process System.registerDynamic declaration
+    if (registration[2]) {
+      link.moduleObj.default = {};
+      link.moduleObj.__useDefault = true;
+      link.executingRequire = registration[1];
+      link.execute = registration[2];
+    }
+
+    // process System.register declaration
+    else {
+      registerDeclarative(loader, load, link, registration[1]);
+    }
+
+    // shortpath to instantiateDeps
+    if (!link.dependencies.length) {
+      link.linked = true;
+      if (loader.trace)
+        traceLoad(loader, load, link);
+    }
+
+    return load;
+  })
+  .catch(function (err) {
+    throw link.error = LoaderError__Check_error_message_for_loader_stack(err, 'Instantiating ' + load.key);
+  }));
+}
+
+// like resolveInstantiate, but returning load records for linking
+function resolveInstantiateDep (loader, key, parentKey, registry, state, traceDepMap) {
+  // normalization shortpaths for already-normalized key
+  // DISABLED to prioritise consistent resolver calls
+  // could add a plain name filter, but doesn't yet seem necessary for perf
+  /* var load = state.records[key];
+  var module = registry[key];
+
+  if (module) {
+    if (traceDepMap)
+      traceDepMap[key] = key;
+
+    // registry authority check in case module was deleted or replaced in main registry
+    if (load && load.module && load.module === module)
+      return load;
+    else
+      return module;
+  }
+
+  // already linked but not in main registry is ignored
+  if (load && !load.module) {
+    if (traceDepMap)
+      traceDepMap[key] = key;
+    return instantiate(loader, load, load.linkRecord, registry, state);
+  } */
+  return loader.resolve(key, parentKey)
+  .then(function (resolvedKey) {
+    if (traceDepMap)
+      traceDepMap[key] = resolvedKey;
+
+    // normalization shortpaths for already-normalized key
+    var load = state.records[resolvedKey];
+    var module = registry[resolvedKey];
+
+    // main loader registry always takes preference
+    if (module && (!load || load.module && module !== load.module))
+      return module;
+
+    // already has a module value but not already in the registry (load.module)
+    // means it was removed by registry.delete, so we should
+    // disgard the current load record creating a new one over it
+    // but keep any existing registration
+    if (!load || !module && load.module)
+      load = createLoadRecord(state, resolvedKey, load && load.registration);
+
+    var link = load.linkRecord;
+    if (!link)
+      return load;
+
+    return instantiate(loader, load, link, registry, state);
+  });
+}
+
+function traceLoad (loader, load, link) {
+  loader.loads = loader.loads || {};
+  loader.loads[load.key] = {
+    key: load.key,
+    deps: link.dependencies,
+    depMap: link.depMap || {}
+  };
+}
+
+/*
+ * Convert a CJS module.exports into a valid object for new Module:
+ *
+ *   new Module(getEsModule(module.exports))
+ *
+ * Sets the default value to the module, while also reading off named exports carefully.
+ */
+function registerDeclarative (loader, load, link, declare) {
+  var moduleObj = link.moduleObj;
+  var importerSetters = load.importerSetters;
+
+  var locked = false;
+
+  // closure especially not based on link to allow link record disposal
+  var declared = declare.call(envGlobal, function (name, value) {
+    // export setter propogation with locking to avoid cycles
+    if (locked)
+      return;
+
+    if (typeof name === 'object') {
+      for (var p in name)
+        if (p !== '__useDefault')
+          moduleObj[p] = name[p];
+    }
+    else {
+      moduleObj[name] = value;
+    }
+
+    locked = true;
+    for (var i = 0; i < importerSetters.length; i++)
+      importerSetters[i](moduleObj);
+    locked = false;
+
+    return value;
+  }, new ContextualLoader(loader, load.key));
+
+  link.setters = declared.setters;
+  link.execute = declared.execute;
+  if (declared.exports)
+    link.moduleObj = moduleObj = declared.exports;
+}
+
+function instantiateDeps (loader, load, link, registry, state, seen) {
+  return (link.depsInstantiatePromise || (link.depsInstantiatePromise = Promise.resolve()
+  .then(function () {
+    var depsInstantiatePromises = Array(link.dependencies.length);
+
+    for (var i = 0; i < link.dependencies.length; i++)
+      depsInstantiatePromises[i] = resolveInstantiateDep(loader, link.dependencies[i], load.key, registry, state, loader.trace && link.depMap || (link.depMap = {}));
+
+    return Promise.all(depsInstantiatePromises);
+  })
+  .then(function (dependencyInstantiations) {
+    link.dependencyInstantiations = dependencyInstantiations;
+
+    // run setters to set up bindings to instantiated dependencies
+    if (link.setters) {
+      for (var i = 0; i < dependencyInstantiations.length; i++) {
+        var setter = link.setters[i];
+        if (setter) {
+          var instantiation = dependencyInstantiations[i];
+
+          if (instantiation instanceof ModuleNamespace) {
+            setter(instantiation);
+          }
+          else {
+            setter(instantiation.module || instantiation.linkRecord.moduleObj);
+            // this applies to both es and dynamic registrations
+            if (instantiation.importerSetters)
+              instantiation.importerSetters.push(setter);
+          }
+        }
+      }
+    }
+  })))
+  .then(function () {
+    // now deeply instantiateDeps on each dependencyInstantiation that is a load record
+    var deepDepsInstantiatePromises = [];
+
+    for (var i = 0; i < link.dependencies.length; i++) {
+      var depLoad = link.dependencyInstantiations[i];
+      var depLink = depLoad.linkRecord;
+
+      if (!depLink || depLink.linked)
+        continue;
+
+      if (seen.indexOf(depLoad) !== -1)
+        continue;
+      seen.push(depLoad);
+
+      deepDepsInstantiatePromises.push(instantiateDeps(loader, depLoad, depLoad.linkRecord, registry, state, seen));
+    }
+
+    return Promise.all(deepDepsInstantiatePromises);
+  })
+  .then(function () {
+    // as soon as all dependencies instantiated, we are ready for evaluation so can add to the registry
+    // this can run multiple times, but so what
+    link.linked = true;
+    if (loader.trace)
+      traceLoad(loader, load, link);
+
+    return load;
+  })
+  .catch(function (err) {
+    err = LoaderError__Check_error_message_for_loader_stack(err, 'Loading ' + load.key);
+
+    // throw up the instantiateDeps stack
+    // loads are then synchonously cleared at the top-level through the clearLoadErrors helper below
+    // this then ensures avoiding partially unloaded tree states
+    link.error = link.error || err;
+
+    throw err;
+  });
+}
+
+// clears an errored load and all its errored dependencies from the loads registry
+function clearLoadErrors (loader, load) {
+  var state = loader[REGISTER_INTERNAL];
+
+  // clear from loads
+  if (state.records[load.key] === load)
+    delete state.records[load.key];
+
+  var link = load.linkRecord;
+
+  if (!link)
+    return;
+
+  if (link.dependencyInstantiations)
+    link.dependencyInstantiations.forEach(function (depLoad, index) {
+      if (!depLoad || depLoad instanceof ModuleNamespace)
+        return;
+
+      if (depLoad.linkRecord) {
+        if (depLoad.linkRecord.error) {
+          // provides a circular reference check
+          if (state.records[depLoad.key] === depLoad)
+            clearLoadErrors(loader, depLoad);
+        }
+
+        // unregister setters for es dependency load records that will remain
+        if (link.setters && depLoad.importerSetters) {
+          var setterIndex = depLoad.importerSetters.indexOf(link.setters[index]);
+          depLoad.importerSetters.splice(setterIndex, 1);
+        }
+      }
+    });
+}
+
+/*
+ * System.register
+ */
+RegisterLoader$1.prototype.register = function (key, deps, declare) {
+  var state = this[REGISTER_INTERNAL];
+
+  // anonymous modules get stored as lastAnon
+  if (declare === undefined) {
+    state.lastRegister = [key, deps, undefined];
+  }
+
+  // everything else registers into the register cache
+  else {
+    var load = state.records[key] || createLoadRecord(state, key, undefined);
+    load.registration = [deps, declare, undefined];
+  }
+};
+
+/*
+ * System.registerDyanmic
+ */
+RegisterLoader$1.prototype.registerDynamic = function (key, deps, executingRequire, execute) {
+  var state = this[REGISTER_INTERNAL];
+
+  // anonymous modules get stored as lastAnon
+  if (typeof key !== 'string') {
+    state.lastRegister = [key, deps, executingRequire];
+  }
+
+  // everything else registers into the register cache
+  else {
+    var load = state.records[key] || createLoadRecord(state, key, undefined);
+    load.registration = [deps, executingRequire, execute];
+  }
+};
+
+// ContextualLoader class
+// backwards-compatible with previous System.register context argument by exposing .id
+function ContextualLoader (loader, key) {
+  this.loader = loader;
+  this.key = this.id = key;
+}
+ContextualLoader.prototype.constructor = function () {
+  throw new TypeError('Cannot subclass the contextual loader only Reflect.Loader.');
+};
+ContextualLoader.prototype.import = function (key) {
+  return this.loader.import(key, this.key);
+};
+ContextualLoader.prototype.resolve = function (key) {
+  return this.loader.resolve(key, this.key);
+};
+ContextualLoader.prototype.load = function (key) {
+  return this.loader.load(key, this.key);
+};
+
+// this is the execution function bound to the Module namespace record
+function ensureEvaluate (loader, load, link, registry, state, seen) {
+  if (load.module)
+    return load.module;
+
+  if (link.error)
+    throw link.error;
+
+  if (seen && seen.indexOf(load) !== -1)
+    return load.linkRecord.moduleObj;
+
+  // for ES loads we always run ensureEvaluate on top-level, so empty seen is passed regardless
+  // for dynamic loads, we pass seen if also dynamic
+  var err = doEvaluate(loader, load, link, registry, state, link.setters ? [] : seen || []);
+  if (err) {
+    clearLoadErrors(loader, load);
+    throw err;
+  }
+
+  return load.module;
+}
+
+function makeDynamicRequire (loader, key, dependencies, dependencyInstantiations, registry, state, seen) {
+  // we can only require from already-known dependencies
+  return function (name) {
+    for (var i = 0; i < dependencies.length; i++) {
+      if (dependencies[i] === name) {
+        var depLoad = dependencyInstantiations[i];
+        var module;
+
+        if (depLoad instanceof ModuleNamespace)
+          module = depLoad;
+        else
+          module = ensureEvaluate(loader, depLoad, depLoad.linkRecord, registry, state, seen);
+
+        return module.__useDefault ? module.default : module;
+      }
+    }
+    throw new Error('Module ' + name + ' not declared as a System.registerDynamic dependency of ' + key);
+  };
+}
+
+// ensures the given es load is evaluated
+// returns the error if any
+function doEvaluate (loader, load, link, registry, state, seen) {
+  seen.push(load);
+
+  var err;
+
+  // es modules evaluate dependencies first
+  // non es modules explicitly call moduleEvaluate through require
+  if (link.setters) {
+    var depLoad, depLink;
+    for (var i = 0; i < link.dependencies.length; i++) {
+      depLoad = link.dependencyInstantiations[i];
+
+      if (depLoad instanceof ModuleNamespace)
+        continue;
+
+      // custom Module returned from instantiate
+      depLink = depLoad.linkRecord;
+      if (depLink && seen.indexOf(depLoad) === -1) {
+        if (depLink.error)
+          err = depLink.error;
+        else
+          // dynamic / declarative boundaries clear the "seen" list
+          // we just let cross format circular throw as would happen in real implementations
+          err = doEvaluate(loader, depLoad, depLink, registry, state, depLink.setters ? seen : []);
+      }
+
+      if (err)
+        return link.error = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key);
+    }
+  }
+
+  // link.execute won't exist for Module returns from instantiate on top-level load
+  if (link.execute) {
+    // ES System.register execute
+    // "this" is null in ES
+    if (link.setters) {
+      err = declarativeExecute(link.execute);
+    }
+    // System.registerDynamic execute
+    // "this" is "exports" in CJS
+    else {
+      var module = { id: load.key };
+      var moduleObj = link.moduleObj;
+      Object.defineProperty(module, 'exports', {
+        configurable: true,
+        set: function (exports) {
+          moduleObj.default = exports;
+        },
+        get: function () {
+          return moduleObj.default;
+        }
+      });
+
+      var require = makeDynamicRequire(loader, load.key, link.dependencies, link.dependencyInstantiations, registry, state, seen);
+
+      // evaluate deps first
+      if (!link.executingRequire)
+        for (var i = 0; i < link.dependencies.length; i++)
+          require(link.dependencies[i]);
+
+      err = dynamicExecute(link.execute, require, moduleObj.default, module);
+
+      // pick up defineProperty calls to module.exports when we can
+      if (module.exports !== moduleObj.default)
+        moduleObj.default = module.exports;
+
+      var moduleDefault = moduleObj.default;
+
+      // __esModule flag extension support via lifting
+      if (moduleDefault && moduleDefault.__esModule) {
+        if (moduleObj.__useDefault)
+          delete moduleObj.__useDefault;
+        for (var p in moduleDefault) {
+          if (Object.hasOwnProperty.call(moduleDefault, p))
+            moduleObj[p] = moduleDefault[p];
+        }
+        moduleObj.__esModule = true;
+      }
+    }
+  }
+
+  if (err)
+    return link.error = LoaderError__Check_error_message_for_loader_stack(err, 'Evaluating ' + load.key);
+
+  registry[load.key] = load.module = new ModuleNamespace(link.moduleObj);
+
+  // if not an esm module, run importer setters and clear them
+  // this allows dynamic modules to update themselves into es modules
+  // as soon as execution has completed
+  if (!link.setters) {
+    if (load.importerSetters)
+      for (var i = 0; i < load.importerSetters.length; i++)
+        load.importerSetters[i](load.module);
+    load.importerSetters = undefined;
+  }
+
+  // dispose link record
+  load.linkRecord = undefined;
+}
+
+// {} is the closest we can get to call(undefined)
+var nullContext = {};
+if (Object.freeze)
+  Object.freeze(nullContext);
+
+function declarativeExecute (execute) {
+  try {
+    execute.call(nullContext);
+  }
+  catch (e) {
+    return e;
+  }
+}
+
+function dynamicExecute (execute, require, exports, module) {
+  try {
+    var output = execute.call(envGlobal, require, exports, module);
+    if (output !== undefined)
+      module.exports = output;
+  }
+  catch (e) {
+    return e;
+  }
+}
+
+var loader;
+
+// <script type="module"> support
+var anonSources = {};
+if (typeof document != 'undefined' && document.getElementsByTagName) {
+  var handleError = function(err) {
+    // dispatch an error event so that we can display in errors in browsers
+    // that don't yet support unhandledrejection
+    if (window.onunhandledrejection === undefined) {
+      try {
+        var evt = new Event('error');
+      } catch (_eventError) {
+        var evt = document.createEvent('Event');
+        evt.initEvent('error', true, true);
+      }
+      evt.message = err.message;
+      if (err.fileName) {
+        evt.filename = err.fileName;
+        evt.lineno = err.lineNumber;
+        evt.colno = err.columnNumber;
+      } else if (err.sourceURL) {
+        evt.filename = err.sourceURL;
+        evt.lineno = err.line;
+        evt.colno = err.column;
+      }
+      evt.error = err;
+      window.dispatchEvent(evt);
+    }
+
+    // throw so it still shows up in the console
+    throw err;
+  };
+
+  var ready = function() {
+    document.removeEventListener('DOMContentLoaded', ready, false );
+
+    var anonCnt = 0;
+
+    var scripts = document.getElementsByTagName('script');
+    for (var i = 0; i < scripts.length; i++) {
+      var script = scripts[i];
+      if (script.type == 'module' && !script.loaded) {
+        script.loaded = true;
+        if (script.src) {
+          loader.import(script.src).catch(handleError);
+        }
+        // anonymous modules supported via a custom naming scheme and registry
+        else {
+          var uri = './<anon' + ++anonCnt + '>';
+          if (script.id !== ""){
+            uri = "./" + script.id;
+          }
+
+          var anonName = resolveIfNotPlain(uri, baseURI);
+          anonSources[anonName] = script.innerHTML;
+          loader.import(anonName).catch(handleError);
+        }
+      }
+    }
+  };
+
+  // simple DOM ready
+  if (document.readyState === 'complete')
+    setTimeout(ready);
+  else
+    document.addEventListener('DOMContentLoaded', ready, false);
+}
+
+function BrowserESModuleLoader(baseKey) {
+  if (baseKey)
+    this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
+
+  RegisterLoader$1.call(this);
+
+  var loader = this;
+
+  // ensure System.register is available
+  envGlobal.System = envGlobal.System || {};
+  if (typeof envGlobal.System.register == 'function')
+    var prevRegister = envGlobal.System.register;
+  envGlobal.System.register = function() {
+    loader.register.apply(loader, arguments);
+    if (prevRegister)
+      prevRegister.apply(this, arguments);
+  };
+}
+BrowserESModuleLoader.prototype = Object.create(RegisterLoader$1.prototype);
+
+// normalize is never given a relative name like "./x", that part is already handled
+BrowserESModuleLoader.prototype[RegisterLoader$1.resolve] = function(key, parent) {
+  var resolved = RegisterLoader$1.prototype[RegisterLoader$1.resolve].call(this, key, parent || this.baseKey) || key;
+  if (!resolved)
+    throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
+
+  return resolved;
+};
+
+function xhrFetch(url, resolve, reject) {
+  var xhr = new XMLHttpRequest();
+  var load = function(source) {
+    resolve(xhr.responseText);
+  };
+  var error = function() {
+    reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText  : '') + ')' : '') + ' loading ' + url));
+  };
+
+  xhr.onreadystatechange = function () {
+    if (xhr.readyState === 4) {
+      // in Chrome on file:/// URLs, status is 0
+      if (xhr.status == 0) {
+        if (xhr.responseText) {
+          load();
+        }
+        else {
+          // when responseText is empty, wait for load or error event
+          // to inform if it is a 404 or empty file
+          xhr.addEventListener('error', error);
+          xhr.addEventListener('load', load);
+        }
+      }
+      else if (xhr.status === 200) {
+        load();
+      }
+      else {
+        error();
+      }
+    }
+  };
+  xhr.open("GET", url, true);
+  xhr.send(null);
+}
+
+var WorkerPool = function (script, size) {
+  script = document.currentScript.src.substr(0, document.currentScript.src.lastIndexOf("/")) + "/" + script;
+  this._workers = new Array(size);
+  this._ind = 0;
+  this._size = size;
+  this._jobs = 0;
+  this.onmessage = undefined;
+  this._stopTimeout = undefined;
+  for (var i = 0; i < size; i++) {
+    var wrkr = new Worker(script);
+    wrkr._count = 0;
+    wrkr._ind = i;
+    wrkr.onmessage = this._onmessage.bind(this, wrkr);
+    wrkr.onerror = this._onerror.bind(this);
+    this._workers[i] = wrkr;
+  }
+
+  this._checkJobs();
+};
+WorkerPool.prototype = {
+  postMessage: function (msg) {
+    if (this._stopTimeout !== undefined) {
+      clearTimeout(this._stopTimeout);
+      this._stopTimeout = undefined;
+    }
+    var wrkr = this._workers[this._ind % this._size];
+    wrkr._count++;
+    this._jobs++;
+    wrkr.postMessage(msg);
+    this._ind++;
+  },
+
+  _onmessage: function (wrkr, evt) {
+    wrkr._count--;
+    this._jobs--;
+    this.onmessage(evt, wrkr);
+    this._checkJobs();
+  },
+
+  _onerror: function(err) {
+    try {
+        var evt = new Event('error');
+    } catch (_eventError) {
+        var evt = document.createEvent('Event');
+        evt.initEvent('error', true, true);
+    }
+    evt.message = err.message;
+    evt.filename = err.filename;
+    evt.lineno = err.lineno;
+    evt.colno = err.colno;
+    evt.error = err.error;
+    window.dispatchEvent(evt);
+  },
+
+  _checkJobs: function () {
+    if (this._jobs === 0 && this._stopTimeout === undefined) {
+      // wait for 2s of inactivity before stopping (that should be enough for local loading)
+      this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
+    }
+  },
+
+  _stop: function () {
+    this._workers.forEach(function(wrkr) {
+      wrkr.terminate();
+    });
+  }
+};
+
+var promiseMap = new Map();
+var babelWorker = new WorkerPool('babel-worker.js', 3);
+babelWorker.onmessage = function (evt) {
+    var promFuncs = promiseMap.get(evt.data.key);
+    promFuncs.resolve(evt.data);
+    promiseMap.delete(evt.data.key);
+};
+
+// instantiate just needs to run System.register
+// so we fetch the source, convert into the Babel System module format, then evaluate it
+BrowserESModuleLoader.prototype[RegisterLoader$1.instantiate] = function(key, processAnonRegister) {
+  var loader = this;
+
+  // load as ES with Babel converting into System.register
+  return new Promise(function(resolve, reject) {
+    // anonymous module
+    if (anonSources[key]) {
+      resolve(anonSources[key]);
+      anonSources[key] = undefined;
+    }
+    // otherwise we fetch
+    else {
+      xhrFetch(key, resolve, reject);
+    }
+  })
+  .then(function(source) {
+    // check our cache first
+    var cacheEntry = localStorage.getItem(key);
+    if (cacheEntry) {
+      cacheEntry = JSON.parse(cacheEntry);
+      // TODO: store a hash instead
+      if (cacheEntry.source === source) {
+        return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
+      }
+    }
+    return new Promise(function (resolve, reject) {
+      promiseMap.set(key, {resolve: resolve, reject: reject});
+      babelWorker.postMessage({key: key, source: source});
+    });
+  }).then(function (data) {
+    // evaluate without require, exports and module variables
+    // we leave module in for now to allow module.require access
+    try {
+      var cacheEntry = JSON.stringify({source: data.source, code: data.code});
+      localStorage.setItem(key, cacheEntry);
+    } catch (e) {
+      if (window.console) {
+        window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
+      }
+    }
+    (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
+    processAnonRegister();
+  });
+};
+
+// create a default loader instance in the browser
+if (isBrowser)
+  loader = new BrowserESModuleLoader();
+
+return BrowserESModuleLoader;
+
+})));
+//# sourceMappingURL=browser-es-module-loader.js.map

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
static/novnc/vendor/browser-es-module-loader/dist/browser-es-module-loader.js.map


+ 16 - 0
static/novnc/vendor/browser-es-module-loader/rollup.config.js

@@ -0,0 +1,16 @@
+import nodeResolve from 'rollup-plugin-node-resolve';
+
+export default {
+  entry: 'src/browser-es-module-loader.js',
+  dest: 'dist/browser-es-module-loader.js',
+  format: 'umd',
+  moduleName: 'BrowserESModuleLoader',
+  sourceMap: true,
+
+  plugins: [
+    nodeResolve(),
+  ],
+
+  // skip rollup warnings (specifically the eval warning)
+  onwarn: function() {}
+};

+ 23 - 0
static/novnc/vendor/browser-es-module-loader/src/babel-worker.js

@@ -0,0 +1,23 @@
+/*import { transform as babelTransform } from 'babel-core';
+import babelTransformDynamicImport from 'babel-plugin-syntax-dynamic-import';
+import babelTransformES2015ModulesSystemJS from 'babel-plugin-transform-es2015-modules-systemjs';*/
+
+// sadly, due to how rollup works, we can't use es6 imports here
+var babelTransform = require('babel-core').transform;
+var babelTransformDynamicImport = require('babel-plugin-syntax-dynamic-import');
+var babelTransformES2015ModulesSystemJS = require('babel-plugin-transform-es2015-modules-systemjs');
+
+self.onmessage = function (evt) {
+    // transform source with Babel
+    var output = babelTransform(evt.data.source, {
+      compact: false,
+      filename: evt.data.key + '!transpiled',
+      sourceFileName: evt.data.key,
+      moduleIds: false,
+      sourceMaps: 'inline',
+      babelrc: false,
+      plugins: [babelTransformDynamicImport, babelTransformES2015ModulesSystemJS],
+    });
+
+    self.postMessage({key: evt.data.key, code: output.code, source: evt.data.source});
+};

+ 266 - 0
static/novnc/vendor/browser-es-module-loader/src/browser-es-module-loader.js

@@ -0,0 +1,266 @@
+import RegisterLoader from 'es-module-loader/core/register-loader.js';
+import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
+
+import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
+import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
+
+var loader;
+
+// <script type="module"> support
+var anonSources = {};
+if (typeof document != 'undefined' && document.getElementsByTagName) {
+  var handleError = function(err) {
+    // dispatch an error event so that we can display in errors in browsers
+    // that don't yet support unhandledrejection
+    if (window.onunhandledrejection === undefined) {
+      try {
+        var evt = new Event('error');
+      } catch (_eventError) {
+        var evt = document.createEvent('Event');
+        evt.initEvent('error', true, true);
+      }
+      evt.message = err.message;
+      if (err.fileName) {
+        evt.filename = err.fileName;
+        evt.lineno = err.lineNumber;
+        evt.colno = err.columnNumber;
+      } else if (err.sourceURL) {
+        evt.filename = err.sourceURL;
+        evt.lineno = err.line;
+        evt.colno = err.column;
+      }
+      evt.error = err;
+      window.dispatchEvent(evt);
+    }
+
+    // throw so it still shows up in the console
+    throw err;
+  }
+
+  var ready = function() {
+    document.removeEventListener('DOMContentLoaded', ready, false );
+
+    var anonCnt = 0;
+
+    var scripts = document.getElementsByTagName('script');
+    for (var i = 0; i < scripts.length; i++) {
+      var script = scripts[i];
+      if (script.type == 'module' && !script.loaded) {
+        script.loaded = true;
+        if (script.src) {
+          loader.import(script.src).catch(handleError);
+        }
+        // anonymous modules supported via a custom naming scheme and registry
+        else {
+          var uri = './<anon' + ++anonCnt + '>';
+          if (script.id !== ""){
+            uri = "./" + script.id;
+          }
+
+          var anonName = resolveIfNotPlain(uri, baseURI);
+          anonSources[anonName] = script.innerHTML;
+          loader.import(anonName).catch(handleError);
+        }
+      }
+    }
+  }
+
+  // simple DOM ready
+  if (document.readyState === 'complete')
+    setTimeout(ready);
+  else
+    document.addEventListener('DOMContentLoaded', ready, false);
+}
+
+function BrowserESModuleLoader(baseKey) {
+  if (baseKey)
+    this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
+
+  RegisterLoader.call(this);
+
+  var loader = this;
+
+  // ensure System.register is available
+  global.System = global.System || {};
+  if (typeof global.System.register == 'function')
+    var prevRegister = global.System.register;
+  global.System.register = function() {
+    loader.register.apply(loader, arguments);
+    if (prevRegister)
+      prevRegister.apply(this, arguments);
+  };
+}
+BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
+
+// normalize is never given a relative name like "./x", that part is already handled
+BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
+  var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
+  if (!resolved)
+    throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
+
+  return resolved;
+};
+
+function xhrFetch(url, resolve, reject) {
+  var xhr = new XMLHttpRequest();
+  var load = function(source) {
+    resolve(xhr.responseText);
+  }
+  var error = function() {
+    reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText  : '') + ')' : '') + ' loading ' + url));
+  }
+
+  xhr.onreadystatechange = function () {
+    if (xhr.readyState === 4) {
+      // in Chrome on file:/// URLs, status is 0
+      if (xhr.status == 0) {
+        if (xhr.responseText) {
+          load();
+        }
+        else {
+          // when responseText is empty, wait for load or error event
+          // to inform if it is a 404 or empty file
+          xhr.addEventListener('error', error);
+          xhr.addEventListener('load', load);
+        }
+      }
+      else if (xhr.status === 200) {
+        load();
+      }
+      else {
+        error();
+      }
+    }
+  };
+  xhr.open("GET", url, true);
+  xhr.send(null);
+}
+
+var WorkerPool = function (script, size) {
+  script = document.currentScript.src.substr(0, document.currentScript.src.lastIndexOf("/")) + "/" + script;
+  this._workers = new Array(size);
+  this._ind = 0;
+  this._size = size;
+  this._jobs = 0;
+  this.onmessage = undefined;
+  this._stopTimeout = undefined;
+  for (var i = 0; i < size; i++) {
+    var wrkr = new Worker(script);
+    wrkr._count = 0;
+    wrkr._ind = i;
+    wrkr.onmessage = this._onmessage.bind(this, wrkr);
+    wrkr.onerror = this._onerror.bind(this);
+    this._workers[i] = wrkr;
+  }
+
+  this._checkJobs();
+};
+WorkerPool.prototype = {
+  postMessage: function (msg) {
+    if (this._stopTimeout !== undefined) {
+      clearTimeout(this._stopTimeout);
+      this._stopTimeout = undefined;
+    }
+    var wrkr = this._workers[this._ind % this._size];
+    wrkr._count++;
+    this._jobs++;
+    wrkr.postMessage(msg);
+    this._ind++;
+  },
+
+  _onmessage: function (wrkr, evt) {
+    wrkr._count--;
+    this._jobs--;
+    this.onmessage(evt, wrkr);
+    this._checkJobs();
+  },
+
+  _onerror: function(err) {
+    try {
+        var evt = new Event('error');
+    } catch (_eventError) {
+        var evt = document.createEvent('Event');
+        evt.initEvent('error', true, true);
+    }
+    evt.message = err.message;
+    evt.filename = err.filename;
+    evt.lineno = err.lineno;
+    evt.colno = err.colno;
+    evt.error = err.error;
+    window.dispatchEvent(evt);
+  },
+
+  _checkJobs: function () {
+    if (this._jobs === 0 && this._stopTimeout === undefined) {
+      // wait for 2s of inactivity before stopping (that should be enough for local loading)
+      this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
+    }
+  },
+
+  _stop: function () {
+    this._workers.forEach(function(wrkr) {
+      wrkr.terminate();
+    });
+  }
+};
+
+var promiseMap = new Map();
+var babelWorker = new WorkerPool('babel-worker.js', 3);
+babelWorker.onmessage = function (evt) {
+    var promFuncs = promiseMap.get(evt.data.key);
+    promFuncs.resolve(evt.data);
+    promiseMap.delete(evt.data.key);
+};
+
+// instantiate just needs to run System.register
+// so we fetch the source, convert into the Babel System module format, then evaluate it
+BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
+  var loader = this;
+
+  // load as ES with Babel converting into System.register
+  return new Promise(function(resolve, reject) {
+    // anonymous module
+    if (anonSources[key]) {
+      resolve(anonSources[key])
+      anonSources[key] = undefined;
+    }
+    // otherwise we fetch
+    else {
+      xhrFetch(key, resolve, reject);
+    }
+  })
+  .then(function(source) {
+    // check our cache first
+    var cacheEntry = localStorage.getItem(key);
+    if (cacheEntry) {
+      cacheEntry = JSON.parse(cacheEntry);
+      // TODO: store a hash instead
+      if (cacheEntry.source === source) {
+        return Promise.resolve({key: key, code: cacheEntry.code, source: cacheEntry.source});
+      }
+    }
+    return new Promise(function (resolve, reject) {
+      promiseMap.set(key, {resolve: resolve, reject: reject});
+      babelWorker.postMessage({key: key, source: source});
+    });
+  }).then(function (data) {
+    // evaluate without require, exports and module variables
+    // we leave module in for now to allow module.require access
+    try {
+      var cacheEntry = JSON.stringify({source: data.source, code: data.code});
+      localStorage.setItem(key, cacheEntry);
+    } catch (e) {
+      if (window.console) {
+        window.console.warn('Unable to cache transpiled version of ' + key + ': ' + e);
+      }
+    }
+    (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
+    processAnonRegister();
+  });
+};
+
+// create a default loader instance in the browser
+if (isBrowser)
+  loader = new BrowserESModuleLoader();
+
+export default BrowserESModuleLoader;

+ 21 - 0
static/novnc/vendor/pako/LICENSE

@@ -0,0 +1,21 @@
+(The MIT License)
+
+Copyright (C) 2014-2016 by Vitaly Puzrin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 6 - 0
static/novnc/vendor/pako/README.md

@@ -0,0 +1,6 @@
+This is an ES6-modules-compatible version of
+https://github.com/nodeca/pako, based on pako version 1.0.3.
+
+It's more-or-less a direct translation of the original, with unused parts
+removed, and the dynamic support for non-typed arrays removed (since ES6
+modules don't work well with dynamic exports).

+ 45 - 0
static/novnc/vendor/pako/lib/utils/common.js

@@ -0,0 +1,45 @@
+// reduce buffer size, avoiding mem copy
+export function shrinkBuf (buf, size) {
+  if (buf.length === size) { return buf; }
+  if (buf.subarray) { return buf.subarray(0, size); }
+  buf.length = size;
+  return buf;
+};
+
+
+export function arraySet (dest, src, src_offs, len, dest_offs) {
+  if (src.subarray && dest.subarray) {
+    dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
+    return;
+  }
+  // Fallback to ordinary array
+  for (var i = 0; i < len; i++) {
+    dest[dest_offs + i] = src[src_offs + i];
+  }
+}
+
+// Join array of chunks to single array.
+export function flattenChunks (chunks) {
+  var i, l, len, pos, chunk, result;
+
+  // calculate data length
+  len = 0;
+  for (i = 0, l = chunks.length; i < l; i++) {
+    len += chunks[i].length;
+  }
+
+  // join chunks
+  result = new Uint8Array(len);
+  pos = 0;
+  for (i = 0, l = chunks.length; i < l; i++) {
+    chunk = chunks[i];
+    result.set(chunk, pos);
+    pos += chunk.length;
+  }
+
+  return result;
+}
+
+export var Buf8  = Uint8Array;
+export var Buf16 = Uint16Array;
+export var Buf32 = Int32Array;

+ 27 - 0
static/novnc/vendor/pako/lib/zlib/adler32.js

@@ -0,0 +1,27 @@
+// Note: adler32 takes 12% for level 0 and 2% for level 6.
+// It doesn't worth to make additional optimizationa as in original.
+// Small size is preferable.
+
+export default function adler32(adler, buf, len, pos) {
+  var s1 = (adler & 0xffff) |0,
+      s2 = ((adler >>> 16) & 0xffff) |0,
+      n = 0;
+
+  while (len !== 0) {
+    // Set limit ~ twice less than 5552, to keep
+    // s2 in 31-bits, because we force signed ints.
+    // in other case %= will fail.
+    n = len > 2000 ? 2000 : len;
+    len -= n;
+
+    do {
+      s1 = (s1 + buf[pos++]) |0;
+      s2 = (s2 + s1) |0;
+    } while (--n);
+
+    s1 %= 65521;
+    s2 %= 65521;
+  }
+
+  return (s1 | (s2 << 16)) |0;
+}

+ 47 - 0
static/novnc/vendor/pako/lib/zlib/constants.js

@@ -0,0 +1,47 @@
+export default {
+
+  /* Allowed flush values; see deflate() and inflate() below for details */
+  Z_NO_FLUSH:         0,
+  Z_PARTIAL_FLUSH:    1,
+  Z_SYNC_FLUSH:       2,
+  Z_FULL_FLUSH:       3,
+  Z_FINISH:           4,
+  Z_BLOCK:            5,
+  Z_TREES:            6,
+
+  /* Return codes for the compression/decompression functions. Negative values
+  * are errors, positive values are used for special but normal events.
+  */
+  Z_OK:               0,
+  Z_STREAM_END:       1,
+  Z_NEED_DICT:        2,
+  Z_ERRNO:           -1,
+  Z_STREAM_ERROR:    -2,
+  Z_DATA_ERROR:      -3,
+  //Z_MEM_ERROR:     -4,
+  Z_BUF_ERROR:       -5,
+  //Z_VERSION_ERROR: -6,
+
+  /* compression levels */
+  Z_NO_COMPRESSION:         0,
+  Z_BEST_SPEED:             1,
+  Z_BEST_COMPRESSION:       9,
+  Z_DEFAULT_COMPRESSION:   -1,
+
+
+  Z_FILTERED:               1,
+  Z_HUFFMAN_ONLY:           2,
+  Z_RLE:                    3,
+  Z_FIXED:                  4,
+  Z_DEFAULT_STRATEGY:       0,
+
+  /* Possible values of the data_type field (though see inflate()) */
+  Z_BINARY:                 0,
+  Z_TEXT:                   1,
+  //Z_ASCII:                1, // = Z_TEXT (deprecated)
+  Z_UNKNOWN:                2,
+
+  /* The deflate compression method */
+  Z_DEFLATED:               8
+  //Z_NULL:                 null // Use -1 or null inline, depending on var type
+};

+ 36 - 0
static/novnc/vendor/pako/lib/zlib/crc32.js

@@ -0,0 +1,36 @@
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+
+// Use ordinary array, since untyped makes no boost here
+export default function makeTable() {
+  var c, table = [];
+
+  for (var n = 0; n < 256; n++) {
+    c = n;
+    for (var k = 0; k < 8; k++) {
+      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+    }
+    table[n] = c;
+  }
+
+  return table;
+}
+
+// Create table on load. Just 255 signed longs. Not a problem.
+var crcTable = makeTable();
+
+
+function crc32(crc, buf, len, pos) {
+  var t = crcTable,
+      end = pos + len;
+
+  crc ^= -1;
+
+  for (var i = pos; i < end; i++) {
+    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+  }
+
+  return (crc ^ (-1)); // >>> 0;
+}

+ 1846 - 0
static/novnc/vendor/pako/lib/zlib/deflate.js

@@ -0,0 +1,1846 @@
+import * as utils from "../utils/common.js";
+import * as trees from "./trees.js";
+import adler32 from "./adler32.js";
+import crc32 from "./crc32.js";
+import msg from "./messages.js";
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+var Z_NO_FLUSH      = 0;
+var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH    = 2;
+var Z_FULL_FLUSH    = 3;
+var Z_FINISH        = 4;
+var Z_BLOCK         = 5;
+//var Z_TREES         = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK            = 0;
+var Z_STREAM_END    = 1;
+//var Z_NEED_DICT     = 2;
+//var Z_ERRNO         = -1;
+var Z_STREAM_ERROR  = -2;
+var Z_DATA_ERROR    = -3;
+//var Z_MEM_ERROR     = -4;
+var Z_BUF_ERROR     = -5;
+//var Z_VERSION_ERROR = -6;
+
+
+/* compression levels */
+//var Z_NO_COMPRESSION      = 0;
+//var Z_BEST_SPEED          = 1;
+//var Z_BEST_COMPRESSION    = 9;
+var Z_DEFAULT_COMPRESSION = -1;
+
+
+var Z_FILTERED            = 1;
+var Z_HUFFMAN_ONLY        = 2;
+var Z_RLE                 = 3;
+var Z_FIXED               = 4;
+var Z_DEFAULT_STRATEGY    = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+//var Z_BINARY              = 0;
+//var Z_TEXT                = 1;
+//var Z_ASCII               = 1; // = Z_TEXT
+var Z_UNKNOWN             = 2;
+
+
+/* The deflate compression method */
+var Z_DEFLATED  = 8;
+
+/*============================================================================*/
+
+
+var MAX_MEM_LEVEL = 9;
+/* Maximum value for memLevel in deflateInit2 */
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_MEM_LEVEL = 8;
+
+
+var LENGTH_CODES  = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+var LITERALS      = 256;
+/* number of literal bytes 0..255 */
+var L_CODES       = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+var D_CODES       = 30;
+/* number of distance codes */
+var BL_CODES      = 19;
+/* number of codes used to transfer the bit lengths */
+var HEAP_SIZE     = 2 * L_CODES + 1;
+/* maximum heap size */
+var MAX_BITS  = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+var MIN_MATCH = 3;
+var MAX_MATCH = 258;
+var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
+
+var PRESET_DICT = 0x20;
+
+var INIT_STATE = 42;
+var EXTRA_STATE = 69;
+var NAME_STATE = 73;
+var COMMENT_STATE = 91;
+var HCRC_STATE = 103;
+var BUSY_STATE = 113;
+var FINISH_STATE = 666;
+
+var BS_NEED_MORE      = 1; /* block not completed, need more input or more output */
+var BS_BLOCK_DONE     = 2; /* block flush performed */
+var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
+var BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */
+
+var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.
+
+function err(strm, errorCode) {
+  strm.msg = msg[errorCode];
+  return errorCode;
+}
+
+function rank(f) {
+  return ((f) << 1) - ((f) > 4 ? 9 : 0);
+}
+
+function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->output buffer and copying into it.
+ * (See also read_buf()).
+ */
+function flush_pending(strm) {
+  var s = strm.state;
+
+  //_tr_flush_bits(s);
+  var len = s.pending;
+  if (len > strm.avail_out) {
+    len = strm.avail_out;
+  }
+  if (len === 0) { return; }
+
+  utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
+  strm.next_out += len;
+  s.pending_out += len;
+  strm.total_out += len;
+  strm.avail_out -= len;
+  s.pending -= len;
+  if (s.pending === 0) {
+    s.pending_out = 0;
+  }
+}
+
+
+function flush_block_only(s, last) {
+  trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
+  s.block_start = s.strstart;
+  flush_pending(s.strm);
+}
+
+
+function put_byte(s, b) {
+  s.pending_buf[s.pending++] = b;
+}
+
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+function putShortMSB(s, b) {
+//  put_byte(s, (Byte)(b >> 8));
+//  put_byte(s, (Byte)(b & 0xff));
+  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
+  s.pending_buf[s.pending++] = b & 0xff;
+}
+
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read.  All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->input buffer and copying from it.
+ * (See also flush_pending()).
+ */
+function read_buf(strm, buf, start, size) {
+  var len = strm.avail_in;
+
+  if (len > size) { len = size; }
+  if (len === 0) { return 0; }
+
+  strm.avail_in -= len;
+
+  // zmemcpy(buf, strm->next_in, len);
+  utils.arraySet(buf, strm.input, strm.next_in, len, start);
+  if (strm.state.wrap === 1) {
+    strm.adler = adler32(strm.adler, buf, len, start);
+  }
+
+  else if (strm.state.wrap === 2) {
+    strm.adler = crc32(strm.adler, buf, len, start);
+  }
+
+  strm.next_in += len;
+  strm.total_in += len;
+
+  return len;
+}
+
+
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+function longest_match(s, cur_match) {
+  var chain_length = s.max_chain_length;      /* max hash chain length */
+  var scan = s.strstart; /* current string */
+  var match;                       /* matched string */
+  var len;                           /* length of current match */
+  var best_len = s.prev_length;              /* best match length so far */
+  var nice_match = s.nice_match;             /* stop if match long enough */
+  var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
+      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;
+
+  var _win = s.window; // shortcut
+
+  var wmask = s.w_mask;
+  var prev  = s.prev;
+
+  /* Stop when cur_match becomes <= limit. To simplify the code,
+   * we prevent matches with the string of window index 0.
+   */
+
+  var strend = s.strstart + MAX_MATCH;
+  var scan_end1  = _win[scan + best_len - 1];
+  var scan_end   = _win[scan + best_len];
+
+  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+   * It is easy to get rid of this optimization if necessary.
+   */
+  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+  /* Do not waste too much time if we already have a good match: */
+  if (s.prev_length >= s.good_match) {
+    chain_length >>= 2;
+  }
+  /* Do not look for matches beyond the end of the input. This is necessary
+   * to make deflate deterministic.
+   */
+  if (nice_match > s.lookahead) { nice_match = s.lookahead; }
+
+  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+  do {
+    // Assert(cur_match < s->strstart, "no future");
+    match = cur_match;
+
+    /* Skip to next match if the match length cannot increase
+     * or if the match length is less than 2.  Note that the checks below
+     * for insufficient lookahead only occur occasionally for performance
+     * reasons.  Therefore uninitialized memory will be accessed, and
+     * conditional jumps will be made that depend on those values.
+     * However the length of the match is limited to the lookahead, so
+     * the output of deflate is not affected by the uninitialized values.
+     */
+
+    if (_win[match + best_len]     !== scan_end  ||
+        _win[match + best_len - 1] !== scan_end1 ||
+        _win[match]                !== _win[scan] ||
+        _win[++match]              !== _win[scan + 1]) {
+      continue;
+    }
+
+    /* The check at best_len-1 can be removed because it will be made
+     * again later. (This heuristic is not always a win.)
+     * It is not necessary to compare scan[2] and match[2] since they
+     * are always equal when the other bytes match, given that
+     * the hash keys are equal and that HASH_BITS >= 8.
+     */
+    scan += 2;
+    match++;
+    // Assert(*scan == *match, "match[2]?");
+
+    /* We check for insufficient lookahead only every 8th comparison;
+     * the 256th check will be made at strstart+258.
+     */
+    do {
+      /*jshint noempty:false*/
+    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+             scan < strend);
+
+    // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+    len = MAX_MATCH - (strend - scan);
+    scan = strend - MAX_MATCH;
+
+    if (len > best_len) {
+      s.match_start = cur_match;
+      best_len = len;
+      if (len >= nice_match) {
+        break;
+      }
+      scan_end1  = _win[scan + best_len - 1];
+      scan_end   = _win[scan + best_len];
+    }
+  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);
+
+  if (best_len <= s.lookahead) {
+    return best_len;
+  }
+  return s.lookahead;
+}
+
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ *    At least one byte has been read, or avail_in == 0; reads are
+ *    performed for at least two bytes (required for the zip translate_eol
+ *    option -- not supported here).
+ */
+function fill_window(s) {
+  var _w_size = s.w_size;
+  var p, n, m, more, str;
+
+  //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+  do {
+    more = s.window_size - s.lookahead - s.strstart;
+
+    // JS ints have 32 bit, block below not needed
+    /* Deal with !@#$% 64K limit: */
+    //if (sizeof(int) <= 2) {
+    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+    //        more = wsize;
+    //
+    //  } else if (more == (unsigned)(-1)) {
+    //        /* Very unlikely, but possible on 16 bit machine if
+    //         * strstart == 0 && lookahead == 1 (input done a byte at time)
+    //         */
+    //        more--;
+    //    }
+    //}
+
+
+    /* If the window is almost full and there is insufficient lookahead,
+     * move the upper half to the lower one to make room in the upper half.
+     */
+    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
+
+      utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
+      s.match_start -= _w_size;
+      s.strstart -= _w_size;
+      /* we now have strstart >= MAX_DIST */
+      s.block_start -= _w_size;
+
+      /* Slide the hash table (could be avoided with 32 bit values
+       at the expense of memory usage). We slide even when level == 0
+       to keep the hash table consistent if we switch back to level > 0
+       later. (Using level 0 permanently is not an optimal usage of
+       zlib, so we don't care about this pathological case.)
+       */
+
+      n = s.hash_size;
+      p = n;
+      do {
+        m = s.head[--p];
+        s.head[p] = (m >= _w_size ? m - _w_size : 0);
+      } while (--n);
+
+      n = _w_size;
+      p = n;
+      do {
+        m = s.prev[--p];
+        s.prev[p] = (m >= _w_size ? m - _w_size : 0);
+        /* If n is not on any hash chain, prev[n] is garbage but
+         * its value will never be used.
+         */
+      } while (--n);
+
+      more += _w_size;
+    }
+    if (s.strm.avail_in === 0) {
+      break;
+    }
+
+    /* If there was no sliding:
+     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+     *    more == window_size - lookahead - strstart
+     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+     * => more >= window_size - 2*WSIZE + 2
+     * In the BIG_MEM or MMAP case (not yet supported),
+     *   window_size == input_size + MIN_LOOKAHEAD  &&
+     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+     * Otherwise, window_size == 2*WSIZE so more >= 2.
+     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+     */
+    //Assert(more >= 2, "more < 2");
+    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
+    s.lookahead += n;
+
+    /* Initialize the hash value now that we have some input: */
+    if (s.lookahead + s.insert >= MIN_MATCH) {
+      str = s.strstart - s.insert;
+      s.ins_h = s.window[str];
+
+      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;
+//#if MIN_MATCH != 3
+//        Call update_hash() MIN_MATCH-3 more times
+//#endif
+      while (s.insert) {
+        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
+
+        s.prev[str & s.w_mask] = s.head[s.ins_h];
+        s.head[s.ins_h] = str;
+        str++;
+        s.insert--;
+        if (s.lookahead + s.insert < MIN_MATCH) {
+          break;
+        }
+      }
+    }
+    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+     * but this is not important since only literal bytes will be emitted.
+     */
+
+  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
+
+  /* If the WIN_INIT bytes after the end of the current data have never been
+   * written, then zero those bytes in order to avoid memory check reports of
+   * the use of uninitialized (or uninitialised as Julian writes) bytes by
+   * the longest match routines.  Update the high water mark for the next
+   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
+   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+   */
+//  if (s.high_water < s.window_size) {
+//    var curr = s.strstart + s.lookahead;
+//    var init = 0;
+//
+//    if (s.high_water < curr) {
+//      /* Previous high water mark below current data -- zero WIN_INIT
+//       * bytes or up to end of window, whichever is less.
+//       */
+//      init = s.window_size - curr;
+//      if (init > WIN_INIT)
+//        init = WIN_INIT;
+//      zmemzero(s->window + curr, (unsigned)init);
+//      s->high_water = curr + init;
+//    }
+//    else if (s->high_water < (ulg)curr + WIN_INIT) {
+//      /* High water mark at or above current data, but below current data
+//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+//       * to end of window, whichever is less.
+//       */
+//      init = (ulg)curr + WIN_INIT - s->high_water;
+//      if (init > s->window_size - s->high_water)
+//        init = s->window_size - s->high_water;
+//      zmemzero(s->window + s->high_water, (unsigned)init);
+//      s->high_water += init;
+//    }
+//  }
+//
+//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+//    "not enough room for search");
+}
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+function deflate_stored(s, flush) {
+  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+   * to pending_buf_size, and each stored block has a 5 byte header:
+   */
+  var max_block_size = 0xffff;
+
+  if (max_block_size > s.pending_buf_size - 5) {
+    max_block_size = s.pending_buf_size - 5;
+  }
+
+  /* Copy as much as possible from input to output: */
+  for (;;) {
+    /* Fill the window as much as possible: */
+    if (s.lookahead <= 1) {
+
+      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+      //  s->block_start >= (long)s->w_size, "slide too late");
+//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
+//        s.block_start >= s.w_size)) {
+//        throw  new Error("slide too late");
+//      }
+
+      fill_window(s);
+      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+
+      if (s.lookahead === 0) {
+        break;
+      }
+      /* flush the current block */
+    }
+    //Assert(s->block_start >= 0L, "block gone");
+//    if (s.block_start < 0) throw new Error("block gone");
+
+    s.strstart += s.lookahead;
+    s.lookahead = 0;
+
+    /* Emit a stored block if pending_buf will be full: */
+    var max_start = s.block_start + max_block_size;
+
+    if (s.strstart === 0 || s.strstart >= max_start) {
+      /* strstart == 0 is possible when wraparound on 16-bit machine */
+      s.lookahead = s.strstart - max_start;
+      s.strstart = max_start;
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+
+
+    }
+    /* Flush if we may have to slide, otherwise block_start may become
+     * negative and the data will be gone:
+     */
+    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+
+  s.insert = 0;
+
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+
+  if (s.strstart > s.block_start) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+
+  return BS_NEED_MORE;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+function deflate_fast(s, flush) {
+  var hash_head;        /* head of the hash chain */
+  var bflush;           /* set if current block must be flushed */
+
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the next match, plus MIN_MATCH bytes to insert the
+     * string following the next match.
+     */
+    if (s.lookahead < MIN_LOOKAHEAD) {
+      fill_window(s);
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) {
+        break; /* flush the current block */
+      }
+    }
+
+    /* Insert the string window[strstart .. strstart+2] in the
+     * dictionary, and set hash_head to the head of the hash chain:
+     */
+    hash_head = 0/*NIL*/;
+    if (s.lookahead >= MIN_MATCH) {
+      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+      s.head[s.ins_h] = s.strstart;
+      /***/
+    }
+
+    /* Find the longest match, discarding those <= prev_length.
+     * At this point we have always match_length < MIN_MATCH
+     */
+    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
+      /* To simplify the code, we prevent matches with the string
+       * of window index 0 (in particular we have to avoid a match
+       * of the string with itself at the start of the input file).
+       */
+      s.match_length = longest_match(s, hash_head);
+      /* longest_match() sets match_start */
+    }
+    if (s.match_length >= MIN_MATCH) {
+      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only
+
+      /*** _tr_tally_dist(s, s.strstart - s.match_start,
+                     s.match_length - MIN_MATCH, bflush); ***/
+      bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);
+
+      s.lookahead -= s.match_length;
+
+      /* Insert new strings in the hash table only if the match length
+       * is not too large. This saves time but degrades compression.
+       */
+      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
+        s.match_length--; /* string at strstart already in table */
+        do {
+          s.strstart++;
+          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+          s.head[s.ins_h] = s.strstart;
+          /***/
+          /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+           * always MIN_MATCH bytes ahead.
+           */
+        } while (--s.match_length !== 0);
+        s.strstart++;
+      } else
+      {
+        s.strstart += s.match_length;
+        s.match_length = 0;
+        s.ins_h = s.window[s.strstart];
+        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
+        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;
+
+//#if MIN_MATCH != 3
+//                Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+         * matter since it will be recomputed at next deflate call.
+         */
+      }
+    } else {
+      /* No match, output a literal byte */
+      //Tracevv((stderr,"%c", s.window[s.strstart]));
+      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+
+      s.lookahead--;
+      s.strstart++;
+    }
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+}
+
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+function deflate_slow(s, flush) {
+  var hash_head;          /* head of hash chain */
+  var bflush;              /* set if current block must be flushed */
+
+  var max_insert;
+
+  /* Process the input block. */
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the next match, plus MIN_MATCH bytes to insert the
+     * string following the next match.
+     */
+    if (s.lookahead < MIN_LOOKAHEAD) {
+      fill_window(s);
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) { break; } /* flush the current block */
+    }
+
+    /* Insert the string window[strstart .. strstart+2] in the
+     * dictionary, and set hash_head to the head of the hash chain:
+     */
+    hash_head = 0/*NIL*/;
+    if (s.lookahead >= MIN_MATCH) {
+      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+      s.head[s.ins_h] = s.strstart;
+      /***/
+    }
+
+    /* Find the longest match, discarding those <= prev_length.
+     */
+    s.prev_length = s.match_length;
+    s.prev_match = s.match_start;
+    s.match_length = MIN_MATCH - 1;
+
+    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
+        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
+      /* To simplify the code, we prevent matches with the string
+       * of window index 0 (in particular we have to avoid a match
+       * of the string with itself at the start of the input file).
+       */
+      s.match_length = longest_match(s, hash_head);
+      /* longest_match() sets match_start */
+
+      if (s.match_length <= 5 &&
+         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {
+
+        /* If prev_match is also MIN_MATCH, match_start is garbage
+         * but we will ignore the current match anyway.
+         */
+        s.match_length = MIN_MATCH - 1;
+      }
+    }
+    /* If there was a match at the previous step and the current
+     * match is not better, output the previous match:
+     */
+    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
+      max_insert = s.strstart + s.lookahead - MIN_MATCH;
+      /* Do not insert strings in hash table beyond this. */
+
+      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);
+
+      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
+                     s.prev_length - MIN_MATCH, bflush);***/
+      bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
+      /* Insert in hash table all strings up to the end of the match.
+       * strstart-1 and strstart are already inserted. If there is not
+       * enough lookahead, the last two strings are not inserted in
+       * the hash table.
+       */
+      s.lookahead -= s.prev_length - 1;
+      s.prev_length -= 2;
+      do {
+        if (++s.strstart <= max_insert) {
+          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
+          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+          s.head[s.ins_h] = s.strstart;
+          /***/
+        }
+      } while (--s.prev_length !== 0);
+      s.match_available = 0;
+      s.match_length = MIN_MATCH - 1;
+      s.strstart++;
+
+      if (bflush) {
+        /*** FLUSH_BLOCK(s, 0); ***/
+        flush_block_only(s, false);
+        if (s.strm.avail_out === 0) {
+          return BS_NEED_MORE;
+        }
+        /***/
+      }
+
+    } else if (s.match_available) {
+      /* If there was no match at the previous position, output a
+       * single literal. If there was a match but the current match
+       * is longer, truncate the previous match to a single literal.
+       */
+      //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+      bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
+
+      if (bflush) {
+        /*** FLUSH_BLOCK_ONLY(s, 0) ***/
+        flush_block_only(s, false);
+        /***/
+      }
+      s.strstart++;
+      s.lookahead--;
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+    } else {
+      /* There is no previous match to compare with, wait for
+       * the next step to decide.
+       */
+      s.match_available = 1;
+      s.strstart++;
+      s.lookahead--;
+    }
+  }
+  //Assert (flush != Z_NO_FLUSH, "no flush?");
+  if (s.match_available) {
+    //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+    bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);
+
+    s.match_available = 0;
+  }
+  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+
+  return BS_BLOCK_DONE;
+}
+
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one.  Do not maintain a hash table.  (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+function deflate_rle(s, flush) {
+  var bflush;            /* set if current block must be flushed */
+  var prev;              /* byte at distance one to match */
+  var scan, strend;      /* scan goes up to strend for length of run */
+
+  var _win = s.window;
+
+  for (;;) {
+    /* Make sure that we always have enough lookahead, except
+     * at the end of the input file. We need MAX_MATCH bytes
+     * for the longest run, plus one for the unrolled loop.
+     */
+    if (s.lookahead <= MAX_MATCH) {
+      fill_window(s);
+      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
+        return BS_NEED_MORE;
+      }
+      if (s.lookahead === 0) { break; } /* flush the current block */
+    }
+
+    /* See how many times the previous byte repeats */
+    s.match_length = 0;
+    if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
+      scan = s.strstart - 1;
+      prev = _win[scan];
+      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
+        strend = s.strstart + MAX_MATCH;
+        do {
+          /*jshint noempty:false*/
+        } while (prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 prev === _win[++scan] && prev === _win[++scan] &&
+                 scan < strend);
+        s.match_length = MAX_MATCH - (strend - scan);
+        if (s.match_length > s.lookahead) {
+          s.match_length = s.lookahead;
+        }
+      }
+      //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+    }
+
+    /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+    if (s.match_length >= MIN_MATCH) {
+      //check_match(s, s.strstart, s.strstart - 1, s.match_length);
+
+      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
+      bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);
+
+      s.lookahead -= s.match_length;
+      s.strstart += s.match_length;
+      s.match_length = 0;
+    } else {
+      /* No match, output a literal byte */
+      //Tracevv((stderr,"%c", s->window[s->strstart]));
+      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+
+      s.lookahead--;
+      s.strstart++;
+    }
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = 0;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+function deflate_huff(s, flush) {
+  var bflush;             /* set if current block must be flushed */
+
+  for (;;) {
+    /* Make sure that we have a literal to write. */
+    if (s.lookahead === 0) {
+      fill_window(s);
+      if (s.lookahead === 0) {
+        if (flush === Z_NO_FLUSH) {
+          return BS_NEED_MORE;
+        }
+        break;      /* flush the current block */
+      }
+    }
+
+    /* Output a literal byte */
+    s.match_length = 0;
+    //Tracevv((stderr,"%c", s->window[s->strstart]));
+    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+    bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
+    s.lookahead--;
+    s.strstart++;
+    if (bflush) {
+      /*** FLUSH_BLOCK(s, 0); ***/
+      flush_block_only(s, false);
+      if (s.strm.avail_out === 0) {
+        return BS_NEED_MORE;
+      }
+      /***/
+    }
+  }
+  s.insert = 0;
+  if (flush === Z_FINISH) {
+    /*** FLUSH_BLOCK(s, 1); ***/
+    flush_block_only(s, true);
+    if (s.strm.avail_out === 0) {
+      return BS_FINISH_STARTED;
+    }
+    /***/
+    return BS_FINISH_DONE;
+  }
+  if (s.last_lit) {
+    /*** FLUSH_BLOCK(s, 0); ***/
+    flush_block_only(s, false);
+    if (s.strm.avail_out === 0) {
+      return BS_NEED_MORE;
+    }
+    /***/
+  }
+  return BS_BLOCK_DONE;
+}
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+function Config(good_length, max_lazy, nice_length, max_chain, func) {
+  this.good_length = good_length;
+  this.max_lazy = max_lazy;
+  this.nice_length = nice_length;
+  this.max_chain = max_chain;
+  this.func = func;
+}
+
+var configuration_table;
+
+configuration_table = [
+  /*      good lazy nice chain */
+  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */
+  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */
+  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */
+  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */
+
+  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */
+  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */
+  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */
+  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */
+  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */
+  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */
+];
+
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+function lm_init(s) {
+  s.window_size = 2 * s.w_size;
+
+  /*** CLEAR_HASH(s); ***/
+  zero(s.head); // Fill with NIL (= 0);
+
+  /* Set the default configuration parameters:
+   */
+  s.max_lazy_match = configuration_table[s.level].max_lazy;
+  s.good_match = configuration_table[s.level].good_length;
+  s.nice_match = configuration_table[s.level].nice_length;
+  s.max_chain_length = configuration_table[s.level].max_chain;
+
+  s.strstart = 0;
+  s.block_start = 0;
+  s.lookahead = 0;
+  s.insert = 0;
+  s.match_length = s.prev_length = MIN_MATCH - 1;
+  s.match_available = 0;
+  s.ins_h = 0;
+}
+
+
+function DeflateState() {
+  this.strm = null;            /* pointer back to this zlib stream */
+  this.status = 0;            /* as the name implies */
+  this.pending_buf = null;      /* output still pending */
+  this.pending_buf_size = 0;  /* size of pending_buf */
+  this.pending_out = 0;       /* next pending byte to output to the stream */
+  this.pending = 0;           /* nb of bytes in the pending buffer */
+  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
+  this.gzhead = null;         /* gzip header information to write */
+  this.gzindex = 0;           /* where in extra, name, or comment */
+  this.method = Z_DEFLATED; /* can only be DEFLATED */
+  this.last_flush = -1;   /* value of flush param for previous deflate call */
+
+  this.w_size = 0;  /* LZ77 window size (32K by default) */
+  this.w_bits = 0;  /* log2(w_size)  (8..16) */
+  this.w_mask = 0;  /* w_size - 1 */
+
+  this.window = null;
+  /* Sliding window. Input bytes are read into the second half of the window,
+   * and move to the first half later to keep a dictionary of at least wSize
+   * bytes. With this organization, matches are limited to a distance of
+   * wSize-MAX_MATCH bytes, but this ensures that IO is always
+   * performed with a length multiple of the block size.
+   */
+
+  this.window_size = 0;
+  /* Actual size of window: 2*wSize, except when the user input buffer
+   * is directly used as sliding window.
+   */
+
+  this.prev = null;
+  /* Link to older string with same hash index. To limit the size of this
+   * array to 64K, this link is maintained only for the last 32K strings.
+   * An index in this array is thus a window index modulo 32K.
+   */
+
+  this.head = null;   /* Heads of the hash chains or NIL. */
+
+  this.ins_h = 0;       /* hash index of string to be inserted */
+  this.hash_size = 0;   /* number of elements in hash table */
+  this.hash_bits = 0;   /* log2(hash_size) */
+  this.hash_mask = 0;   /* hash_size-1 */
+
+  this.hash_shift = 0;
+  /* Number of bits by which ins_h must be shifted at each input
+   * step. It must be such that after MIN_MATCH steps, the oldest
+   * byte no longer takes part in the hash key, that is:
+   *   hash_shift * MIN_MATCH >= hash_bits
+   */
+
+  this.block_start = 0;
+  /* Window position at the beginning of the current output block. Gets
+   * negative when the window is moved backwards.
+   */
+
+  this.match_length = 0;      /* length of best match */
+  this.prev_match = 0;        /* previous match */
+  this.match_available = 0;   /* set if previous match exists */
+  this.strstart = 0;          /* start of string to insert */
+  this.match_start = 0;       /* start of matching string */
+  this.lookahead = 0;         /* number of valid bytes ahead in window */
+
+  this.prev_length = 0;
+  /* Length of the best match at previous step. Matches not greater than this
+   * are discarded. This is used in the lazy match evaluation.
+   */
+
+  this.max_chain_length = 0;
+  /* To speed up deflation, hash chains are never searched beyond this
+   * length.  A higher limit improves compression ratio but degrades the
+   * speed.
+   */
+
+  this.max_lazy_match = 0;
+  /* Attempt to find a better match only when the current match is strictly
+   * smaller than this value. This mechanism is used only for compression
+   * levels >= 4.
+   */
+  // That's alias to max_lazy_match, don't use directly
+  //this.max_insert_length = 0;
+  /* Insert new strings in the hash table only if the match length is not
+   * greater than this length. This saves time but degrades compression.
+   * max_insert_length is used only for compression levels <= 3.
+   */
+
+  this.level = 0;     /* compression level (1..9) */
+  this.strategy = 0;  /* favor or force Huffman coding*/
+
+  this.good_match = 0;
+  /* Use a faster search when the previous match is longer than this */
+
+  this.nice_match = 0; /* Stop searching when current match exceeds this */
+
+              /* used by trees.c: */
+
+  /* Didn't use ct_data typedef below to suppress compiler warning */
+
+  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
+  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */
+
+  // Use flat array of DOUBLE size, with interleaved fata,
+  // because JS does not support effective
+  this.dyn_ltree  = new utils.Buf16(HEAP_SIZE * 2);
+  this.dyn_dtree  = new utils.Buf16((2 * D_CODES + 1) * 2);
+  this.bl_tree    = new utils.Buf16((2 * BL_CODES + 1) * 2);
+  zero(this.dyn_ltree);
+  zero(this.dyn_dtree);
+  zero(this.bl_tree);
+
+  this.l_desc   = null;         /* desc. for literal tree */
+  this.d_desc   = null;         /* desc. for distance tree */
+  this.bl_desc  = null;         /* desc. for bit length tree */
+
+  //ush bl_count[MAX_BITS+1];
+  this.bl_count = new utils.Buf16(MAX_BITS + 1);
+  /* number of codes at each bit length for an optimal tree */
+
+  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
+  this.heap = new utils.Buf16(2 * L_CODES + 1);  /* heap used to build the Huffman trees */
+  zero(this.heap);
+
+  this.heap_len = 0;               /* number of elements in the heap */
+  this.heap_max = 0;               /* element of largest frequency */
+  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+   * The same heap array is used to build all trees.
+   */
+
+  this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
+  zero(this.depth);
+  /* Depth of each subtree used as tie breaker for trees of equal frequency
+   */
+
+  this.l_buf = 0;          /* buffer index for literals or lengths */
+
+  this.lit_bufsize = 0;
+  /* Size of match buffer for literals/lengths.  There are 4 reasons for
+   * limiting lit_bufsize to 64K:
+   *   - frequencies can be kept in 16 bit counters
+   *   - if compression is not successful for the first block, all input
+   *     data is still in the window so we can still emit a stored block even
+   *     when input comes from standard input.  (This can also be done for
+   *     all blocks if lit_bufsize is not greater than 32K.)
+   *   - if compression is not successful for a file smaller than 64K, we can
+   *     even emit a stored file instead of a stored block (saving 5 bytes).
+   *     This is applicable only for zip (not gzip or zlib).
+   *   - creating new Huffman trees less frequently may not provide fast
+   *     adaptation to changes in the input data statistics. (Take for
+   *     example a binary file with poorly compressible code followed by
+   *     a highly compressible string table.) Smaller buffer sizes give
+   *     fast adaptation but have of course the overhead of transmitting
+   *     trees more frequently.
+   *   - I can't count above 4
+   */
+
+  this.last_lit = 0;      /* running index in l_buf */
+
+  this.d_buf = 0;
+  /* Buffer index for distances. To simplify the code, d_buf and l_buf have
+   * the same number of elements. To use different lengths, an extra flag
+   * array would be necessary.
+   */
+
+  this.opt_len = 0;       /* bit length of current block with optimal trees */
+  this.static_len = 0;    /* bit length of current block with static trees */
+  this.matches = 0;       /* number of string matches in current block */
+  this.insert = 0;        /* bytes at end of window left to insert */
+
+
+  this.bi_buf = 0;
+  /* Output buffer. bits are inserted starting at the bottom (least
+   * significant bits).
+   */
+  this.bi_valid = 0;
+  /* Number of valid bits in bi_buf.  All bits above the last valid bit
+   * are always zero.
+   */
+
+  // Used for window memory init. We safely ignore it for JS. That makes
+  // sense only for pointers and memory check tools.
+  //this.high_water = 0;
+  /* High water mark offset in window for initialized bytes -- bytes above
+   * this are set to zero in order to avoid memory check warnings when
+   * longest match routines access bytes past the input.  This is then
+   * updated to the new high water mark.
+   */
+}
+
+
+function deflateResetKeep(strm) {
+  var s;
+
+  if (!strm || !strm.state) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+  strm.total_in = strm.total_out = 0;
+  strm.data_type = Z_UNKNOWN;
+
+  s = strm.state;
+  s.pending = 0;
+  s.pending_out = 0;
+
+  if (s.wrap < 0) {
+    s.wrap = -s.wrap;
+    /* was made negative by deflate(..., Z_FINISH); */
+  }
+  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
+  strm.adler = (s.wrap === 2) ?
+    0  // crc32(0, Z_NULL, 0)
+  :
+    1; // adler32(0, Z_NULL, 0)
+  s.last_flush = Z_NO_FLUSH;
+  trees._tr_init(s);
+  return Z_OK;
+}
+
+
+function deflateReset(strm) {
+  var ret = deflateResetKeep(strm);
+  if (ret === Z_OK) {
+    lm_init(strm.state);
+  }
+  return ret;
+}
+
+
+function deflateSetHeader(strm, head) {
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
+  strm.state.gzhead = head;
+  return Z_OK;
+}
+
+
+function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
+  if (!strm) { // === Z_NULL
+    return Z_STREAM_ERROR;
+  }
+  var wrap = 1;
+
+  if (level === Z_DEFAULT_COMPRESSION) {
+    level = 6;
+  }
+
+  if (windowBits < 0) { /* suppress zlib wrapper */
+    wrap = 0;
+    windowBits = -windowBits;
+  }
+
+  else if (windowBits > 15) {
+    wrap = 2;           /* write gzip wrapper instead */
+    windowBits -= 16;
+  }
+
+
+  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
+    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+    strategy < 0 || strategy > Z_FIXED) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+
+  if (windowBits === 8) {
+    windowBits = 9;
+  }
+  /* until 256-byte window bug fixed */
+
+  var s = new DeflateState();
+
+  strm.state = s;
+  s.strm = strm;
+
+  s.wrap = wrap;
+  s.gzhead = null;
+  s.w_bits = windowBits;
+  s.w_size = 1 << s.w_bits;
+  s.w_mask = s.w_size - 1;
+
+  s.hash_bits = memLevel + 7;
+  s.hash_size = 1 << s.hash_bits;
+  s.hash_mask = s.hash_size - 1;
+  s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
+
+  s.window = new utils.Buf8(s.w_size * 2);
+  s.head = new utils.Buf16(s.hash_size);
+  s.prev = new utils.Buf16(s.w_size);
+
+  // Don't need mem init magic for JS.
+  //s.high_water = 0;  /* nothing written to s->window yet */
+
+  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+  s.pending_buf_size = s.lit_bufsize * 4;
+
+  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+  //s->pending_buf = (uchf *) overlay;
+  s.pending_buf = new utils.Buf8(s.pending_buf_size);
+
+  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
+  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+  s.d_buf = 1 * s.lit_bufsize;
+
+  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+  s.l_buf = (1 + 2) * s.lit_bufsize;
+
+  s.level = level;
+  s.strategy = strategy;
+  s.method = method;
+
+  return deflateReset(strm);
+}
+
+function deflateInit(strm, level) {
+  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+}
+
+
+function deflate(strm, flush) {
+  var old_flush, s;
+  var beg, val; // for gzip header write only
+
+  if (!strm || !strm.state ||
+    flush > Z_BLOCK || flush < 0) {
+    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
+  }
+
+  s = strm.state;
+
+  if (!strm.output ||
+      (!strm.input && strm.avail_in !== 0) ||
+      (s.status === FINISH_STATE && flush !== Z_FINISH)) {
+    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
+  }
+
+  s.strm = strm; /* just in case */
+  old_flush = s.last_flush;
+  s.last_flush = flush;
+
+  /* Write the header */
+  if (s.status === INIT_STATE) {
+
+    if (s.wrap === 2) { // GZIP header
+      strm.adler = 0;  //crc32(0L, Z_NULL, 0);
+      put_byte(s, 31);
+      put_byte(s, 139);
+      put_byte(s, 8);
+      if (!s.gzhead) { // s->gzhead == Z_NULL
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, 0);
+        put_byte(s, s.level === 9 ? 2 :
+                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+                     4 : 0));
+        put_byte(s, OS_CODE);
+        s.status = BUSY_STATE;
+      }
+      else {
+        put_byte(s, (s.gzhead.text ? 1 : 0) +
+                    (s.gzhead.hcrc ? 2 : 0) +
+                    (!s.gzhead.extra ? 0 : 4) +
+                    (!s.gzhead.name ? 0 : 8) +
+                    (!s.gzhead.comment ? 0 : 16)
+                );
+        put_byte(s, s.gzhead.time & 0xff);
+        put_byte(s, (s.gzhead.time >> 8) & 0xff);
+        put_byte(s, (s.gzhead.time >> 16) & 0xff);
+        put_byte(s, (s.gzhead.time >> 24) & 0xff);
+        put_byte(s, s.level === 9 ? 2 :
+                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+                     4 : 0));
+        put_byte(s, s.gzhead.os & 0xff);
+        if (s.gzhead.extra && s.gzhead.extra.length) {
+          put_byte(s, s.gzhead.extra.length & 0xff);
+          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
+        }
+        if (s.gzhead.hcrc) {
+          strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
+        }
+        s.gzindex = 0;
+        s.status = EXTRA_STATE;
+      }
+    }
+    else // DEFLATE header
+    {
+      var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
+      var level_flags = -1;
+
+      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
+        level_flags = 0;
+      } else if (s.level < 6) {
+        level_flags = 1;
+      } else if (s.level === 6) {
+        level_flags = 2;
+      } else {
+        level_flags = 3;
+      }
+      header |= (level_flags << 6);
+      if (s.strstart !== 0) { header |= PRESET_DICT; }
+      header += 31 - (header % 31);
+
+      s.status = BUSY_STATE;
+      putShortMSB(s, header);
+
+      /* Save the adler32 of the preset dictionary: */
+      if (s.strstart !== 0) {
+        putShortMSB(s, strm.adler >>> 16);
+        putShortMSB(s, strm.adler & 0xffff);
+      }
+      strm.adler = 1; // adler32(0L, Z_NULL, 0);
+    }
+  }
+
+//#ifdef GZIP
+  if (s.status === EXTRA_STATE) {
+    if (s.gzhead.extra/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+
+      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            break;
+          }
+        }
+        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
+        s.gzindex++;
+      }
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (s.gzindex === s.gzhead.extra.length) {
+        s.gzindex = 0;
+        s.status = NAME_STATE;
+      }
+    }
+    else {
+      s.status = NAME_STATE;
+    }
+  }
+  if (s.status === NAME_STATE) {
+    if (s.gzhead.name/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+      //int val;
+
+      do {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            val = 1;
+            break;
+          }
+        }
+        // JS specific: little magic to add zero terminator to end of string
+        if (s.gzindex < s.gzhead.name.length) {
+          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
+        } else {
+          val = 0;
+        }
+        put_byte(s, val);
+      } while (val !== 0);
+
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (val === 0) {
+        s.gzindex = 0;
+        s.status = COMMENT_STATE;
+      }
+    }
+    else {
+      s.status = COMMENT_STATE;
+    }
+  }
+  if (s.status === COMMENT_STATE) {
+    if (s.gzhead.comment/* != Z_NULL*/) {
+      beg = s.pending;  /* start of bytes to update crc */
+      //int val;
+
+      do {
+        if (s.pending === s.pending_buf_size) {
+          if (s.gzhead.hcrc && s.pending > beg) {
+            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+          }
+          flush_pending(strm);
+          beg = s.pending;
+          if (s.pending === s.pending_buf_size) {
+            val = 1;
+            break;
+          }
+        }
+        // JS specific: little magic to add zero terminator to end of string
+        if (s.gzindex < s.gzhead.comment.length) {
+          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
+        } else {
+          val = 0;
+        }
+        put_byte(s, val);
+      } while (val !== 0);
+
+      if (s.gzhead.hcrc && s.pending > beg) {
+        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
+      }
+      if (val === 0) {
+        s.status = HCRC_STATE;
+      }
+    }
+    else {
+      s.status = HCRC_STATE;
+    }
+  }
+  if (s.status === HCRC_STATE) {
+    if (s.gzhead.hcrc) {
+      if (s.pending + 2 > s.pending_buf_size) {
+        flush_pending(strm);
+      }
+      if (s.pending + 2 <= s.pending_buf_size) {
+        put_byte(s, strm.adler & 0xff);
+        put_byte(s, (strm.adler >> 8) & 0xff);
+        strm.adler = 0; //crc32(0L, Z_NULL, 0);
+        s.status = BUSY_STATE;
+      }
+    }
+    else {
+      s.status = BUSY_STATE;
+    }
+  }
+//#endif
+
+  /* Flush as much pending output as possible */
+  if (s.pending !== 0) {
+    flush_pending(strm);
+    if (strm.avail_out === 0) {
+      /* Since avail_out is 0, deflate will be called again with
+       * more output space, but possibly with both pending and
+       * avail_in equal to zero. There won't be anything to do,
+       * but this is not an error situation so make sure we
+       * return OK instead of BUF_ERROR at next call of deflate:
+       */
+      s.last_flush = -1;
+      return Z_OK;
+    }
+
+    /* Make sure there is something to do and avoid duplicate consecutive
+     * flushes. For repeated and useless calls with Z_FINISH, we keep
+     * returning Z_STREAM_END instead of Z_BUF_ERROR.
+     */
+  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
+    flush !== Z_FINISH) {
+    return err(strm, Z_BUF_ERROR);
+  }
+
+  /* User must not provide more input after the first FINISH: */
+  if (s.status === FINISH_STATE && strm.avail_in !== 0) {
+    return err(strm, Z_BUF_ERROR);
+  }
+
+  /* Start a new block or continue the current one.
+   */
+  if (strm.avail_in !== 0 || s.lookahead !== 0 ||
+    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
+    var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
+      (s.strategy === Z_RLE ? deflate_rle(s, flush) :
+        configuration_table[s.level].func(s, flush));
+
+    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
+      s.status = FINISH_STATE;
+    }
+    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
+      if (strm.avail_out === 0) {
+        s.last_flush = -1;
+        /* avoid BUF_ERROR next call, see above */
+      }
+      return Z_OK;
+      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+       * of deflate should use the same flush parameter to make sure
+       * that the flush is complete. So we don't have to output an
+       * empty block here, this will be done at next call. This also
+       * ensures that for a very small output buffer, we emit at most
+       * one empty block.
+       */
+    }
+    if (bstate === BS_BLOCK_DONE) {
+      if (flush === Z_PARTIAL_FLUSH) {
+        trees._tr_align(s);
+      }
+      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+
+        trees._tr_stored_block(s, 0, 0, false);
+        /* For a full flush, this empty block will be recognized
+         * as a special marker by inflate_sync().
+         */
+        if (flush === Z_FULL_FLUSH) {
+          /*** CLEAR_HASH(s); ***/             /* forget history */
+          zero(s.head); // Fill with NIL (= 0);
+
+          if (s.lookahead === 0) {
+            s.strstart = 0;
+            s.block_start = 0;
+            s.insert = 0;
+          }
+        }
+      }
+      flush_pending(strm);
+      if (strm.avail_out === 0) {
+        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+        return Z_OK;
+      }
+    }
+  }
+  //Assert(strm->avail_out > 0, "bug2");
+  //if (strm.avail_out <= 0) { throw new Error("bug2");}
+
+  if (flush !== Z_FINISH) { return Z_OK; }
+  if (s.wrap <= 0) { return Z_STREAM_END; }
+
+  /* Write the trailer */
+  if (s.wrap === 2) {
+    put_byte(s, strm.adler & 0xff);
+    put_byte(s, (strm.adler >> 8) & 0xff);
+    put_byte(s, (strm.adler >> 16) & 0xff);
+    put_byte(s, (strm.adler >> 24) & 0xff);
+    put_byte(s, strm.total_in & 0xff);
+    put_byte(s, (strm.total_in >> 8) & 0xff);
+    put_byte(s, (strm.total_in >> 16) & 0xff);
+    put_byte(s, (strm.total_in >> 24) & 0xff);
+  }
+  else
+  {
+    putShortMSB(s, strm.adler >>> 16);
+    putShortMSB(s, strm.adler & 0xffff);
+  }
+
+  flush_pending(strm);
+  /* If avail_out is zero, the application will call deflate again
+   * to flush the rest.
+   */
+  if (s.wrap > 0) { s.wrap = -s.wrap; }
+  /* write the trailer only once! */
+  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
+}
+
+function deflateEnd(strm) {
+  var status;
+
+  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  status = strm.state.status;
+  if (status !== INIT_STATE &&
+    status !== EXTRA_STATE &&
+    status !== NAME_STATE &&
+    status !== COMMENT_STATE &&
+    status !== HCRC_STATE &&
+    status !== BUSY_STATE &&
+    status !== FINISH_STATE
+  ) {
+    return err(strm, Z_STREAM_ERROR);
+  }
+
+  strm.state = null;
+
+  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
+}
+
+
+/* =========================================================================
+ * Initializes the compression dictionary from the given byte
+ * sequence without producing any compressed output.
+ */
+function deflateSetDictionary(strm, dictionary) {
+  var dictLength = dictionary.length;
+
+  var s;
+  var str, n;
+  var wrap;
+  var avail;
+  var next;
+  var input;
+  var tmpDict;
+
+  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  s = strm.state;
+  wrap = s.wrap;
+
+  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
+    return Z_STREAM_ERROR;
+  }
+
+  /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+  if (wrap === 1) {
+    /* adler32(strm->adler, dictionary, dictLength); */
+    strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
+  }
+
+  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */
+
+  /* if dictionary would fill window, just replace the history */
+  if (dictLength >= s.w_size) {
+    if (wrap === 0) {            /* already empty otherwise */
+      /*** CLEAR_HASH(s); ***/
+      zero(s.head); // Fill with NIL (= 0);
+      s.strstart = 0;
+      s.block_start = 0;
+      s.insert = 0;
+    }
+    /* use the tail */
+    // dictionary = dictionary.slice(dictLength - s.w_size);
+    tmpDict = new utils.Buf8(s.w_size);
+    utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
+    dictionary = tmpDict;
+    dictLength = s.w_size;
+  }
+  /* insert dictionary into window and hash */
+  avail = strm.avail_in;
+  next = strm.next_in;
+  input = strm.input;
+  strm.avail_in = dictLength;
+  strm.next_in = 0;
+  strm.input = dictionary;
+  fill_window(s);
+  while (s.lookahead >= MIN_MATCH) {
+    str = s.strstart;
+    n = s.lookahead - (MIN_MATCH - 1);
+    do {
+      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;
+
+      s.prev[str & s.w_mask] = s.head[s.ins_h];
+
+      s.head[s.ins_h] = str;
+      str++;
+    } while (--n);
+    s.strstart = str;
+    s.lookahead = MIN_MATCH - 1;
+    fill_window(s);
+  }
+  s.strstart += s.lookahead;
+  s.block_start = s.strstart;
+  s.insert = s.lookahead;
+  s.lookahead = 0;
+  s.match_length = s.prev_length = MIN_MATCH - 1;
+  s.match_available = 0;
+  strm.next_in = next;
+  strm.input = input;
+  strm.avail_in = avail;
+  s.wrap = wrap;
+  return Z_OK;
+}
+
+
+export { deflateInit, deflateInit2, deflateReset, deflateResetKeep, deflateSetHeader, deflate, deflateEnd, deflateSetDictionary };
+export var deflateInfo = 'pako deflate (from Nodeca project)';
+
+/* Not implemented
+exports.deflateBound = deflateBound;
+exports.deflateCopy = deflateCopy;
+exports.deflateParams = deflateParams;
+exports.deflatePending = deflatePending;
+exports.deflatePrime = deflatePrime;
+exports.deflateTune = deflateTune;
+*/

+ 35 - 0
static/novnc/vendor/pako/lib/zlib/gzheader.js

@@ -0,0 +1,35 @@
+export default function GZheader() {
+  /* true if compressed data believed to be text */
+  this.text       = 0;
+  /* modification time */
+  this.time       = 0;
+  /* extra flags (not used when writing a gzip file) */
+  this.xflags     = 0;
+  /* operating system */
+  this.os         = 0;
+  /* pointer to extra field or Z_NULL if none */
+  this.extra      = null;
+  /* extra field length (valid if extra != Z_NULL) */
+  this.extra_len  = 0; // Actually, we don't need it in JS,
+                       // but leave for few code modifications
+
+  //
+  // Setup limits is not necessary because in js we should not preallocate memory
+  // for inflate use constant limit in 65536 bytes
+  //
+
+  /* space at extra (only when reading header) */
+  // this.extra_max  = 0;
+  /* pointer to zero-terminated file name or Z_NULL */
+  this.name       = '';
+  /* space at name (only when reading header) */
+  // this.name_max   = 0;
+  /* pointer to zero-terminated comment or Z_NULL */
+  this.comment    = '';
+  /* space at comment (only when reading header) */
+  // this.comm_max   = 0;
+  /* true if there was or will be a header crc */
+  this.hcrc       = 0;
+  /* true when done reading gzip header (not used when writing a gzip file) */
+  this.done       = false;
+}

+ 324 - 0
static/novnc/vendor/pako/lib/zlib/inffast.js

@@ -0,0 +1,324 @@
+// See state defs from inflate.js
+var BAD = 30;       /* got a data error -- remain here until reset */
+var TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
+
+/*
+   Decode literal, length, and distance codes and write out the resulting
+   literal and match bytes until either not enough input or output is
+   available, an end-of-block is encountered, or a data error is encountered.
+   When large enough input and output buffers are supplied to inflate(), for
+   example, a 16K input buffer and a 64K output buffer, more than 95% of the
+   inflate execution time is spent in this routine.
+
+   Entry assumptions:
+
+        state.mode === LEN
+        strm.avail_in >= 6
+        strm.avail_out >= 258
+        start >= strm.avail_out
+        state.bits < 8
+
+   On return, state.mode is one of:
+
+        LEN -- ran out of enough output space or enough available input
+        TYPE -- reached end of block code, inflate() to interpret next block
+        BAD -- error in block data
+
+   Notes:
+
+    - The maximum input bits used by a length/distance pair is 15 bits for the
+      length code, 5 bits for the length extra, 15 bits for the distance code,
+      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
+      Therefore if strm.avail_in >= 6, then there is enough input to avoid
+      checking for available input while decoding.
+
+    - The maximum bytes that a single length/distance pair can output is 258
+      bytes, which is the maximum length that can be coded.  inflate_fast()
+      requires strm.avail_out >= 258 for each loop to avoid checking for
+      output space.
+ */
+export default function inflate_fast(strm, start) {
+  var state;
+  var _in;                    /* local strm.input */
+  var last;                   /* have enough input while in < last */
+  var _out;                   /* local strm.output */
+  var beg;                    /* inflate()'s initial strm.output */
+  var end;                    /* while out < end, enough space available */
+//#ifdef INFLATE_STRICT
+  var dmax;                   /* maximum distance from zlib header */
+//#endif
+  var wsize;                  /* window size or zero if not using window */
+  var whave;                  /* valid bytes in the window */
+  var wnext;                  /* window write index */
+  // Use `s_window` instead `window`, avoid conflict with instrumentation tools
+  var s_window;               /* allocated sliding window, if wsize != 0 */
+  var hold;                   /* local strm.hold */
+  var bits;                   /* local strm.bits */
+  var lcode;                  /* local strm.lencode */
+  var dcode;                  /* local strm.distcode */
+  var lmask;                  /* mask for first level of length codes */
+  var dmask;                  /* mask for first level of distance codes */
+  var here;                   /* retrieved table entry */
+  var op;                     /* code bits, operation, extra bits, or */
+                              /*  window position, window bytes to copy */
+  var len;                    /* match length, unused bytes */
+  var dist;                   /* match distance */
+  var from;                   /* where to copy match from */
+  var from_source;
+
+
+  var input, output; // JS specific, because we have no pointers
+
+  /* copy state to local variables */
+  state = strm.state;
+  //here = state.here;
+  _in = strm.next_in;
+  input = strm.input;
+  last = _in + (strm.avail_in - 5);
+  _out = strm.next_out;
+  output = strm.output;
+  beg = _out - (start - strm.avail_out);
+  end = _out + (strm.avail_out - 257);
+//#ifdef INFLATE_STRICT
+  dmax = state.dmax;
+//#endif
+  wsize = state.wsize;
+  whave = state.whave;
+  wnext = state.wnext;
+  s_window = state.window;
+  hold = state.hold;
+  bits = state.bits;
+  lcode = state.lencode;
+  dcode = state.distcode;
+  lmask = (1 << state.lenbits) - 1;
+  dmask = (1 << state.distbits) - 1;
+
+
+  /* decode literals and length/distances until end-of-block or not enough
+     input data or output space */
+
+  top:
+  do {
+    if (bits < 15) {
+      hold += input[_in++] << bits;
+      bits += 8;
+      hold += input[_in++] << bits;
+      bits += 8;
+    }
+
+    here = lcode[hold & lmask];
+
+    dolen:
+    for (;;) { // Goto emulation
+      op = here >>> 24/*here.bits*/;
+      hold >>>= op;
+      bits -= op;
+      op = (here >>> 16) & 0xff/*here.op*/;
+      if (op === 0) {                          /* literal */
+        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+        //        "inflate:         literal '%c'\n" :
+        //        "inflate:         literal 0x%02x\n", here.val));
+        output[_out++] = here & 0xffff/*here.val*/;
+      }
+      else if (op & 16) {                     /* length base */
+        len = here & 0xffff/*here.val*/;
+        op &= 15;                           /* number of extra bits */
+        if (op) {
+          if (bits < op) {
+            hold += input[_in++] << bits;
+            bits += 8;
+          }
+          len += hold & ((1 << op) - 1);
+          hold >>>= op;
+          bits -= op;
+        }
+        //Tracevv((stderr, "inflate:         length %u\n", len));
+        if (bits < 15) {
+          hold += input[_in++] << bits;
+          bits += 8;
+          hold += input[_in++] << bits;
+          bits += 8;
+        }
+        here = dcode[hold & dmask];
+
+        dodist:
+        for (;;) { // goto emulation
+          op = here >>> 24/*here.bits*/;
+          hold >>>= op;
+          bits -= op;
+          op = (here >>> 16) & 0xff/*here.op*/;
+
+          if (op & 16) {                      /* distance base */
+            dist = here & 0xffff/*here.val*/;
+            op &= 15;                       /* number of extra bits */
+            if (bits < op) {
+              hold += input[_in++] << bits;
+              bits += 8;
+              if (bits < op) {
+                hold += input[_in++] << bits;
+                bits += 8;
+              }
+            }
+            dist += hold & ((1 << op) - 1);
+//#ifdef INFLATE_STRICT
+            if (dist > dmax) {
+              strm.msg = 'invalid distance too far back';
+              state.mode = BAD;
+              break top;
+            }
+//#endif
+            hold >>>= op;
+            bits -= op;
+            //Tracevv((stderr, "inflate:         distance %u\n", dist));
+            op = _out - beg;                /* max distance in output */
+            if (dist > op) {                /* see if copy from window */
+              op = dist - op;               /* distance back in window */
+              if (op > whave) {
+                if (state.sane) {
+                  strm.msg = 'invalid distance too far back';
+                  state.mode = BAD;
+                  break top;
+                }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+//                if (len <= op - whave) {
+//                  do {
+//                    output[_out++] = 0;
+//                  } while (--len);
+//                  continue top;
+//                }
+//                len -= op - whave;
+//                do {
+//                  output[_out++] = 0;
+//                } while (--op > whave);
+//                if (op === 0) {
+//                  from = _out - dist;
+//                  do {
+//                    output[_out++] = output[from++];
+//                  } while (--len);
+//                  continue top;
+//                }
+//#endif
+              }
+              from = 0; // window index
+              from_source = s_window;
+              if (wnext === 0) {           /* very common case */
+                from += wsize - op;
+                if (op < len) {         /* some from window */
+                  len -= op;
+                  do {
+                    output[_out++] = s_window[from++];
+                  } while (--op);
+                  from = _out - dist;  /* rest from output */
+                  from_source = output;
+                }
+              }
+              else if (wnext < op) {      /* wrap around window */
+                from += wsize + wnext - op;
+                op -= wnext;
+                if (op < len) {         /* some from end of window */
+                  len -= op;
+                  do {
+                    output[_out++] = s_window[from++];
+                  } while (--op);
+                  from = 0;
+                  if (wnext < len) {  /* some from start of window */
+                    op = wnext;
+                    len -= op;
+                    do {
+                      output[_out++] = s_window[from++];
+                    } while (--op);
+                    from = _out - dist;      /* rest from output */
+                    from_source = output;
+                  }
+                }
+              }
+              else {                      /* contiguous in window */
+                from += wnext - op;
+                if (op < len) {         /* some from window */
+                  len -= op;
+                  do {
+                    output[_out++] = s_window[from++];
+                  } while (--op);
+                  from = _out - dist;  /* rest from output */
+                  from_source = output;
+                }
+              }
+              while (len > 2) {
+                output[_out++] = from_source[from++];
+                output[_out++] = from_source[from++];
+                output[_out++] = from_source[from++];
+                len -= 3;
+              }
+              if (len) {
+                output[_out++] = from_source[from++];
+                if (len > 1) {
+                  output[_out++] = from_source[from++];
+                }
+              }
+            }
+            else {
+              from = _out - dist;          /* copy direct from output */
+              do {                        /* minimum length is three */
+                output[_out++] = output[from++];
+                output[_out++] = output[from++];
+                output[_out++] = output[from++];
+                len -= 3;
+              } while (len > 2);
+              if (len) {
+                output[_out++] = output[from++];
+                if (len > 1) {
+                  output[_out++] = output[from++];
+                }
+              }
+            }
+          }
+          else if ((op & 64) === 0) {          /* 2nd level distance code */
+            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+            continue dodist;
+          }
+          else {
+            strm.msg = 'invalid distance code';
+            state.mode = BAD;
+            break top;
+          }
+
+          break; // need to emulate goto via "continue"
+        }
+      }
+      else if ((op & 64) === 0) {              /* 2nd level length code */
+        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+        continue dolen;
+      }
+      else if (op & 32) {                     /* end-of-block */
+        //Tracevv((stderr, "inflate:         end of block\n"));
+        state.mode = TYPE;
+        break top;
+      }
+      else {
+        strm.msg = 'invalid literal/length code';
+        state.mode = BAD;
+        break top;
+      }
+
+      break; // need to emulate goto via "continue"
+    }
+  } while (_in < last && _out < end);
+
+  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+  len = bits >> 3;
+  _in -= len;
+  bits -= len << 3;
+  hold &= (1 << bits) - 1;
+
+  /* update state and return */
+  strm.next_in = _in;
+  strm.next_out = _out;
+  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
+  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
+  state.hold = hold;
+  state.bits = bits;
+  return;
+};

+ 1527 - 0
static/novnc/vendor/pako/lib/zlib/inflate.js

@@ -0,0 +1,1527 @@
+import * as utils from "../utils/common.js";
+import adler32 from "./adler32.js";
+import crc32 from "./crc32.js";
+import inflate_fast from "./inffast.js";
+import inflate_table from "./inftrees.js";
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+//var Z_NO_FLUSH      = 0;
+//var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH    = 2;
+//var Z_FULL_FLUSH    = 3;
+var Z_FINISH        = 4;
+var Z_BLOCK         = 5;
+var Z_TREES         = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK            = 0;
+var Z_STREAM_END    = 1;
+var Z_NEED_DICT     = 2;
+//var Z_ERRNO         = -1;
+var Z_STREAM_ERROR  = -2;
+var Z_DATA_ERROR    = -3;
+var Z_MEM_ERROR     = -4;
+var Z_BUF_ERROR     = -5;
+//var Z_VERSION_ERROR = -6;
+
+/* The deflate compression method */
+var Z_DEFLATED  = 8;
+
+
+/* STATES ====================================================================*/
+/* ===========================================================================*/
+
+
+var    HEAD = 1;       /* i: waiting for magic header */
+var    FLAGS = 2;      /* i: waiting for method and flags (gzip) */
+var    TIME = 3;       /* i: waiting for modification time (gzip) */
+var    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */
+var    EXLEN = 5;      /* i: waiting for extra length (gzip) */
+var    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */
+var    NAME = 7;       /* i: waiting for end of file name (gzip) */
+var    COMMENT = 8;    /* i: waiting for end of comment (gzip) */
+var    HCRC = 9;       /* i: waiting for header crc (gzip) */
+var    DICTID = 10;    /* i: waiting for dictionary check value */
+var    DICT = 11;      /* waiting for inflateSetDictionary() call */
+var        TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
+var        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */
+var        STORED = 14;    /* i: waiting for stored size (length and complement) */
+var        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */
+var        COPY = 16;      /* i/o: waiting for input or output to copy stored block */
+var        TABLE = 17;     /* i: waiting for dynamic block table lengths */
+var        LENLENS = 18;   /* i: waiting for code length code lengths */
+var        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */
+var            LEN_ = 20;      /* i: same as LEN below, but only first time in */
+var            LEN = 21;       /* i: waiting for length/lit/eob code */
+var            LENEXT = 22;    /* i: waiting for length extra bits */
+var            DIST = 23;      /* i: waiting for distance code */
+var            DISTEXT = 24;   /* i: waiting for distance extra bits */
+var            MATCH = 25;     /* o: waiting for output space to copy string */
+var            LIT = 26;       /* o: waiting for output space to write literal */
+var    CHECK = 27;     /* i: waiting for 32-bit check value */
+var    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */
+var    DONE = 29;      /* finished check, done -- remain here until reset */
+var    BAD = 30;       /* got a data error -- remain here until reset */
+var    MEM = 31;       /* got an inflate() memory error -- remain here until reset */
+var    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */
+
+/* ===========================================================================*/
+
+
+
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);
+
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_WBITS = MAX_WBITS;
+
+
+function zswap32(q) {
+  return  (((q >>> 24) & 0xff) +
+          ((q >>> 8) & 0xff00) +
+          ((q & 0xff00) << 8) +
+          ((q & 0xff) << 24));
+}
+
+
+function InflateState() {
+  this.mode = 0;             /* current inflate mode */
+  this.last = false;          /* true if processing last block */
+  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
+  this.havedict = false;      /* true if dictionary provided */
+  this.flags = 0;             /* gzip header method and flags (0 if zlib) */
+  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */
+  this.check = 0;             /* protected copy of check value */
+  this.total = 0;             /* protected copy of output count */
+  // TODO: may be {}
+  this.head = null;           /* where to save gzip header information */
+
+  /* sliding window */
+  this.wbits = 0;             /* log base 2 of requested window size */
+  this.wsize = 0;             /* window size or zero if not using window */
+  this.whave = 0;             /* valid bytes in the window */
+  this.wnext = 0;             /* window write index */
+  this.window = null;         /* allocated sliding window, if needed */
+
+  /* bit accumulator */
+  this.hold = 0;              /* input bit accumulator */
+  this.bits = 0;              /* number of bits in "in" */
+
+  /* for string and stored block copying */
+  this.length = 0;            /* literal or length of data to copy */
+  this.offset = 0;            /* distance back to copy string from */
+
+  /* for table and code decoding */
+  this.extra = 0;             /* extra bits needed */
+
+  /* fixed and dynamic code tables */
+  this.lencode = null;          /* starting table for length/literal codes */
+  this.distcode = null;         /* starting table for distance codes */
+  this.lenbits = 0;           /* index bits for lencode */
+  this.distbits = 0;          /* index bits for distcode */
+
+  /* dynamic table building */
+  this.ncode = 0;             /* number of code length code lengths */
+  this.nlen = 0;              /* number of length code lengths */
+  this.ndist = 0;             /* number of distance code lengths */
+  this.have = 0;              /* number of code lengths in lens[] */
+  this.next = null;              /* next available space in codes[] */
+
+  this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
+  this.work = new utils.Buf16(288); /* work area for code table building */
+
+  /*
+   because we don't have pointers in js, we use lencode and distcode directly
+   as buffers so we don't need codes
+  */
+  //this.codes = new utils.Buf32(ENOUGH);       /* space for code tables */
+  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */
+  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */
+  this.sane = 0;                   /* if false, allow invalid distance too far */
+  this.back = 0;                   /* bits back of last unprocessed length/lit */
+  this.was = 0;                    /* initial length of match */
+}
+
+function inflateResetKeep(strm) {
+  var state;
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+  strm.total_in = strm.total_out = state.total = 0;
+  strm.msg = ''; /*Z_NULL*/
+  if (state.wrap) {       /* to support ill-conceived Java test suite */
+    strm.adler = state.wrap & 1;
+  }
+  state.mode = HEAD;
+  state.last = 0;
+  state.havedict = 0;
+  state.dmax = 32768;
+  state.head = null/*Z_NULL*/;
+  state.hold = 0;
+  state.bits = 0;
+  //state.lencode = state.distcode = state.next = state.codes;
+  state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
+  state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);
+
+  state.sane = 1;
+  state.back = -1;
+  //Tracev((stderr, "inflate: reset\n"));
+  return Z_OK;
+}
+
+function inflateReset(strm) {
+  var state;
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+  state.wsize = 0;
+  state.whave = 0;
+  state.wnext = 0;
+  return inflateResetKeep(strm);
+
+}
+
+function inflateReset2(strm, windowBits) {
+  var wrap;
+  var state;
+
+  /* get the state */
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+
+  /* extract wrap request from windowBits parameter */
+  if (windowBits < 0) {
+    wrap = 0;
+    windowBits = -windowBits;
+  }
+  else {
+    wrap = (windowBits >> 4) + 1;
+    if (windowBits < 48) {
+      windowBits &= 15;
+    }
+  }
+
+  /* set number of window bits, free window if different */
+  if (windowBits && (windowBits < 8 || windowBits > 15)) {
+    return Z_STREAM_ERROR;
+  }
+  if (state.window !== null && state.wbits !== windowBits) {
+    state.window = null;
+  }
+
+  /* update state and reset the rest of it */
+  state.wrap = wrap;
+  state.wbits = windowBits;
+  return inflateReset(strm);
+}
+
+function inflateInit2(strm, windowBits) {
+  var ret;
+  var state;
+
+  if (!strm) { return Z_STREAM_ERROR; }
+  //strm.msg = Z_NULL;                 /* in case we return an error */
+
+  state = new InflateState();
+
+  //if (state === Z_NULL) return Z_MEM_ERROR;
+  //Tracev((stderr, "inflate: allocated\n"));
+  strm.state = state;
+  state.window = null/*Z_NULL*/;
+  ret = inflateReset2(strm, windowBits);
+  if (ret !== Z_OK) {
+    strm.state = null/*Z_NULL*/;
+  }
+  return ret;
+}
+
+function inflateInit(strm) {
+  return inflateInit2(strm, DEF_WBITS);
+}
+
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter.  This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time.  However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+var virgin = true;
+
+var lenfix, distfix; // We have no pointers in JS, so keep tables separate
+
+function fixedtables(state) {
+  /* build fixed huffman tables if first call (may not be thread safe) */
+  if (virgin) {
+    var sym;
+
+    lenfix = new utils.Buf32(512);
+    distfix = new utils.Buf32(32);
+
+    /* literal/length table */
+    sym = 0;
+    while (sym < 144) { state.lens[sym++] = 8; }
+    while (sym < 256) { state.lens[sym++] = 9; }
+    while (sym < 280) { state.lens[sym++] = 7; }
+    while (sym < 288) { state.lens[sym++] = 8; }
+
+    inflate_table(LENS,  state.lens, 0, 288, lenfix,   0, state.work, { bits: 9 });
+
+    /* distance table */
+    sym = 0;
+    while (sym < 32) { state.lens[sym++] = 5; }
+
+    inflate_table(DISTS, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });
+
+    /* do this just once */
+    virgin = false;
+  }
+
+  state.lencode = lenfix;
+  state.lenbits = 9;
+  state.distcode = distfix;
+  state.distbits = 5;
+}
+
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning.  If window does not exist yet, create it.  This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+function updatewindow(strm, src, end, copy) {
+  var dist;
+  var state = strm.state;
+
+  /* if it hasn't been done already, allocate space for the window */
+  if (state.window === null) {
+    state.wsize = 1 << state.wbits;
+    state.wnext = 0;
+    state.whave = 0;
+
+    state.window = new utils.Buf8(state.wsize);
+  }
+
+  /* copy state->wsize or less output bytes into the circular window */
+  if (copy >= state.wsize) {
+    utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);
+    state.wnext = 0;
+    state.whave = state.wsize;
+  }
+  else {
+    dist = state.wsize - state.wnext;
+    if (dist > copy) {
+      dist = copy;
+    }
+    //zmemcpy(state->window + state->wnext, end - copy, dist);
+    utils.arraySet(state.window, src, end - copy, dist, state.wnext);
+    copy -= dist;
+    if (copy) {
+      //zmemcpy(state->window, end - copy, copy);
+      utils.arraySet(state.window, src, end - copy, copy, 0);
+      state.wnext = copy;
+      state.whave = state.wsize;
+    }
+    else {
+      state.wnext += dist;
+      if (state.wnext === state.wsize) { state.wnext = 0; }
+      if (state.whave < state.wsize) { state.whave += dist; }
+    }
+  }
+  return 0;
+}
+
+function inflate(strm, flush) {
+  var state;
+  var input, output;          // input/output buffers
+  var next;                   /* next input INDEX */
+  var put;                    /* next output INDEX */
+  var have, left;             /* available input and output */
+  var hold;                   /* bit buffer */
+  var bits;                   /* bits in bit buffer */
+  var _in, _out;              /* save starting available input and output */
+  var copy;                   /* number of stored or match bytes to copy */
+  var from;                   /* where to copy match bytes from */
+  var from_source;
+  var here = 0;               /* current decoding table entry */
+  var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
+  //var last;                   /* parent table entry */
+  var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
+  var len;                    /* length to copy for repeats, bits to drop */
+  var ret;                    /* return code */
+  var hbuf = new utils.Buf8(4);    /* buffer for gzip header crc calculation */
+  var opts;
+
+  var n; // temporary var for NEED_BITS
+
+  var order = /* permutation of code lengths */
+    [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];
+
+
+  if (!strm || !strm.state || !strm.output ||
+      (!strm.input && strm.avail_in !== 0)) {
+    return Z_STREAM_ERROR;
+  }
+
+  state = strm.state;
+  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */
+
+
+  //--- LOAD() ---
+  put = strm.next_out;
+  output = strm.output;
+  left = strm.avail_out;
+  next = strm.next_in;
+  input = strm.input;
+  have = strm.avail_in;
+  hold = state.hold;
+  bits = state.bits;
+  //---
+
+  _in = have;
+  _out = left;
+  ret = Z_OK;
+
+  inf_leave: // goto emulation
+  for (;;) {
+    switch (state.mode) {
+    case HEAD:
+      if (state.wrap === 0) {
+        state.mode = TYPEDO;
+        break;
+      }
+      //=== NEEDBITS(16);
+      while (bits < 16) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */
+        state.check = 0/*crc32(0L, Z_NULL, 0)*/;
+        //=== CRC2(state.check, hold);
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        state.check = crc32(state.check, hbuf, 2, 0);
+        //===//
+
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = FLAGS;
+        break;
+      }
+      state.flags = 0;           /* expect zlib header */
+      if (state.head) {
+        state.head.done = false;
+      }
+      if (!(state.wrap & 1) ||   /* check if zlib header allowed */
+        (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
+        strm.msg = 'incorrect header check';
+        state.mode = BAD;
+        break;
+      }
+      if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
+        strm.msg = 'unknown compression method';
+        state.mode = BAD;
+        break;
+      }
+      //--- DROPBITS(4) ---//
+      hold >>>= 4;
+      bits -= 4;
+      //---//
+      len = (hold & 0x0f)/*BITS(4)*/ + 8;
+      if (state.wbits === 0) {
+        state.wbits = len;
+      }
+      else if (len > state.wbits) {
+        strm.msg = 'invalid window size';
+        state.mode = BAD;
+        break;
+      }
+      state.dmax = 1 << len;
+      //Tracev((stderr, "inflate:   zlib header ok\n"));
+      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+      state.mode = hold & 0x200 ? DICTID : TYPE;
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      break;
+    case FLAGS:
+      //=== NEEDBITS(16); */
+      while (bits < 16) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      state.flags = hold;
+      if ((state.flags & 0xff) !== Z_DEFLATED) {
+        strm.msg = 'unknown compression method';
+        state.mode = BAD;
+        break;
+      }
+      if (state.flags & 0xe000) {
+        strm.msg = 'unknown header flags set';
+        state.mode = BAD;
+        break;
+      }
+      if (state.head) {
+        state.head.text = ((hold >> 8) & 1);
+      }
+      if (state.flags & 0x0200) {
+        //=== CRC2(state.check, hold);
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        state.check = crc32(state.check, hbuf, 2, 0);
+        //===//
+      }
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = TIME;
+      /* falls through */
+    case TIME:
+      //=== NEEDBITS(32); */
+      while (bits < 32) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if (state.head) {
+        state.head.time = hold;
+      }
+      if (state.flags & 0x0200) {
+        //=== CRC4(state.check, hold)
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        hbuf[2] = (hold >>> 16) & 0xff;
+        hbuf[3] = (hold >>> 24) & 0xff;
+        state.check = crc32(state.check, hbuf, 4, 0);
+        //===
+      }
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = OS;
+      /* falls through */
+    case OS:
+      //=== NEEDBITS(16); */
+      while (bits < 16) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if (state.head) {
+        state.head.xflags = (hold & 0xff);
+        state.head.os = (hold >> 8);
+      }
+      if (state.flags & 0x0200) {
+        //=== CRC2(state.check, hold);
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        state.check = crc32(state.check, hbuf, 2, 0);
+        //===//
+      }
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = EXLEN;
+      /* falls through */
+    case EXLEN:
+      if (state.flags & 0x0400) {
+        //=== NEEDBITS(16); */
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.length = hold;
+        if (state.head) {
+          state.head.extra_len = hold;
+        }
+        if (state.flags & 0x0200) {
+          //=== CRC2(state.check, hold);
+          hbuf[0] = hold & 0xff;
+          hbuf[1] = (hold >>> 8) & 0xff;
+          state.check = crc32(state.check, hbuf, 2, 0);
+          //===//
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+      }
+      else if (state.head) {
+        state.head.extra = null/*Z_NULL*/;
+      }
+      state.mode = EXTRA;
+      /* falls through */
+    case EXTRA:
+      if (state.flags & 0x0400) {
+        copy = state.length;
+        if (copy > have) { copy = have; }
+        if (copy) {
+          if (state.head) {
+            len = state.head.extra_len - state.length;
+            if (!state.head.extra) {
+              // Use untyped array for more conveniend processing later
+              state.head.extra = new Array(state.head.extra_len);
+            }
+            utils.arraySet(
+              state.head.extra,
+              input,
+              next,
+              // extra field is limited to 65536 bytes
+              // - no need for additional size check
+              copy,
+              /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
+              len
+            );
+            //zmemcpy(state.head.extra + len, next,
+            //        len + copy > state.head.extra_max ?
+            //        state.head.extra_max - len : copy);
+          }
+          if (state.flags & 0x0200) {
+            state.check = crc32(state.check, input, copy, next);
+          }
+          have -= copy;
+          next += copy;
+          state.length -= copy;
+        }
+        if (state.length) { break inf_leave; }
+      }
+      state.length = 0;
+      state.mode = NAME;
+      /* falls through */
+    case NAME:
+      if (state.flags & 0x0800) {
+        if (have === 0) { break inf_leave; }
+        copy = 0;
+        do {
+          // TODO: 2 or 1 bytes?
+          len = input[next + copy++];
+          /* use constant limit because in js we should not preallocate memory */
+          if (state.head && len &&
+              (state.length < 65536 /*state.head.name_max*/)) {
+            state.head.name += String.fromCharCode(len);
+          }
+        } while (len && copy < have);
+
+        if (state.flags & 0x0200) {
+          state.check = crc32(state.check, input, copy, next);
+        }
+        have -= copy;
+        next += copy;
+        if (len) { break inf_leave; }
+      }
+      else if (state.head) {
+        state.head.name = null;
+      }
+      state.length = 0;
+      state.mode = COMMENT;
+      /* falls through */
+    case COMMENT:
+      if (state.flags & 0x1000) {
+        if (have === 0) { break inf_leave; }
+        copy = 0;
+        do {
+          len = input[next + copy++];
+          /* use constant limit because in js we should not preallocate memory */
+          if (state.head && len &&
+              (state.length < 65536 /*state.head.comm_max*/)) {
+            state.head.comment += String.fromCharCode(len);
+          }
+        } while (len && copy < have);
+        if (state.flags & 0x0200) {
+          state.check = crc32(state.check, input, copy, next);
+        }
+        have -= copy;
+        next += copy;
+        if (len) { break inf_leave; }
+      }
+      else if (state.head) {
+        state.head.comment = null;
+      }
+      state.mode = HCRC;
+      /* falls through */
+    case HCRC:
+      if (state.flags & 0x0200) {
+        //=== NEEDBITS(16); */
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if (hold !== (state.check & 0xffff)) {
+          strm.msg = 'header crc mismatch';
+          state.mode = BAD;
+          break;
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+      }
+      if (state.head) {
+        state.head.hcrc = ((state.flags >> 9) & 1);
+        state.head.done = true;
+      }
+      strm.adler = state.check = 0;
+      state.mode = TYPE;
+      break;
+    case DICTID:
+      //=== NEEDBITS(32); */
+      while (bits < 32) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      strm.adler = state.check = zswap32(hold);
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = DICT;
+      /* falls through */
+    case DICT:
+      if (state.havedict === 0) {
+        //--- RESTORE() ---
+        strm.next_out = put;
+        strm.avail_out = left;
+        strm.next_in = next;
+        strm.avail_in = have;
+        state.hold = hold;
+        state.bits = bits;
+        //---
+        return Z_NEED_DICT;
+      }
+      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+      state.mode = TYPE;
+      /* falls through */
+    case TYPE:
+      if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
+      /* falls through */
+    case TYPEDO:
+      if (state.last) {
+        //--- BYTEBITS() ---//
+        hold >>>= bits & 7;
+        bits -= bits & 7;
+        //---//
+        state.mode = CHECK;
+        break;
+      }
+      //=== NEEDBITS(3); */
+      while (bits < 3) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      state.last = (hold & 0x01)/*BITS(1)*/;
+      //--- DROPBITS(1) ---//
+      hold >>>= 1;
+      bits -= 1;
+      //---//
+
+      switch ((hold & 0x03)/*BITS(2)*/) {
+      case 0:                             /* stored block */
+        //Tracev((stderr, "inflate:     stored block%s\n",
+        //        state.last ? " (last)" : ""));
+        state.mode = STORED;
+        break;
+      case 1:                             /* fixed block */
+        fixedtables(state);
+        //Tracev((stderr, "inflate:     fixed codes block%s\n",
+        //        state.last ? " (last)" : ""));
+        state.mode = LEN_;             /* decode codes */
+        if (flush === Z_TREES) {
+          //--- DROPBITS(2) ---//
+          hold >>>= 2;
+          bits -= 2;
+          //---//
+          break inf_leave;
+        }
+        break;
+      case 2:                             /* dynamic block */
+        //Tracev((stderr, "inflate:     dynamic codes block%s\n",
+        //        state.last ? " (last)" : ""));
+        state.mode = TABLE;
+        break;
+      case 3:
+        strm.msg = 'invalid block type';
+        state.mode = BAD;
+      }
+      //--- DROPBITS(2) ---//
+      hold >>>= 2;
+      bits -= 2;
+      //---//
+      break;
+    case STORED:
+      //--- BYTEBITS() ---// /* go to byte boundary */
+      hold >>>= bits & 7;
+      bits -= bits & 7;
+      //---//
+      //=== NEEDBITS(32); */
+      while (bits < 32) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
+        strm.msg = 'invalid stored block lengths';
+        state.mode = BAD;
+        break;
+      }
+      state.length = hold & 0xffff;
+      //Tracev((stderr, "inflate:       stored length %u\n",
+      //        state.length));
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = COPY_;
+      if (flush === Z_TREES) { break inf_leave; }
+      /* falls through */
+    case COPY_:
+      state.mode = COPY;
+      /* falls through */
+    case COPY:
+      copy = state.length;
+      if (copy) {
+        if (copy > have) { copy = have; }
+        if (copy > left) { copy = left; }
+        if (copy === 0) { break inf_leave; }
+        //--- zmemcpy(put, next, copy); ---
+        utils.arraySet(output, input, next, copy, put);
+        //---//
+        have -= copy;
+        next += copy;
+        left -= copy;
+        put += copy;
+        state.length -= copy;
+        break;
+      }
+      //Tracev((stderr, "inflate:       stored end\n"));
+      state.mode = TYPE;
+      break;
+    case TABLE:
+      //=== NEEDBITS(14); */
+      while (bits < 14) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
+      //--- DROPBITS(5) ---//
+      hold >>>= 5;
+      bits -= 5;
+      //---//
+      state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
+      //--- DROPBITS(5) ---//
+      hold >>>= 5;
+      bits -= 5;
+      //---//
+      state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
+      //--- DROPBITS(4) ---//
+      hold >>>= 4;
+      bits -= 4;
+      //---//
+//#ifndef PKZIP_BUG_WORKAROUND
+      if (state.nlen > 286 || state.ndist > 30) {
+        strm.msg = 'too many length or distance symbols';
+        state.mode = BAD;
+        break;
+      }
+//#endif
+      //Tracev((stderr, "inflate:       table sizes ok\n"));
+      state.have = 0;
+      state.mode = LENLENS;
+      /* falls through */
+    case LENLENS:
+      while (state.have < state.ncode) {
+        //=== NEEDBITS(3);
+        while (bits < 3) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
+        //--- DROPBITS(3) ---//
+        hold >>>= 3;
+        bits -= 3;
+        //---//
+      }
+      while (state.have < 19) {
+        state.lens[order[state.have++]] = 0;
+      }
+      // We have separate tables & no pointers. 2 commented lines below not needed.
+      //state.next = state.codes;
+      //state.lencode = state.next;
+      // Switch to use dynamic table
+      state.lencode = state.lendyn;
+      state.lenbits = 7;
+
+      opts = { bits: state.lenbits };
+      ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+      state.lenbits = opts.bits;
+
+      if (ret) {
+        strm.msg = 'invalid code lengths set';
+        state.mode = BAD;
+        break;
+      }
+      //Tracev((stderr, "inflate:       code lengths ok\n"));
+      state.have = 0;
+      state.mode = CODELENS;
+      /* falls through */
+    case CODELENS:
+      while (state.have < state.nlen + state.ndist) {
+        for (;;) {
+          here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        if (here_val < 16) {
+          //--- DROPBITS(here.bits) ---//
+          hold >>>= here_bits;
+          bits -= here_bits;
+          //---//
+          state.lens[state.have++] = here_val;
+        }
+        else {
+          if (here_val === 16) {
+            //=== NEEDBITS(here.bits + 2);
+            n = here_bits + 2;
+            while (bits < n) {
+              if (have === 0) { break inf_leave; }
+              have--;
+              hold += input[next++] << bits;
+              bits += 8;
+            }
+            //===//
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            if (state.have === 0) {
+              strm.msg = 'invalid bit length repeat';
+              state.mode = BAD;
+              break;
+            }
+            len = state.lens[state.have - 1];
+            copy = 3 + (hold & 0x03);//BITS(2);
+            //--- DROPBITS(2) ---//
+            hold >>>= 2;
+            bits -= 2;
+            //---//
+          }
+          else if (here_val === 17) {
+            //=== NEEDBITS(here.bits + 3);
+            n = here_bits + 3;
+            while (bits < n) {
+              if (have === 0) { break inf_leave; }
+              have--;
+              hold += input[next++] << bits;
+              bits += 8;
+            }
+            //===//
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            len = 0;
+            copy = 3 + (hold & 0x07);//BITS(3);
+            //--- DROPBITS(3) ---//
+            hold >>>= 3;
+            bits -= 3;
+            //---//
+          }
+          else {
+            //=== NEEDBITS(here.bits + 7);
+            n = here_bits + 7;
+            while (bits < n) {
+              if (have === 0) { break inf_leave; }
+              have--;
+              hold += input[next++] << bits;
+              bits += 8;
+            }
+            //===//
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            len = 0;
+            copy = 11 + (hold & 0x7f);//BITS(7);
+            //--- DROPBITS(7) ---//
+            hold >>>= 7;
+            bits -= 7;
+            //---//
+          }
+          if (state.have + copy > state.nlen + state.ndist) {
+            strm.msg = 'invalid bit length repeat';
+            state.mode = BAD;
+            break;
+          }
+          while (copy--) {
+            state.lens[state.have++] = len;
+          }
+        }
+      }
+
+      /* handle error breaks in while */
+      if (state.mode === BAD) { break; }
+
+      /* check for end-of-block code (better have one) */
+      if (state.lens[256] === 0) {
+        strm.msg = 'invalid code -- missing end-of-block';
+        state.mode = BAD;
+        break;
+      }
+
+      /* build code tables -- note: do not change the lenbits or distbits
+         values here (9 and 6) without reading the comments in inftrees.h
+         concerning the ENOUGH constants, which depend on those values */
+      state.lenbits = 9;
+
+      opts = { bits: state.lenbits };
+      ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+      // We have separate tables & no pointers. 2 commented lines below not needed.
+      // state.next_index = opts.table_index;
+      state.lenbits = opts.bits;
+      // state.lencode = state.next;
+
+      if (ret) {
+        strm.msg = 'invalid literal/lengths set';
+        state.mode = BAD;
+        break;
+      }
+
+      state.distbits = 6;
+      //state.distcode.copy(state.codes);
+      // Switch to use dynamic table
+      state.distcode = state.distdyn;
+      opts = { bits: state.distbits };
+      ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
+      // We have separate tables & no pointers. 2 commented lines below not needed.
+      // state.next_index = opts.table_index;
+      state.distbits = opts.bits;
+      // state.distcode = state.next;
+
+      if (ret) {
+        strm.msg = 'invalid distances set';
+        state.mode = BAD;
+        break;
+      }
+      //Tracev((stderr, 'inflate:       codes ok\n'));
+      state.mode = LEN_;
+      if (flush === Z_TREES) { break inf_leave; }
+      /* falls through */
+    case LEN_:
+      state.mode = LEN;
+      /* falls through */
+    case LEN:
+      if (have >= 6 && left >= 258) {
+        //--- RESTORE() ---
+        strm.next_out = put;
+        strm.avail_out = left;
+        strm.next_in = next;
+        strm.avail_in = have;
+        state.hold = hold;
+        state.bits = bits;
+        //---
+        inflate_fast(strm, _out);
+        //--- LOAD() ---
+        put = strm.next_out;
+        output = strm.output;
+        left = strm.avail_out;
+        next = strm.next_in;
+        input = strm.input;
+        have = strm.avail_in;
+        hold = state.hold;
+        bits = state.bits;
+        //---
+
+        if (state.mode === TYPE) {
+          state.back = -1;
+        }
+        break;
+      }
+      state.back = 0;
+      for (;;) {
+        here = state.lencode[hold & ((1 << state.lenbits) - 1)];  /*BITS(state.lenbits)*/
+        here_bits = here >>> 24;
+        here_op = (here >>> 16) & 0xff;
+        here_val = here & 0xffff;
+
+        if (here_bits <= bits) { break; }
+        //--- PULLBYTE() ---//
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+        //---//
+      }
+      if (here_op && (here_op & 0xf0) === 0) {
+        last_bits = here_bits;
+        last_op = here_op;
+        last_val = here_val;
+        for (;;) {
+          here = state.lencode[last_val +
+                  ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((last_bits + here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        //--- DROPBITS(last.bits) ---//
+        hold >>>= last_bits;
+        bits -= last_bits;
+        //---//
+        state.back += last_bits;
+      }
+      //--- DROPBITS(here.bits) ---//
+      hold >>>= here_bits;
+      bits -= here_bits;
+      //---//
+      state.back += here_bits;
+      state.length = here_val;
+      if (here_op === 0) {
+        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+        //        "inflate:         literal '%c'\n" :
+        //        "inflate:         literal 0x%02x\n", here.val));
+        state.mode = LIT;
+        break;
+      }
+      if (here_op & 32) {
+        //Tracevv((stderr, "inflate:         end of block\n"));
+        state.back = -1;
+        state.mode = TYPE;
+        break;
+      }
+      if (here_op & 64) {
+        strm.msg = 'invalid literal/length code';
+        state.mode = BAD;
+        break;
+      }
+      state.extra = here_op & 15;
+      state.mode = LENEXT;
+      /* falls through */
+    case LENEXT:
+      if (state.extra) {
+        //=== NEEDBITS(state.extra);
+        n = state.extra;
+        while (bits < n) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+        //--- DROPBITS(state.extra) ---//
+        hold >>>= state.extra;
+        bits -= state.extra;
+        //---//
+        state.back += state.extra;
+      }
+      //Tracevv((stderr, "inflate:         length %u\n", state.length));
+      state.was = state.length;
+      state.mode = DIST;
+      /* falls through */
+    case DIST:
+      for (;;) {
+        here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
+        here_bits = here >>> 24;
+        here_op = (here >>> 16) & 0xff;
+        here_val = here & 0xffff;
+
+        if ((here_bits) <= bits) { break; }
+        //--- PULLBYTE() ---//
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+        //---//
+      }
+      if ((here_op & 0xf0) === 0) {
+        last_bits = here_bits;
+        last_op = here_op;
+        last_val = here_val;
+        for (;;) {
+          here = state.distcode[last_val +
+                  ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((last_bits + here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        //--- DROPBITS(last.bits) ---//
+        hold >>>= last_bits;
+        bits -= last_bits;
+        //---//
+        state.back += last_bits;
+      }
+      //--- DROPBITS(here.bits) ---//
+      hold >>>= here_bits;
+      bits -= here_bits;
+      //---//
+      state.back += here_bits;
+      if (here_op & 64) {
+        strm.msg = 'invalid distance code';
+        state.mode = BAD;
+        break;
+      }
+      state.offset = here_val;
+      state.extra = (here_op) & 15;
+      state.mode = DISTEXT;
+      /* falls through */
+    case DISTEXT:
+      if (state.extra) {
+        //=== NEEDBITS(state.extra);
+        n = state.extra;
+        while (bits < n) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+        //--- DROPBITS(state.extra) ---//
+        hold >>>= state.extra;
+        bits -= state.extra;
+        //---//
+        state.back += state.extra;
+      }
+//#ifdef INFLATE_STRICT
+      if (state.offset > state.dmax) {
+        strm.msg = 'invalid distance too far back';
+        state.mode = BAD;
+        break;
+      }
+//#endif
+      //Tracevv((stderr, "inflate:         distance %u\n", state.offset));
+      state.mode = MATCH;
+      /* falls through */
+    case MATCH:
+      if (left === 0) { break inf_leave; }
+      copy = _out - left;
+      if (state.offset > copy) {         /* copy from window */
+        copy = state.offset - copy;
+        if (copy > state.whave) {
+          if (state.sane) {
+            strm.msg = 'invalid distance too far back';
+            state.mode = BAD;
+            break;
+          }
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+//          Trace((stderr, "inflate.c too far\n"));
+//          copy -= state.whave;
+//          if (copy > state.length) { copy = state.length; }
+//          if (copy > left) { copy = left; }
+//          left -= copy;
+//          state.length -= copy;
+//          do {
+//            output[put++] = 0;
+//          } while (--copy);
+//          if (state.length === 0) { state.mode = LEN; }
+//          break;
+//#endif
+        }
+        if (copy > state.wnext) {
+          copy -= state.wnext;
+          from = state.wsize - copy;
+        }
+        else {
+          from = state.wnext - copy;
+        }
+        if (copy > state.length) { copy = state.length; }
+        from_source = state.window;
+      }
+      else {                              /* copy from output */
+        from_source = output;
+        from = put - state.offset;
+        copy = state.length;
+      }
+      if (copy > left) { copy = left; }
+      left -= copy;
+      state.length -= copy;
+      do {
+        output[put++] = from_source[from++];
+      } while (--copy);
+      if (state.length === 0) { state.mode = LEN; }
+      break;
+    case LIT:
+      if (left === 0) { break inf_leave; }
+      output[put++] = state.length;
+      left--;
+      state.mode = LEN;
+      break;
+    case CHECK:
+      if (state.wrap) {
+        //=== NEEDBITS(32);
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          // Use '|' insdead of '+' to make sure that result is signed
+          hold |= input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        _out -= left;
+        strm.total_out += _out;
+        state.total += _out;
+        if (_out) {
+          strm.adler = state.check =
+              /*UPDATE(state.check, put - _out, _out);*/
+              (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));
+
+        }
+        _out = left;
+        // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
+        if ((state.flags ? hold : zswap32(hold)) !== state.check) {
+          strm.msg = 'incorrect data check';
+          state.mode = BAD;
+          break;
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        //Tracev((stderr, "inflate:   check matches trailer\n"));
+      }
+      state.mode = LENGTH;
+      /* falls through */
+    case LENGTH:
+      if (state.wrap && state.flags) {
+        //=== NEEDBITS(32);
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if (hold !== (state.total & 0xffffffff)) {
+          strm.msg = 'incorrect length check';
+          state.mode = BAD;
+          break;
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        //Tracev((stderr, "inflate:   length matches trailer\n"));
+      }
+      state.mode = DONE;
+      /* falls through */
+    case DONE:
+      ret = Z_STREAM_END;
+      break inf_leave;
+    case BAD:
+      ret = Z_DATA_ERROR;
+      break inf_leave;
+    case MEM:
+      return Z_MEM_ERROR;
+    case SYNC:
+      /* falls through */
+    default:
+      return Z_STREAM_ERROR;
+    }
+  }
+
+  // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"
+
+  /*
+     Return from inflate(), updating the total counts and the check value.
+     If there was no progress during the inflate() call, return a buffer
+     error.  Call updatewindow() to create and/or update the window state.
+     Note: a memory error from inflate() is non-recoverable.
+   */
+
+  //--- RESTORE() ---
+  strm.next_out = put;
+  strm.avail_out = left;
+  strm.next_in = next;
+  strm.avail_in = have;
+  state.hold = hold;
+  state.bits = bits;
+  //---
+
+  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
+                      (state.mode < CHECK || flush !== Z_FINISH))) {
+    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
+      state.mode = MEM;
+      return Z_MEM_ERROR;
+    }
+  }
+  _in -= strm.avail_in;
+  _out -= strm.avail_out;
+  strm.total_in += _in;
+  strm.total_out += _out;
+  state.total += _out;
+  if (state.wrap && _out) {
+    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
+      (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
+  }
+  strm.data_type = state.bits + (state.last ? 64 : 0) +
+                    (state.mode === TYPE ? 128 : 0) +
+                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
+  if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
+    ret = Z_BUF_ERROR;
+  }
+  return ret;
+}
+
+function inflateEnd(strm) {
+
+  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  var state = strm.state;
+  if (state.window) {
+    state.window = null;
+  }
+  strm.state = null;
+  return Z_OK;
+}
+
+function inflateGetHeader(strm, head) {
+  var state;
+
+  /* check state */
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }
+
+  /* save header structure */
+  state.head = head;
+  head.done = false;
+  return Z_OK;
+}
+
+function inflateSetDictionary(strm, dictionary) {
+  var dictLength = dictionary.length;
+
+  var state;
+  var dictid;
+  var ret;
+
+  /* check state */
+  if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; }
+  state = strm.state;
+
+  if (state.wrap !== 0 && state.mode !== DICT) {
+    return Z_STREAM_ERROR;
+  }
+
+  /* check for correct dictionary identifier */
+  if (state.mode === DICT) {
+    dictid = 1; /* adler32(0, null, 0)*/
+    /* dictid = adler32(dictid, dictionary, dictLength); */
+    dictid = adler32(dictid, dictionary, dictLength, 0);
+    if (dictid !== state.check) {
+      return Z_DATA_ERROR;
+    }
+  }
+  /* copy dictionary to window using updatewindow(), which will amend the
+   existing dictionary if appropriate */
+  ret = updatewindow(strm, dictionary, dictLength, dictLength);
+  if (ret) {
+    state.mode = MEM;
+    return Z_MEM_ERROR;
+  }
+  state.havedict = 1;
+  // Tracev((stderr, "inflate:   dictionary set\n"));
+  return Z_OK;
+}
+
+export { inflateReset, inflateReset2, inflateResetKeep, inflateInit, inflateInit2, inflate, inflateEnd, inflateGetHeader, inflateSetDictionary };
+export var inflateInfo = 'pako inflate (from Nodeca project)';
+
+/* Not implemented
+exports.inflateCopy = inflateCopy;
+exports.inflateGetDictionary = inflateGetDictionary;
+exports.inflateMark = inflateMark;
+exports.inflatePrime = inflatePrime;
+exports.inflateSync = inflateSync;
+exports.inflateSyncPoint = inflateSyncPoint;
+exports.inflateUndermine = inflateUndermine;
+*/

+ 322 - 0
static/novnc/vendor/pako/lib/zlib/inftrees.js

@@ -0,0 +1,322 @@
+import * as utils from "../utils/common.js";
+
+var MAXBITS = 15;
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+var lbase = [ /* Length codes 257..285 base */
+  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
+];
+
+var lext = [ /* Length codes 257..285 extra */
+  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
+];
+
+var dbase = [ /* Distance codes 0..29 base */
+  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+  8193, 12289, 16385, 24577, 0, 0
+];
+
+var dext = [ /* Distance codes 0..29 extra */
+  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+  28, 28, 29, 29, 64, 64
+];
+
+export default function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
+{
+  var bits = opts.bits;
+      //here = opts.here; /* table entry for duplication */
+
+  var len = 0;               /* a code's length in bits */
+  var sym = 0;               /* index of code symbols */
+  var min = 0, max = 0;          /* minimum and maximum code lengths */
+  var root = 0;              /* number of index bits for root table */
+  var curr = 0;              /* number of index bits for current table */
+  var drop = 0;              /* code bits to drop for sub-table */
+  var left = 0;                   /* number of prefix codes available */
+  var used = 0;              /* code entries in table used */
+  var huff = 0;              /* Huffman code */
+  var incr;              /* for incrementing code, index */
+  var fill;              /* index for replicating entries */
+  var low;               /* low bits for current root entry */
+  var mask;              /* mask for low root bits */
+  var next;             /* next available space in table */
+  var base = null;     /* base value table to use */
+  var base_index = 0;
+//  var shoextra;    /* extra bits table to use */
+  var end;                    /* use base and extra for symbol > end */
+  var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */
+  var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */
+  var extra = null;
+  var extra_index = 0;
+
+  var here_bits, here_op, here_val;
+
+  /*
+   Process a set of code lengths to create a canonical Huffman code.  The
+   code lengths are lens[0..codes-1].  Each length corresponds to the
+   symbols 0..codes-1.  The Huffman code is generated by first sorting the
+   symbols by length from short to long, and retaining the symbol order
+   for codes with equal lengths.  Then the code starts with all zero bits
+   for the first code of the shortest length, and the codes are integer
+   increments for the same length, and zeros are appended as the length
+   increases.  For the deflate format, these bits are stored backwards
+   from their more natural integer increment ordering, and so when the
+   decoding tables are built in the large loop below, the integer codes
+   are incremented backwards.
+
+   This routine assumes, but does not check, that all of the entries in
+   lens[] are in the range 0..MAXBITS.  The caller must assure this.
+   1..MAXBITS is interpreted as that code length.  zero means that that
+   symbol does not occur in this code.
+
+   The codes are sorted by computing a count of codes for each length,
+   creating from that a table of starting indices for each length in the
+   sorted table, and then entering the symbols in order in the sorted
+   table.  The sorted table is work[], with that space being provided by
+   the caller.
+
+   The length counts are used for other purposes as well, i.e. finding
+   the minimum and maximum length codes, determining if there are any
+   codes at all, checking for a valid set of lengths, and looking ahead
+   at length counts to determine sub-table sizes when building the
+   decoding tables.
+   */
+
+  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+  for (len = 0; len <= MAXBITS; len++) {
+    count[len] = 0;
+  }
+  for (sym = 0; sym < codes; sym++) {
+    count[lens[lens_index + sym]]++;
+  }
+
+  /* bound code lengths, force root to be within code lengths */
+  root = bits;
+  for (max = MAXBITS; max >= 1; max--) {
+    if (count[max] !== 0) { break; }
+  }
+  if (root > max) {
+    root = max;
+  }
+  if (max === 0) {                     /* no symbols to code at all */
+    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
+    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
+    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
+    table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+
+    //table.op[opts.table_index] = 64;
+    //table.bits[opts.table_index] = 1;
+    //table.val[opts.table_index++] = 0;
+    table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+    opts.bits = 1;
+    return 0;     /* no symbols, but wait for decoding to report error */
+  }
+  for (min = 1; min < max; min++) {
+    if (count[min] !== 0) { break; }
+  }
+  if (root < min) {
+    root = min;
+  }
+
+  /* check for an over-subscribed or incomplete set of lengths */
+  left = 1;
+  for (len = 1; len <= MAXBITS; len++) {
+    left <<= 1;
+    left -= count[len];
+    if (left < 0) {
+      return -1;
+    }        /* over-subscribed */
+  }
+  if (left > 0 && (type === CODES || max !== 1)) {
+    return -1;                      /* incomplete set */
+  }
+
+  /* generate offsets into symbol table for each length for sorting */
+  offs[1] = 0;
+  for (len = 1; len < MAXBITS; len++) {
+    offs[len + 1] = offs[len] + count[len];
+  }
+
+  /* sort symbols by length, by symbol order within each length */
+  for (sym = 0; sym < codes; sym++) {
+    if (lens[lens_index + sym] !== 0) {
+      work[offs[lens[lens_index + sym]]++] = sym;
+    }
+  }
+
+  /*
+   Create and fill in decoding tables.  In this loop, the table being
+   filled is at next and has curr index bits.  The code being used is huff
+   with length len.  That code is converted to an index by dropping drop
+   bits off of the bottom.  For codes where len is less than drop + curr,
+   those top drop + curr - len bits are incremented through all values to
+   fill the table with replicated entries.
+
+   root is the number of index bits for the root table.  When len exceeds
+   root, sub-tables are created pointed to by the root entry with an index
+   of the low root bits of huff.  This is saved in low to check for when a
+   new sub-table should be started.  drop is zero when the root table is
+   being filled, and drop is root when sub-tables are being filled.
+
+   When a new sub-table is needed, it is necessary to look ahead in the
+   code lengths to determine what size sub-table is needed.  The length
+   counts are used for this, and so count[] is decremented as codes are
+   entered in the tables.
+
+   used keeps track of how many table entries have been allocated from the
+   provided *table space.  It is checked for LENS and DIST tables against
+   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+   the initial root table size constants.  See the comments in inftrees.h
+   for more information.
+
+   sym increments through all symbols, and the loop terminates when
+   all codes of length max, i.e. all codes, have been processed.  This
+   routine permits incomplete codes, so another loop after this one fills
+   in the rest of the decoding tables with invalid code markers.
+   */
+
+  /* set up for code type */
+  // poor man optimization - use if-else instead of switch,
+  // to avoid deopts in old v8
+  if (type === CODES) {
+    base = extra = work;    /* dummy value--not used */
+    end = 19;
+
+  } else if (type === LENS) {
+    base = lbase;
+    base_index -= 257;
+    extra = lext;
+    extra_index -= 257;
+    end = 256;
+
+  } else {                    /* DISTS */
+    base = dbase;
+    extra = dext;
+    end = -1;
+  }
+
+  /* initialize opts for loop */
+  huff = 0;                   /* starting code */
+  sym = 0;                    /* starting code symbol */
+  len = min;                  /* starting code length */
+  next = table_index;              /* current table to fill in */
+  curr = root;                /* current table index bits */
+  drop = 0;                   /* current bits to drop from code for index */
+  low = -1;                   /* trigger new sub-table when len > root */
+  used = 1 << root;          /* use root table entries */
+  mask = used - 1;            /* mask for comparing low */
+
+  /* check available table space */
+  if ((type === LENS && used > ENOUGH_LENS) ||
+    (type === DISTS && used > ENOUGH_DISTS)) {
+    return 1;
+  }
+
+  /* process all codes and make table entries */
+  for (;;) {
+    /* create table entry */
+    here_bits = len - drop;
+    if (work[sym] < end) {
+      here_op = 0;
+      here_val = work[sym];
+    }
+    else if (work[sym] > end) {
+      here_op = extra[extra_index + work[sym]];
+      here_val = base[base_index + work[sym]];
+    }
+    else {
+      here_op = 32 + 64;         /* end of block */
+      here_val = 0;
+    }
+
+    /* replicate for those indices with low len bits equal to huff */
+    incr = 1 << (len - drop);
+    fill = 1 << curr;
+    min = fill;                 /* save offset to next table */
+    do {
+      fill -= incr;
+      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
+    } while (fill !== 0);
+
+    /* backwards increment the len-bit code huff */
+    incr = 1 << (len - 1);
+    while (huff & incr) {
+      incr >>= 1;
+    }
+    if (incr !== 0) {
+      huff &= incr - 1;
+      huff += incr;
+    } else {
+      huff = 0;
+    }
+
+    /* go to next symbol, update count, len */
+    sym++;
+    if (--count[len] === 0) {
+      if (len === max) { break; }
+      len = lens[lens_index + work[sym]];
+    }
+
+    /* create new sub-table if needed */
+    if (len > root && (huff & mask) !== low) {
+      /* if first time, transition to sub-tables */
+      if (drop === 0) {
+        drop = root;
+      }
+
+      /* increment past last table */
+      next += min;            /* here min is 1 << curr */
+
+      /* determine length of next table */
+      curr = len - drop;
+      left = 1 << curr;
+      while (curr + drop < max) {
+        left -= count[curr + drop];
+        if (left <= 0) { break; }
+        curr++;
+        left <<= 1;
+      }
+
+      /* check for enough space */
+      used += 1 << curr;
+      if ((type === LENS && used > ENOUGH_LENS) ||
+        (type === DISTS && used > ENOUGH_DISTS)) {
+        return 1;
+      }
+
+      /* point entry in root table to sub-table */
+      low = huff & mask;
+      /*table.op[low] = curr;
+      table.bits[low] = root;
+      table.val[low] = next - opts.table_index;*/
+      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
+    }
+  }
+
+  /* fill in remaining table entry if code is incomplete (guaranteed to have
+   at most one remaining entry, since if the code is incomplete, the
+   maximum code length that was allowed to get this far is one bit) */
+  if (huff !== 0) {
+    //table.op[next + huff] = 64;            /* invalid code marker */
+    //table.bits[next + huff] = len - drop;
+    //table.val[next + huff] = 0;
+    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
+  }
+
+  /* set return parameters */
+  //opts.table_index += used;
+  opts.bits = root;
+  return 0;
+};

+ 11 - 0
static/novnc/vendor/pako/lib/zlib/messages.js

@@ -0,0 +1,11 @@
+export default {
+  2:      'need dictionary',     /* Z_NEED_DICT       2  */
+  1:      'stream end',          /* Z_STREAM_END      1  */
+  0:      '',                    /* Z_OK              0  */
+  '-1':   'file error',          /* Z_ERRNO         (-1) */
+  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
+  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
+  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
+  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
+  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
+};

+ 1195 - 0
static/novnc/vendor/pako/lib/zlib/trees.js

@@ -0,0 +1,1195 @@
+import * as utils from "../utils/common.js";
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+//var Z_FILTERED          = 1;
+//var Z_HUFFMAN_ONLY      = 2;
+//var Z_RLE               = 3;
+var Z_FIXED               = 4;
+//var Z_DEFAULT_STRATEGY  = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+var Z_BINARY              = 0;
+var Z_TEXT                = 1;
+//var Z_ASCII             = 1; // = Z_TEXT
+var Z_UNKNOWN             = 2;
+
+/*============================================================================*/
+
+
+function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+// From zutil.h
+
+var STORED_BLOCK = 0;
+var STATIC_TREES = 1;
+var DYN_TREES    = 2;
+/* The three kinds of block type */
+
+var MIN_MATCH    = 3;
+var MAX_MATCH    = 258;
+/* The minimum and maximum match lengths */
+
+// From deflate.h
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+var LENGTH_CODES  = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+
+var LITERALS      = 256;
+/* number of literal bytes 0..255 */
+
+var L_CODES       = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+var D_CODES       = 30;
+/* number of distance codes */
+
+var BL_CODES      = 19;
+/* number of codes used to transfer the bit lengths */
+
+var HEAP_SIZE     = 2 * L_CODES + 1;
+/* maximum heap size */
+
+var MAX_BITS      = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+var Buf_size      = 16;
+/* size of bit buffer in bi_buf */
+
+
+/* ===========================================================================
+ * Constants
+ */
+
+var MAX_BL_BITS = 7;
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+var END_BLOCK   = 256;
+/* end of block literal code */
+
+var REP_3_6     = 16;
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+var REPZ_3_10   = 17;
+/* repeat a zero length 3-10 times  (3 bits of repeat count) */
+
+var REPZ_11_138 = 18;
+/* repeat a zero length 11-138 times  (7 bits of repeat count) */
+
+/* eslint-disable comma-spacing,array-bracket-spacing */
+var extra_lbits =   /* extra bits for each length code */
+  [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
+
+var extra_dbits =   /* extra bits for each distance code */
+  [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
+
+var extra_blbits =  /* extra bits for each bit length code */
+  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];
+
+var bl_order =
+  [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];
+/* eslint-enable comma-spacing,array-bracket-spacing */
+
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+// We pre-fill arrays with 0 to avoid uninitialized gaps
+
+var DIST_CODE_LEN = 512; /* see definition of array dist_code below */
+
+// !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1
+var static_ltree  = new Array((L_CODES + 2) * 2);
+zero(static_ltree);
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+var static_dtree  = new Array(D_CODES * 2);
+zero(static_dtree);
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+var _dist_code    = new Array(DIST_CODE_LEN);
+zero(_dist_code);
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+var _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);
+zero(_length_code);
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+var base_length   = new Array(LENGTH_CODES);
+zero(base_length);
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+var base_dist     = new Array(D_CODES);
+zero(base_dist);
+/* First normalized distance for each code (0 = distance of 1) */
+
+
+function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {
+
+  this.static_tree  = static_tree;  /* static tree or NULL */
+  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */
+  this.extra_base   = extra_base;   /* base index for extra_bits */
+  this.elems        = elems;        /* max number of elements in the tree */
+  this.max_length   = max_length;   /* max bit length for the codes */
+
+  // show if `static_tree` has data or dummy - needed for monomorphic objects
+  this.has_stree    = static_tree && static_tree.length;
+}
+
+
+var static_l_desc;
+var static_d_desc;
+var static_bl_desc;
+
+
+function TreeDesc(dyn_tree, stat_desc) {
+  this.dyn_tree = dyn_tree;     /* the dynamic tree */
+  this.max_code = 0;            /* largest code with non zero frequency */
+  this.stat_desc = stat_desc;   /* the corresponding static tree */
+}
+
+
+
+function d_code(dist) {
+  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
+}
+
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+function put_short(s, w) {
+//    put_byte(s, (uch)((w) & 0xff));
+//    put_byte(s, (uch)((ush)(w) >> 8));
+  s.pending_buf[s.pending++] = (w) & 0xff;
+  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
+}
+
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+function send_bits(s, value, length) {
+  if (s.bi_valid > (Buf_size - length)) {
+    s.bi_buf |= (value << s.bi_valid) & 0xffff;
+    put_short(s, s.bi_buf);
+    s.bi_buf = value >> (Buf_size - s.bi_valid);
+    s.bi_valid += length - Buf_size;
+  } else {
+    s.bi_buf |= (value << s.bi_valid) & 0xffff;
+    s.bi_valid += length;
+  }
+}
+
+
+function send_code(s, c, tree) {
+  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
+}
+
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+function bi_reverse(code, len) {
+  var res = 0;
+  do {
+    res |= code & 1;
+    code >>>= 1;
+    res <<= 1;
+  } while (--len > 0);
+  return res >>> 1;
+}
+
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+function bi_flush(s) {
+  if (s.bi_valid === 16) {
+    put_short(s, s.bi_buf);
+    s.bi_buf = 0;
+    s.bi_valid = 0;
+
+  } else if (s.bi_valid >= 8) {
+    s.pending_buf[s.pending++] = s.bi_buf & 0xff;
+    s.bi_buf >>= 8;
+    s.bi_valid -= 8;
+  }
+}
+
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+function gen_bitlen(s, desc)
+//    deflate_state *s;
+//    tree_desc *desc;    /* the tree descriptor */
+{
+  var tree            = desc.dyn_tree;
+  var max_code        = desc.max_code;
+  var stree           = desc.stat_desc.static_tree;
+  var has_stree       = desc.stat_desc.has_stree;
+  var extra           = desc.stat_desc.extra_bits;
+  var base            = desc.stat_desc.extra_base;
+  var max_length      = desc.stat_desc.max_length;
+  var h;              /* heap index */
+  var n, m;           /* iterate over the tree elements */
+  var bits;           /* bit length */
+  var xbits;          /* extra bits */
+  var f;              /* frequency */
+  var overflow = 0;   /* number of elements with bit length too large */
+
+  for (bits = 0; bits <= MAX_BITS; bits++) {
+    s.bl_count[bits] = 0;
+  }
+
+  /* In a first pass, compute the optimal bit lengths (which may
+   * overflow in the case of the bit length tree).
+   */
+  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */
+
+  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+    n = s.heap[h];
+    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
+    if (bits > max_length) {
+      bits = max_length;
+      overflow++;
+    }
+    tree[n * 2 + 1]/*.Len*/ = bits;
+    /* We overwrite tree[n].Dad which is no longer needed */
+
+    if (n > max_code) { continue; } /* not a leaf node */
+
+    s.bl_count[bits]++;
+    xbits = 0;
+    if (n >= base) {
+      xbits = extra[n - base];
+    }
+    f = tree[n * 2]/*.Freq*/;
+    s.opt_len += f * (bits + xbits);
+    if (has_stree) {
+      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
+    }
+  }
+  if (overflow === 0) { return; }
+
+  // Trace((stderr,"\nbit length overflow\n"));
+  /* This happens for example on obj2 and pic of the Calgary corpus */
+
+  /* Find the first bit length which could increase: */
+  do {
+    bits = max_length - 1;
+    while (s.bl_count[bits] === 0) { bits--; }
+    s.bl_count[bits]--;      /* move one leaf down the tree */
+    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
+    s.bl_count[max_length]--;
+    /* The brother of the overflow item also moves one step up,
+     * but this does not affect bl_count[max_length]
+     */
+    overflow -= 2;
+  } while (overflow > 0);
+
+  /* Now recompute all bit lengths, scanning in increasing frequency.
+   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+   * lengths instead of fixing only the wrong ones. This idea is taken
+   * from 'ar' written by Haruhiko Okumura.)
+   */
+  for (bits = max_length; bits !== 0; bits--) {
+    n = s.bl_count[bits];
+    while (n !== 0) {
+      m = s.heap[--h];
+      if (m > max_code) { continue; }
+      if (tree[m * 2 + 1]/*.Len*/ !== bits) {
+        // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
+        tree[m * 2 + 1]/*.Len*/ = bits;
+      }
+      n--;
+    }
+  }
+}
+
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ *     zero code length.
+ */
+function gen_codes(tree, max_code, bl_count)
+//    ct_data *tree;             /* the tree to decorate */
+//    int max_code;              /* largest code with non zero frequency */
+//    ushf *bl_count;            /* number of codes at each bit length */
+{
+  var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
+  var code = 0;              /* running code value */
+  var bits;                  /* bit index */
+  var n;                     /* code index */
+
+  /* The distribution counts are first used to generate the code values
+   * without bit reversal.
+   */
+  for (bits = 1; bits <= MAX_BITS; bits++) {
+    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
+  }
+  /* Check that the bit counts in bl_count are consistent. The last code
+   * must be all ones.
+   */
+  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+  //        "inconsistent bit counts");
+  //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+  for (n = 0;  n <= max_code; n++) {
+    var len = tree[n * 2 + 1]/*.Len*/;
+    if (len === 0) { continue; }
+    /* Now reverse the bits */
+    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);
+
+    //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+  }
+}
+
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+function tr_static_init() {
+  var n;        /* iterates over tree elements */
+  var bits;     /* bit counter */
+  var length;   /* length value */
+  var code;     /* code value */
+  var dist;     /* distance index */
+  var bl_count = new Array(MAX_BITS + 1);
+  /* number of codes at each bit length for an optimal tree */
+
+  // do check in _tr_init()
+  //if (static_init_done) return;
+
+  /* For some embedded targets, global variables are not initialized: */
+/*#ifdef NO_INIT_GLOBAL_POINTERS
+  static_l_desc.static_tree = static_ltree;
+  static_l_desc.extra_bits = extra_lbits;
+  static_d_desc.static_tree = static_dtree;
+  static_d_desc.extra_bits = extra_dbits;
+  static_bl_desc.extra_bits = extra_blbits;
+#endif*/
+
+  /* Initialize the mapping length (0..255) -> length code (0..28) */
+  length = 0;
+  for (code = 0; code < LENGTH_CODES - 1; code++) {
+    base_length[code] = length;
+    for (n = 0; n < (1 << extra_lbits[code]); n++) {
+      _length_code[length++] = code;
+    }
+  }
+  //Assert (length == 256, "tr_static_init: length != 256");
+  /* Note that the length 255 (match length 258) can be represented
+   * in two different ways: code 284 + 5 bits or code 285, so we
+   * overwrite length_code[255] to use the best encoding:
+   */
+  _length_code[length - 1] = code;
+
+  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+  dist = 0;
+  for (code = 0; code < 16; code++) {
+    base_dist[code] = dist;
+    for (n = 0; n < (1 << extra_dbits[code]); n++) {
+      _dist_code[dist++] = code;
+    }
+  }
+  //Assert (dist == 256, "tr_static_init: dist != 256");
+  dist >>= 7; /* from now on, all distances are divided by 128 */
+  for (; code < D_CODES; code++) {
+    base_dist[code] = dist << 7;
+    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
+      _dist_code[256 + dist++] = code;
+    }
+  }
+  //Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+  /* Construct the codes of the static literal tree */
+  for (bits = 0; bits <= MAX_BITS; bits++) {
+    bl_count[bits] = 0;
+  }
+
+  n = 0;
+  while (n <= 143) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 8;
+    n++;
+    bl_count[8]++;
+  }
+  while (n <= 255) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 9;
+    n++;
+    bl_count[9]++;
+  }
+  while (n <= 279) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 7;
+    n++;
+    bl_count[7]++;
+  }
+  while (n <= 287) {
+    static_ltree[n * 2 + 1]/*.Len*/ = 8;
+    n++;
+    bl_count[8]++;
+  }
+  /* Codes 286 and 287 do not exist, but we must include them in the
+   * tree construction to get a canonical Huffman tree (longest code
+   * all ones)
+   */
+  gen_codes(static_ltree, L_CODES + 1, bl_count);
+
+  /* The static distance tree is trivial: */
+  for (n = 0; n < D_CODES; n++) {
+    static_dtree[n * 2 + 1]/*.Len*/ = 5;
+    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
+  }
+
+  // Now data ready and we can init static trees
+  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);
+  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);
+
+  //static_init_done = true;
+}
+
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+function init_block(s) {
+  var n; /* iterates over tree elements */
+
+  /* Initialize the trees. */
+  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }
+
+  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
+  s.opt_len = s.static_len = 0;
+  s.last_lit = s.matches = 0;
+}
+
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+function bi_windup(s)
+{
+  if (s.bi_valid > 8) {
+    put_short(s, s.bi_buf);
+  } else if (s.bi_valid > 0) {
+    //put_byte(s, (Byte)s->bi_buf);
+    s.pending_buf[s.pending++] = s.bi_buf;
+  }
+  s.bi_buf = 0;
+  s.bi_valid = 0;
+}
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+function copy_block(s, buf, len, header)
+//DeflateState *s;
+//charf    *buf;    /* the input data */
+//unsigned len;     /* its length */
+//int      header;  /* true if block header must be written */
+{
+  bi_windup(s);        /* align on byte boundary */
+
+  if (header) {
+    put_short(s, len);
+    put_short(s, ~len);
+  }
+//  while (len--) {
+//    put_byte(s, *buf++);
+//  }
+  utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
+  s.pending += len;
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+function smaller(tree, n, m, depth) {
+  var _n2 = n * 2;
+  var _m2 = m * 2;
+  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
+         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
+}
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+function pqdownheap(s, tree, k)
+//    deflate_state *s;
+//    ct_data *tree;  /* the tree to restore */
+//    int k;               /* node to move down */
+{
+  var v = s.heap[k];
+  var j = k << 1;  /* left son of k */
+  while (j <= s.heap_len) {
+    /* Set j to the smallest of the two sons: */
+    if (j < s.heap_len &&
+      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
+      j++;
+    }
+    /* Exit if v is smaller than both sons */
+    if (smaller(tree, v, s.heap[j], s.depth)) { break; }
+
+    /* Exchange v with the smallest son */
+    s.heap[k] = s.heap[j];
+    k = j;
+
+    /* And continue down the tree, setting j to the left son of k */
+    j <<= 1;
+  }
+  s.heap[k] = v;
+}
+
+
+// inlined manually
+// var SMALLEST = 1;
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+function compress_block(s, ltree, dtree)
+//    deflate_state *s;
+//    const ct_data *ltree; /* literal tree */
+//    const ct_data *dtree; /* distance tree */
+{
+  var dist;           /* distance of matched string */
+  var lc;             /* match length or unmatched char (if dist == 0) */
+  var lx = 0;         /* running index in l_buf */
+  var code;           /* the code to send */
+  var extra;          /* number of extra bits to send */
+
+  if (s.last_lit !== 0) {
+    do {
+      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
+      lc = s.pending_buf[s.l_buf + lx];
+      lx++;
+
+      if (dist === 0) {
+        send_code(s, lc, ltree); /* send a literal byte */
+        //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+      } else {
+        /* Here, lc is the match length - MIN_MATCH */
+        code = _length_code[lc];
+        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
+        extra = extra_lbits[code];
+        if (extra !== 0) {
+          lc -= base_length[code];
+          send_bits(s, lc, extra);       /* send the extra length bits */
+        }
+        dist--; /* dist is now the match distance - 1 */
+        code = d_code(dist);
+        //Assert (code < D_CODES, "bad d_code");
+
+        send_code(s, code, dtree);       /* send the distance code */
+        extra = extra_dbits[code];
+        if (extra !== 0) {
+          dist -= base_dist[code];
+          send_bits(s, dist, extra);   /* send the extra distance bits */
+        }
+      } /* literal or match pair ? */
+
+      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+      //       "pendingBuf overflow");
+
+    } while (lx < s.last_lit);
+  }
+
+  send_code(s, END_BLOCK, ltree);
+}
+
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+function build_tree(s, desc)
+//    deflate_state *s;
+//    tree_desc *desc; /* the tree descriptor */
+{
+  var tree     = desc.dyn_tree;
+  var stree    = desc.stat_desc.static_tree;
+  var has_stree = desc.stat_desc.has_stree;
+  var elems    = desc.stat_desc.elems;
+  var n, m;          /* iterate over heap elements */
+  var max_code = -1; /* largest code with non zero frequency */
+  var node;          /* new node being created */
+
+  /* Construct the initial heap, with least frequent element in
+   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+   * heap[0] is not used.
+   */
+  s.heap_len = 0;
+  s.heap_max = HEAP_SIZE;
+
+  for (n = 0; n < elems; n++) {
+    if (tree[n * 2]/*.Freq*/ !== 0) {
+      s.heap[++s.heap_len] = max_code = n;
+      s.depth[n] = 0;
+
+    } else {
+      tree[n * 2 + 1]/*.Len*/ = 0;
+    }
+  }
+
+  /* The pkzip format requires that at least one distance code exists,
+   * and that at least one bit should be sent even if there is only one
+   * possible code. So to avoid special checks later on we force at least
+   * two codes of non zero frequency.
+   */
+  while (s.heap_len < 2) {
+    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
+    tree[node * 2]/*.Freq*/ = 1;
+    s.depth[node] = 0;
+    s.opt_len--;
+
+    if (has_stree) {
+      s.static_len -= stree[node * 2 + 1]/*.Len*/;
+    }
+    /* node is 0 or 1 so it does not have extra bits */
+  }
+  desc.max_code = max_code;
+
+  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+   * establish sub-heaps of increasing lengths:
+   */
+  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }
+
+  /* Construct the Huffman tree by repeatedly combining the least two
+   * frequent nodes.
+   */
+  node = elems;              /* next internal node of the tree */
+  do {
+    //pqremove(s, tree, n);  /* n = node of least frequency */
+    /*** pqremove ***/
+    n = s.heap[1/*SMALLEST*/];
+    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
+    pqdownheap(s, tree, 1/*SMALLEST*/);
+    /***/
+
+    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */
+
+    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
+    s.heap[--s.heap_max] = m;
+
+    /* Create a new node father of n and m */
+    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
+    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
+    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;
+
+    /* and insert the new node in the heap */
+    s.heap[1/*SMALLEST*/] = node++;
+    pqdownheap(s, tree, 1/*SMALLEST*/);
+
+  } while (s.heap_len >= 2);
+
+  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];
+
+  /* At this point, the fields freq and dad are set. We can now
+   * generate the bit lengths.
+   */
+  gen_bitlen(s, desc);
+
+  /* The field len is now set, we can generate the bit codes */
+  gen_codes(tree, max_code, s.bl_count);
+}
+
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+function scan_tree(s, tree, max_code)
+//    deflate_state *s;
+//    ct_data *tree;   /* the tree to be scanned */
+//    int max_code;    /* and its largest code of non zero frequency */
+{
+  var n;                     /* iterates over all tree elements */
+  var prevlen = -1;          /* last emitted length */
+  var curlen;                /* length of current code */
+
+  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+  var count = 0;             /* repeat count of the current code */
+  var max_count = 7;         /* max repeat count */
+  var min_count = 4;         /* min repeat count */
+
+  if (nextlen === 0) {
+    max_count = 138;
+    min_count = 3;
+  }
+  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */
+
+  for (n = 0; n <= max_code; n++) {
+    curlen = nextlen;
+    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+    if (++count < max_count && curlen === nextlen) {
+      continue;
+
+    } else if (count < min_count) {
+      s.bl_tree[curlen * 2]/*.Freq*/ += count;
+
+    } else if (curlen !== 0) {
+
+      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
+      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;
+
+    } else if (count <= 10) {
+      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;
+
+    } else {
+      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
+    }
+
+    count = 0;
+    prevlen = curlen;
+
+    if (nextlen === 0) {
+      max_count = 138;
+      min_count = 3;
+
+    } else if (curlen === nextlen) {
+      max_count = 6;
+      min_count = 3;
+
+    } else {
+      max_count = 7;
+      min_count = 4;
+    }
+  }
+}
+
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+function send_tree(s, tree, max_code)
+//    deflate_state *s;
+//    ct_data *tree; /* the tree to be scanned */
+//    int max_code;       /* and its largest code of non zero frequency */
+{
+  var n;                     /* iterates over all tree elements */
+  var prevlen = -1;          /* last emitted length */
+  var curlen;                /* length of current code */
+
+  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+  var count = 0;             /* repeat count of the current code */
+  var max_count = 7;         /* max repeat count */
+  var min_count = 4;         /* min repeat count */
+
+  /* tree[max_code+1].Len = -1; */  /* guard already set */
+  if (nextlen === 0) {
+    max_count = 138;
+    min_count = 3;
+  }
+
+  for (n = 0; n <= max_code; n++) {
+    curlen = nextlen;
+    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+    if (++count < max_count && curlen === nextlen) {
+      continue;
+
+    } else if (count < min_count) {
+      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);
+
+    } else if (curlen !== 0) {
+      if (curlen !== prevlen) {
+        send_code(s, curlen, s.bl_tree);
+        count--;
+      }
+      //Assert(count >= 3 && count <= 6, " 3_6?");
+      send_code(s, REP_3_6, s.bl_tree);
+      send_bits(s, count - 3, 2);
+
+    } else if (count <= 10) {
+      send_code(s, REPZ_3_10, s.bl_tree);
+      send_bits(s, count - 3, 3);
+
+    } else {
+      send_code(s, REPZ_11_138, s.bl_tree);
+      send_bits(s, count - 11, 7);
+    }
+
+    count = 0;
+    prevlen = curlen;
+    if (nextlen === 0) {
+      max_count = 138;
+      min_count = 3;
+
+    } else if (curlen === nextlen) {
+      max_count = 6;
+      min_count = 3;
+
+    } else {
+      max_count = 7;
+      min_count = 4;
+    }
+  }
+}
+
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+function build_bl_tree(s) {
+  var max_blindex;  /* index of last bit length code of non zero freq */
+
+  /* Determine the bit length frequencies for literal and distance trees */
+  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
+  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
+
+  /* Build the bit length tree: */
+  build_tree(s, s.bl_desc);
+  /* opt_len now includes the length of the tree representations, except
+   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+   */
+
+  /* Determine the number of bit length codes to send. The pkzip format
+   * requires that at least 4 bit length codes be sent. (appnote.txt says
+   * 3 but the actual value used is 4.)
+   */
+  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
+      break;
+    }
+  }
+  /* Update opt_len to include the bit length tree and counts */
+  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+  //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+  //        s->opt_len, s->static_len));
+
+  return max_blindex;
+}
+
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+function send_all_trees(s, lcodes, dcodes, blcodes)
+//    deflate_state *s;
+//    int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+  var rank;                    /* index in bl_order */
+
+  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+  //        "too many codes");
+  //Tracev((stderr, "\nbl counts: "));
+  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
+  send_bits(s, dcodes - 1,   5);
+  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */
+  for (rank = 0; rank < blcodes; rank++) {
+    //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
+  }
+  //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
+  //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
+  //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ *    a) There are no non-portable control characters belonging to the
+ *       "black list" (0..6, 14..25, 28..31).
+ *    b) There is at least one printable character belonging to the
+ *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ *   "gray list" that is ignored in this detection algorithm:
+ *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+function detect_data_type(s) {
+  /* black_mask is the bit mask of black-listed bytes
+   * set bits 0..6, 14..25, and 28..31
+   * 0xf3ffc07f = binary 11110011111111111100000001111111
+   */
+  var black_mask = 0xf3ffc07f;
+  var n;
+
+  /* Check for non-textual ("black-listed") bytes. */
+  for (n = 0; n <= 31; n++, black_mask >>>= 1) {
+    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
+      return Z_BINARY;
+    }
+  }
+
+  /* Check for textual ("white-listed") bytes. */
+  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
+      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
+    return Z_TEXT;
+  }
+  for (n = 32; n < LITERALS; n++) {
+    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
+      return Z_TEXT;
+    }
+  }
+
+  /* There are no "black-listed" or "white-listed" bytes:
+   * this stream either is empty or has tolerated ("gray-listed") bytes only.
+   */
+  return Z_BINARY;
+}
+
+
+var static_init_done = false;
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+function _tr_init(s)
+{
+
+  if (!static_init_done) {
+    tr_static_init();
+    static_init_done = true;
+  }
+
+  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);
+  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);
+  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
+
+  s.bi_buf = 0;
+  s.bi_valid = 0;
+
+  /* Initialize the first block of the first file: */
+  init_block(s);
+}
+
+
+/* ===========================================================================
+ * Send a stored block
+ */
+function _tr_stored_block(s, buf, stored_len, last)
+//DeflateState *s;
+//charf *buf;       /* input block */
+//ulg stored_len;   /* length of input block */
+//int last;         /* one if this is the last block for a file */
+{
+  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */
+  copy_block(s, buf, stored_len, true); /* with header */
+}
+
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+function _tr_align(s) {
+  send_bits(s, STATIC_TREES << 1, 3);
+  send_code(s, END_BLOCK, static_ltree);
+  bi_flush(s);
+}
+
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+function _tr_flush_block(s, buf, stored_len, last)
+//DeflateState *s;
+//charf *buf;       /* input block, or NULL if too old */
+//ulg stored_len;   /* length of input block */
+//int last;         /* one if this is the last block for a file */
+{
+  var opt_lenb, static_lenb;  /* opt_len and static_len in bytes */
+  var max_blindex = 0;        /* index of last bit length code of non zero freq */
+
+  /* Build the Huffman trees unless a stored block is forced */
+  if (s.level > 0) {
+
+    /* Check if the file is binary or text */
+    if (s.strm.data_type === Z_UNKNOWN) {
+      s.strm.data_type = detect_data_type(s);
+    }
+
+    /* Construct the literal and distance trees */
+    build_tree(s, s.l_desc);
+    // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+    //        s->static_len));
+
+    build_tree(s, s.d_desc);
+    // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+    //        s->static_len));
+    /* At this point, opt_len and static_len are the total bit lengths of
+     * the compressed block data, excluding the tree representations.
+     */
+
+    /* Build the bit length tree for the above two trees, and get the index
+     * in bl_order of the last bit length code to send.
+     */
+    max_blindex = build_bl_tree(s);
+
+    /* Determine the best encoding. Compute the block lengths in bytes. */
+    opt_lenb = (s.opt_len + 3 + 7) >>> 3;
+    static_lenb = (s.static_len + 3 + 7) >>> 3;
+
+    // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+    //        s->last_lit));
+
+    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }
+
+  } else {
+    // Assert(buf != (char*)0, "lost buf");
+    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+  }
+
+  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
+    /* 4: two words for the lengths */
+
+    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+     * Otherwise we can't have processed more than WSIZE input bytes since
+     * the last block flush, because compression would have been
+     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+     * transform a block into a stored block.
+     */
+    _tr_stored_block(s, buf, stored_len, last);
+
+  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
+
+    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
+    compress_block(s, static_ltree, static_dtree);
+
+  } else {
+    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
+    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
+    compress_block(s, s.dyn_ltree, s.dyn_dtree);
+  }
+  // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+  /* The above check is made mod 2^32, for files larger than 512 MB
+   * and uLong implemented on 32 bits.
+   */
+  init_block(s);
+
+  if (last) {
+    bi_windup(s);
+  }
+  // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+  //       s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+function _tr_tally(s, dist, lc)
+//    deflate_state *s;
+//    unsigned dist;  /* distance of matched string */
+//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+  //var out_length, in_length, dcode;
+
+  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;
+  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;
+
+  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
+  s.last_lit++;
+
+  if (dist === 0) {
+    /* lc is the unmatched char */
+    s.dyn_ltree[lc * 2]/*.Freq*/++;
+  } else {
+    s.matches++;
+    /* Here, lc is the match length - MIN_MATCH */
+    dist--;             /* dist = match distance - 1 */
+    //Assert((ush)dist < (ush)MAX_DIST(s) &&
+    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+    //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");
+
+    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
+    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
+  }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+
+//#ifdef TRUNCATE_BLOCK
+//  /* Try to guess if it is profitable to stop the current block here */
+//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
+//    /* Compute an upper bound for the compressed length */
+//    out_length = s.last_lit*8;
+//    in_length = s.strstart - s.block_start;
+//
+//    for (dcode = 0; dcode < D_CODES; dcode++) {
+//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
+//    }
+//    out_length >>>= 3;
+//    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+//    //       s->last_lit, in_length, out_length,
+//    //       100L - out_length*100L/in_length));
+//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
+//      return true;
+//    }
+//  }
+//#endif
+
+  return (s.last_lit === s.lit_bufsize - 1);
+  /* We avoid equality with lit_bufsize because of wraparound at 64K
+   * on 16 bit machines and because stored blocks are restricted to
+   * 64K-1 bytes.
+   */
+}
+
+export { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align };

+ 24 - 0
static/novnc/vendor/pako/lib/zlib/zstream.js

@@ -0,0 +1,24 @@
+export default function ZStream() {
+  /* next input byte */
+  this.input = null; // JS specific, because we have no pointers
+  this.next_in = 0;
+  /* number of bytes available at input */
+  this.avail_in = 0;
+  /* total number of input bytes read so far */
+  this.total_in = 0;
+  /* next output byte should be put there */
+  this.output = null; // JS specific, because we have no pointers
+  this.next_out = 0;
+  /* remaining free space at output */
+  this.avail_out = 0;
+  /* total number of bytes output so far */
+  this.total_out = 0;
+  /* last error message, NULL if no error */
+  this.msg = ''/*Z_NULL*/;
+  /* not visible by applications */
+  this.state = null;
+  /* best guess about the data type: binary or text */
+  this.data_type = 2/*Z_UNKNOWN*/;
+  /* adler32 value of the uncompressed data */
+  this.adler = 0;
+}

+ 255 - 0
static/novnc/vendor/promise.js

@@ -0,0 +1,255 @@
+/* Copyright (c) 2014 Taylor Hakes
+ * Copyright (c) 2014 Forbes Lindesay
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+(function (root) {
+
+  // Store setTimeout reference so promise-polyfill will be unaffected by
+  // other code modifying setTimeout (like sinon.useFakeTimers())
+  var setTimeoutFunc = setTimeout;
+
+  function noop() {}
+  
+  // Polyfill for Function.prototype.bind
+  function bind(fn, thisArg) {
+    return function () {
+      fn.apply(thisArg, arguments);
+    };
+  }
+
+  function Promise(fn) {
+    if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
+    if (typeof fn !== 'function') throw new TypeError('not a function');
+    this._state = 0;
+    this._handled = false;
+    this._value = undefined;
+    this._deferreds = [];
+
+    doResolve(fn, this);
+  }
+
+  function handle(self, deferred) {
+    while (self._state === 3) {
+      self = self._value;
+    }
+    if (self._state === 0) {
+      self._deferreds.push(deferred);
+      return;
+    }
+    self._handled = true;
+    Promise._immediateFn(function () {
+      var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
+      if (cb === null) {
+        (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
+        return;
+      }
+      var ret;
+      try {
+        ret = cb(self._value);
+      } catch (e) {
+        reject(deferred.promise, e);
+        return;
+      }
+      resolve(deferred.promise, ret);
+    });
+  }
+
+  function resolve(self, newValue) {
+    try {
+      // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+      if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.');
+      if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
+        var then = newValue.then;
+        if (newValue instanceof Promise) {
+          self._state = 3;
+          self._value = newValue;
+          finale(self);
+          return;
+        } else if (typeof then === 'function') {
+          doResolve(bind(then, newValue), self);
+          return;
+        }
+      }
+      self._state = 1;
+      self._value = newValue;
+      finale(self);
+    } catch (e) {
+      reject(self, e);
+    }
+  }
+
+  function reject(self, newValue) {
+    self._state = 2;
+    self._value = newValue;
+    finale(self);
+  }
+
+  function finale(self) {
+    if (self._state === 2 && self._deferreds.length === 0) {
+      Promise._immediateFn(function() {
+        if (!self._handled) {
+          Promise._unhandledRejectionFn(self._value);
+        }
+      });
+    }
+
+    for (var i = 0, len = self._deferreds.length; i < len; i++) {
+      handle(self, self._deferreds[i]);
+    }
+    self._deferreds = null;
+  }
+
+  function Handler(onFulfilled, onRejected, promise) {
+    this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+    this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+    this.promise = promise;
+  }
+
+  /**
+   * Take a potentially misbehaving resolver function and make sure
+   * onFulfilled and onRejected are only called once.
+   *
+   * Makes no guarantees about asynchrony.
+   */
+  function doResolve(fn, self) {
+    var done = false;
+    try {
+      fn(function (value) {
+        if (done) return;
+        done = true;
+        resolve(self, value);
+      }, function (reason) {
+        if (done) return;
+        done = true;
+        reject(self, reason);
+      });
+    } catch (ex) {
+      if (done) return;
+      done = true;
+      reject(self, ex);
+    }
+  }
+
+  Promise.prototype['catch'] = function (onRejected) {
+    return this.then(null, onRejected);
+  };
+
+  Promise.prototype.then = function (onFulfilled, onRejected) {
+    var prom = new (this.constructor)(noop);
+
+    handle(this, new Handler(onFulfilled, onRejected, prom));
+    return prom;
+  };
+
+  Promise.all = function (arr) {
+    var args = Array.prototype.slice.call(arr);
+
+    return new Promise(function (resolve, reject) {
+      if (args.length === 0) return resolve([]);
+      var remaining = args.length;
+
+      function res(i, val) {
+        try {
+          if (val && (typeof val === 'object' || typeof val === 'function')) {
+            var then = val.then;
+            if (typeof then === 'function') {
+              then.call(val, function (val) {
+                res(i, val);
+              }, reject);
+              return;
+            }
+          }
+          args[i] = val;
+          if (--remaining === 0) {
+            resolve(args);
+          }
+        } catch (ex) {
+          reject(ex);
+        }
+      }
+
+      for (var i = 0; i < args.length; i++) {
+        res(i, args[i]);
+      }
+    });
+  };
+
+  Promise.resolve = function (value) {
+    if (value && typeof value === 'object' && value.constructor === Promise) {
+      return value;
+    }
+
+    return new Promise(function (resolve) {
+      resolve(value);
+    });
+  };
+
+  Promise.reject = function (value) {
+    return new Promise(function (resolve, reject) {
+      reject(value);
+    });
+  };
+
+  Promise.race = function (values) {
+    return new Promise(function (resolve, reject) {
+      for (var i = 0, len = values.length; i < len; i++) {
+        values[i].then(resolve, reject);
+      }
+    });
+  };
+
+  // Use polyfill for setImmediate for performance gains
+  Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) ||
+    function (fn) {
+      setTimeoutFunc(fn, 0);
+    };
+
+  Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
+    if (typeof console !== 'undefined' && console) {
+      console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
+    }
+  };
+
+  /**
+   * Set the immediate function to execute callbacks
+   * @param fn {function} Function to execute
+   * @deprecated
+   */
+  Promise._setImmediateFn = function _setImmediateFn(fn) {
+    Promise._immediateFn = fn;
+  };
+
+  /**
+   * Change the function to execute on unhandled rejection
+   * @param {function} fn Function to execute on unhandled rejection
+   * @deprecated
+   */
+  Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) {
+    Promise._unhandledRejectionFn = fn;
+  };
+  
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = Promise;
+  } else if (!root.Promise) {
+    root.Promise = Promise;
+  }
+
+})(this);

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно