You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2020/09/22 12:12:48 UTC

[cloudstack] branch master updated: systemvm: update novnc v1.2.0 (#4323)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/master by this push:
     new c06e7de  systemvm: update novnc v1.2.0 (#4323)
c06e7de is described below

commit c06e7ded3c52b18eb92ec0dd53f0884db0b91004
Author: davidjumani <dj...@gmail.com>
AuthorDate: Tue Sep 22 17:42:30 2020 +0530

    systemvm: update novnc v1.2.0 (#4323)
    
    Update noVNC v1.2.0, add support for clipboard, explicit button toolbar and resize screensize
---
 .../com/cloud/servlet/ConsoleProxyServlet.java     |    2 +-
 systemvm/agent/noVNC/.eslintignore                 |    1 -
 systemvm/agent/noVNC/.eslintrc                     |   48 -
 .../noVNC/.github/ISSUE_TEMPLATE/bug_report.md     |   34 -
 .../.github/ISSUE_TEMPLATE/feature_request.md      |   17 -
 systemvm/agent/noVNC/.gitignore                    |   12 -
 systemvm/agent/noVNC/.gitmodules                   |    0
 systemvm/agent/noVNC/.travis.yml                   |   58 -
 systemvm/agent/noVNC/AUTHORS                       |   13 -
 systemvm/agent/noVNC/LICENSE.txt                   |   68 -
 systemvm/agent/noVNC/README.md                     |  152 --
 systemvm/agent/noVNC/VERSION                       |    1 -
 systemvm/agent/noVNC/app/error-handler.js          |    8 +
 systemvm/agent/noVNC/app/images/alt.png            |  Bin 0 -> 335 bytes
 systemvm/agent/noVNC/app/images/alt.svg            |   92 -
 systemvm/agent/noVNC/app/images/clipboard.png      |  Bin 0 -> 220 bytes
 systemvm/agent/noVNC/app/images/clipboard.svg      |  106 -
 systemvm/agent/noVNC/app/images/connect.png        |  Bin 0 -> 424 bytes
 systemvm/agent/noVNC/app/images/connect.svg        |   96 -
 systemvm/agent/noVNC/app/images/ctrl.png           |  Bin 0 -> 399 bytes
 systemvm/agent/noVNC/app/images/ctrl.svg           |   96 -
 systemvm/agent/noVNC/app/images/ctrlaltdel.png     |  Bin 0 -> 191 bytes
 systemvm/agent/noVNC/app/images/ctrlaltdel.svg     |  100 -
 systemvm/agent/noVNC/app/images/disconnect.png     |  Bin 0 -> 442 bytes
 systemvm/agent/noVNC/app/images/disconnect.svg     |   94 -
 systemvm/agent/noVNC/app/images/drag.png           |  Bin 0 -> 336 bytes
 systemvm/agent/noVNC/app/images/drag.svg           |   76 -
 systemvm/agent/noVNC/app/images/error.png          |  Bin 0 -> 348 bytes
 systemvm/agent/noVNC/app/images/error.svg          |   81 -
 systemvm/agent/noVNC/app/images/esc.png            |  Bin 0 -> 365 bytes
 systemvm/agent/noVNC/app/images/esc.svg            |   92 -
 systemvm/agent/noVNC/app/images/expander.png       |  Bin 0 -> 167 bytes
 systemvm/agent/noVNC/app/images/expander.svg       |   69 -
 systemvm/agent/noVNC/app/images/fullscreen.png     |  Bin 0 -> 280 bytes
 systemvm/agent/noVNC/app/images/fullscreen.svg     |   93 -
 systemvm/agent/noVNC/app/images/handle.png         |  Bin 0 -> 126 bytes
 systemvm/agent/noVNC/app/images/handle.svg         |   82 -
 systemvm/agent/noVNC/app/images/handle_bg.png      |  Bin 0 -> 123 bytes
 systemvm/agent/noVNC/app/images/handle_bg.svg      |  172 --
 systemvm/agent/noVNC/app/images/info.png           |  Bin 0 -> 448 bytes
 systemvm/agent/noVNC/app/images/info.svg           |   81 -
 systemvm/agent/noVNC/app/images/keyboard.png       |  Bin 0 -> 308 bytes
 systemvm/agent/noVNC/app/images/keyboard.svg       |   88 -
 systemvm/agent/noVNC/app/images/mouse_left.svg     |   92 -
 systemvm/agent/noVNC/app/images/mouse_middle.svg   |   92 -
 systemvm/agent/noVNC/app/images/mouse_none.svg     |   92 -
 systemvm/agent/noVNC/app/images/mouse_right.svg    |   92 -
 systemvm/agent/noVNC/app/images/power.png          |  Bin 0 -> 421 bytes
 systemvm/agent/noVNC/app/images/power.svg          |   87 -
 systemvm/agent/noVNC/app/images/settings.png       |  Bin 0 -> 379 bytes
 systemvm/agent/noVNC/app/images/settings.svg       |   76 -
 systemvm/agent/noVNC/app/images/tab.png            |  Bin 0 -> 190 bytes
 systemvm/agent/noVNC/app/images/tab.svg            |   86 -
 .../agent/noVNC/app/images/toggleextrakeys.png     |  Bin 0 -> 353 bytes
 .../agent/noVNC/app/images/toggleextrakeys.svg     |   90 -
 systemvm/agent/noVNC/app/images/warning.png        |  Bin 0 -> 319 bytes
 systemvm/agent/noVNC/app/images/warning.svg        |   81 -
 systemvm/agent/noVNC/app/images/windows.png        |  Bin 0 -> 219 bytes
 systemvm/agent/noVNC/app/images/windows.svg        |   85 -
 systemvm/agent/noVNC/app/locale/README             |    1 +
 systemvm/agent/noVNC/app/locale/ja.json            |   73 +
 systemvm/agent/noVNC/app/locale/sv.json            |   15 +-
 systemvm/agent/noVNC/app/locale/zh_CN.json         |   34 +-
 systemvm/agent/noVNC/app/styles/base.css           |  104 +-
 systemvm/agent/noVNC/app/ui.js                     |  349 +--
 systemvm/agent/noVNC/app/webutil.js                |   43 +-
 systemvm/agent/noVNC/core/base64.js                |    8 +-
 systemvm/agent/noVNC/core/decoders/copyrect.js     |    9 +-
 systemvm/agent/noVNC/core/decoders/hextile.js      |   82 +-
 systemvm/agent/noVNC/core/decoders/raw.js          |   30 +-
 systemvm/agent/noVNC/core/decoders/rre.js          |    4 +-
 systemvm/agent/noVNC/core/decoders/tight.js        |   52 +-
 systemvm/agent/noVNC/core/decoders/tightpng.js     |    6 +-
 systemvm/agent/noVNC/core/deflator.js              |   85 +
 systemvm/agent/noVNC/core/display.js               |  296 +--
 systemvm/agent/noVNC/core/encodings.js             |    5 +-
 systemvm/agent/noVNC/core/inflator.js              |   42 +-
 systemvm/agent/noVNC/core/input/domkeytable.js     |   14 +-
 systemvm/agent/noVNC/core/input/gesturehandler.js  |  567 +++++
 systemvm/agent/noVNC/core/input/keyboard.js        |   40 +-
 systemvm/agent/noVNC/core/input/mouse.js           |  276 ---
 systemvm/agent/noVNC/core/input/uskeysym.js        |   57 +
 systemvm/agent/noVNC/core/input/util.js            |   50 +-
 systemvm/agent/noVNC/core/rfb.js                   | 1535 ++++++++++---
 systemvm/agent/noVNC/core/util/browser.js          |   42 +-
 systemvm/agent/noVNC/core/util/cursor.js           |  100 +-
 systemvm/agent/noVNC/core/util/element.js          |   32 +
 systemvm/agent/noVNC/core/util/events.js           |   91 +-
 systemvm/agent/noVNC/core/util/eventtarget.js      |    2 +-
 systemvm/agent/noVNC/core/util/int.js              |   15 +
 systemvm/agent/noVNC/core/util/logging.js          |   16 +-
 systemvm/agent/noVNC/core/util/polyfill.js         |    9 +-
 systemvm/agent/noVNC/core/util/strings.js          |   26 +-
 systemvm/agent/noVNC/core/websock.js               |   66 +-
 systemvm/agent/noVNC/docs/API-internal.md          |  122 -
 systemvm/agent/noVNC/docs/API.md                   |  375 ---
 systemvm/agent/noVNC/docs/EMBEDDING.md             |  119 -
 systemvm/agent/noVNC/docs/LIBRARY.md               |   35 -
 systemvm/agent/noVNC/docs/LICENSE.BSD-2-Clause     |   22 -
 systemvm/agent/noVNC/docs/LICENSE.BSD-3-Clause     |   24 -
 systemvm/agent/noVNC/docs/LICENSE.MPL-2.0          |  373 ---
 systemvm/agent/noVNC/docs/LICENSE.OFL-1.1          |   91 -
 systemvm/agent/noVNC/docs/flash_policy.txt         |    4 -
 systemvm/agent/noVNC/docs/links                    |   76 -
 systemvm/agent/noVNC/docs/notes                    |    5 -
 systemvm/agent/noVNC/docs/rfb_notes                |  147 --
 systemvm/agent/noVNC/docs/rfbproto-3.3.pdf         |  Bin 110778 -> 0 bytes
 systemvm/agent/noVNC/docs/rfbproto-3.7.pdf         |  Bin 165552 -> 0 bytes
 systemvm/agent/noVNC/docs/rfbproto-3.8.pdf         |  Bin 143840 -> 0 bytes
 systemvm/agent/noVNC/karma.conf.js                 |  134 --
 systemvm/agent/noVNC/package.json                  |   62 +-
 systemvm/agent/noVNC/po/Makefile                   |   35 -
 systemvm/agent/noVNC/po/cs.po                      |  294 ---
 systemvm/agent/noVNC/po/de.po                      |  303 ---
 systemvm/agent/noVNC/po/el.po                      |  323 ---
 systemvm/agent/noVNC/po/es.po                      |  283 ---
 systemvm/agent/noVNC/po/ko.po                      |  290 ---
 systemvm/agent/noVNC/po/nl.po                      |  322 ---
 systemvm/agent/noVNC/po/noVNC.pot                  |  302 ---
 systemvm/agent/noVNC/po/pl.po                      |  325 ---
 systemvm/agent/noVNC/po/po2js                      |   43 -
 systemvm/agent/noVNC/po/ru.po                      |  306 ---
 systemvm/agent/noVNC/po/sv.po                      |  316 ---
 systemvm/agent/noVNC/po/tr.po                      |  288 ---
 systemvm/agent/noVNC/po/xgettext-html              |  115 -
 systemvm/agent/noVNC/po/zh_CN.po                   |  284 ---
 systemvm/agent/noVNC/po/zh_TW.po                   |  285 ---
 systemvm/agent/noVNC/tests/.eslintrc               |   15 -
 systemvm/agent/noVNC/tests/assertions.js           |  101 -
 systemvm/agent/noVNC/tests/fake.websocket.js       |   96 -
 systemvm/agent/noVNC/tests/karma-test-main.js      |   48 -
 systemvm/agent/noVNC/tests/playback-ui.js          |  210 --
 systemvm/agent/noVNC/tests/playback.js             |  172 --
 systemvm/agent/noVNC/tests/test.base64.js          |   33 -
 systemvm/agent/noVNC/tests/test.display.js         |  486 ----
 systemvm/agent/noVNC/tests/test.helper.js          |  223 --
 systemvm/agent/noVNC/tests/test.keyboard.js        |  510 -----
 systemvm/agent/noVNC/tests/test.localization.js    |   72 -
 systemvm/agent/noVNC/tests/test.mouse.js           |  304 ---
 systemvm/agent/noVNC/tests/test.rfb.js             | 2389 --------------------
 systemvm/agent/noVNC/tests/test.util.js            |   69 -
 systemvm/agent/noVNC/tests/test.websock.js         |  441 ----
 systemvm/agent/noVNC/tests/test.webutil.js         |  184 --
 systemvm/agent/noVNC/tests/vnc_playback.html       |   43 -
 systemvm/agent/noVNC/utils/.eslintrc               |    8 -
 systemvm/agent/noVNC/utils/README.md               |   14 -
 systemvm/agent/noVNC/utils/b64-to-binary.pl        |   17 -
 systemvm/agent/noVNC/utils/genkeysymdef.js         |  127 --
 systemvm/agent/noVNC/utils/img2js.py               |   40 -
 systemvm/agent/noVNC/utils/json2graph.py           |  206 --
 systemvm/agent/noVNC/utils/launch.sh               |  169 --
 systemvm/agent/noVNC/utils/u2x11                   |   28 -
 systemvm/agent/noVNC/utils/use_require.js          |  313 ---
 systemvm/agent/noVNC/utils/use_require_helpers.js  |   76 -
 systemvm/agent/noVNC/utils/validate                |   45 -
 .../vendor/browser-es-module-loader/README.md      |    4 +-
 .../vendor/browser-es-module-loader/genworker.js   |   13 +
 .../browser-es-module-loader/rollup.config.js      |   15 +-
 .../browser-es-module-loader/src/babel-worker.js   |   18 +-
 .../src/browser-es-module-loader.js                |    1 -
 .../agent/noVNC/vendor/pako/lib/zlib/deflate.js    |   60 +-
 .../agent/noVNC/vendor/pako/lib/zlib/inflate.js    |   34 +-
 systemvm/agent/noVNC/vnc.html                      |  303 +--
 systemvm/agent/noVNC/vnc_lite.html                 |   19 +-
 164 files changed, 3227 insertions(+), 16263 deletions(-)

diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java
index ed73625..b735be8 100644
--- a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java
+++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java
@@ -484,7 +484,7 @@ public class ConsoleProxyServlet extends HttpServlet {
         if (param.getHypervHost() != null || !ConsoleProxyManager.NoVncConsoleDefault.value()) {
             sb.append("/ajax?token=" + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
         } else {
-            sb.append("/resource/noVNC/vnc_lite.html?port=" + ConsoleProxyManager.DEFAULT_NOVNC_PORT + "&token="
+            sb.append("/resource/noVNC/vnc.html?port=" + ConsoleProxyManager.DEFAULT_NOVNC_PORT + "&token="
                 + encryptor.encryptObject(ConsoleProxyClientParam.class, param));
         }
 
diff --git a/systemvm/agent/noVNC/.eslintignore b/systemvm/agent/noVNC/.eslintignore
deleted file mode 100644
index d381628..0000000
--- a/systemvm/agent/noVNC/.eslintignore
+++ /dev/null
@@ -1 +0,0 @@
-**/xtscancodes.js
diff --git a/systemvm/agent/noVNC/.eslintrc b/systemvm/agent/noVNC/.eslintrc
deleted file mode 100644
index 900a718..0000000
--- a/systemvm/agent/noVNC/.eslintrc
+++ /dev/null
@@ -1,48 +0,0 @@
-{
-    "env": {
-        "browser": true,
-        "es6": true
-    },
-    "parserOptions": {
-        "sourceType": "module"
-    },
-    "extends": "eslint:recommended",
-    "rules": {
-        // Unsafe or confusing stuff that we forbid
-
-        "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }],
-        "no-constant-condition": ["error", { "checkLoops": false }],
-        "no-var": "error",
-        "no-useless-constructor": "error",
-        "object-shorthand": ["error", "methods", { "avoidQuotes": true }],
-        "prefer-arrow-callback": "error",
-        "arrow-body-style": ["error", "as-needed", { "requireReturnForObjectLiteral": false } ],
-        "arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
-        "arrow-spacing": ["error"],
-        "no-confusing-arrow": ["error", { "allowParens": true }],
-
-        // Enforced coding style
-
-        "brace-style": ["error", "1tbs", { "allowSingleLine": true }],
-        "indent": ["error", 4, { "SwitchCase": 1,
-                                 "CallExpression": { "arguments": "first" },
-                                 "ArrayExpression": "first",
-                                 "ObjectExpression": "first",
-                                 "ignoreComments": true }],
-        "comma-spacing": ["error"],
-        "comma-style": ["error"],
-        "curly": ["error", "multi-line"],
-        "func-call-spacing": ["error"],
-        "func-names": ["error"],
-        "func-style": ["error", "declaration", { "allowArrowFunctions": true }],
-        "key-spacing": ["error"],
-        "keyword-spacing": ["error"],
-        "no-trailing-spaces": ["error"],
-        "semi": ["error"],
-        "space-before-blocks": ["error"],
-        "space-before-function-paren": ["error", { "anonymous": "always",
-                                                   "named": "never",
-                                                   "asyncArrow": "always" }],
-        "switch-colon-spacing": ["error"],
-    }
-}
diff --git a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index 94ac6f8..0000000
--- a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Client (please complete the following information):**
- - OS: [e.g. iOS]
- - Browser: [e.g. chrome, safari]
- - Browser version: [e.g. 22]
-
-**Server (please complete the following information):**
- - noVNC version: [e.g. 1.0.0 or git commit id]
- - VNC server: [e.g. QEMU, TigerVNC]
- - WebSocket proxy: [e.g. websockify]
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md b/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 066b2d9..0000000
--- a/systemvm/agent/noVNC/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/systemvm/agent/noVNC/.gitignore b/systemvm/agent/noVNC/.gitignore
deleted file mode 100644
index c178dba..0000000
--- a/systemvm/agent/noVNC/.gitignore
+++ /dev/null
@@ -1,12 +0,0 @@
-*.pyc
-*.o
-tests/data_*.js
-utils/rebind.so
-utils/websockify
-/node_modules
-/build
-/lib
-recordings
-*.swp
-*~
-noVNC-*.tgz
diff --git a/systemvm/agent/noVNC/.gitmodules b/systemvm/agent/noVNC/.gitmodules
deleted file mode 100644
index e69de29..0000000
diff --git a/systemvm/agent/noVNC/.travis.yml b/systemvm/agent/noVNC/.travis.yml
deleted file mode 100644
index 78b521a..0000000
--- a/systemvm/agent/noVNC/.travis.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-language: node_js
-sudo: false
-cache:
-  directories:
-  - node_modules
-node_js:
- - 6
-env:
-  matrix:
-  - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10'
-# FIXME Skip tests in Linux since Sauce Labs browser versions are ancient.
-#  - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Linux'
-  - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='OS X 10.11'
-  - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10'
-#  - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Linux'
-  - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='OS X 10.11'
-  - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10'
-  - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7'
-  - TEST_BROWSER_NAME=microsoftedge TEST_BROWSER_OS='Windows 10'
-  - TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.13'
-before_script: npm install -g karma-cli
-addons:
-  sauce_connect:
-    username: "directxman12"
-  jwt:
-    secure: "d3ekMYslpn6R4f0ajtRMt9SUFmNGDiItHpqaXC5T4KI0KMEsxgvEOfJot5PiFFJWg1DSpJZH6oaW2UxGZ3duJLZrXIEd/JePY8a6NtT35BNgiDPgcp+eu2Bu3rhrSNg7/HEsD1ma+JeUTnv18Ai5oMFfCCQJx2J6osIxyl/ZVxA="
-stages:
-- lint
-- test
-- name: deploy
-  if: tag is PRESENT
-jobs:
-  include:
-  - stage: lint
-    env:
-    addons:
-    before_script:
-    script: npm run lint
-  -
-    env:
-    addons:
-    before_script:
-    script: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate
-  - stage: deploy
-    env:
-    addons:
-    script: skip
-    before_script: skip
-    deploy:
-      provider: npm
-      email: ossman@cendio.se
-      api_key:
-        secure: "Qq2Mi9xQawO2zlAigzshzMu2QMHvu1IaN9l0ZIivE99wHJj7eS5f4miJ9wB+/mWRRgb3E8uj9ZRV24+Oc36drlBTU9sz+lHhH0uFMfAIseceK64wZV9sLAZm472fmPp2xdUeTCCqPaRy7g1XBqiJ0LyZvEFLsRijqcLjPBF+b8w="
-      on:
-        tags: true
-        repo: novnc/noVNC
-
-
diff --git a/systemvm/agent/noVNC/AUTHORS b/systemvm/agent/noVNC/AUTHORS
deleted file mode 100644
index dec0e89..0000000
--- a/systemvm/agent/noVNC/AUTHORS
+++ /dev/null
@@ -1,13 +0,0 @@
-maintainers:
-- Joel Martin (@kanaka)
-- Solly Ross (@directxman12)
-- Samuel Mannehed for Cendio AB (@samhed)
-- Pierre Ossman for Cendio AB (@CendioOssman)
-maintainersEmeritus:
-- @astrand 
-contributors:
-# There are a bunch of people that should be here.
-# If you want to be on this list, feel free send a PR
-# to add yourself.
-- jalf <gi...@jalf.dk>
-- NTT corp.
diff --git a/systemvm/agent/noVNC/LICENSE.txt b/systemvm/agent/noVNC/LICENSE.txt
deleted file mode 100644
index 20f3eb0..0000000
--- a/systemvm/agent/noVNC/LICENSE.txt
+++ /dev/null
@@ -1,68 +0,0 @@
-noVNC is Copyright (C) 2018 The noVNC Authors
-(./AUTHORS)
-
-The noVNC core library files are licensed under the MPL 2.0 (Mozilla
-Public License 2.0). The noVNC core library is composed of the
-Javascript code necessary for full noVNC operation. This includes (but
-is not limited to):
-
-    core/**/*.js
-    app/*.js
-    test/playback.js
-
-The HTML, CSS, font and images files that included with the noVNC
-source distibution (or repository) are not considered part of the
-noVNC core library and are licensed under more permissive licenses.
-The intent is to allow easy integration of noVNC into existing web
-sites and web applications.
-
-The HTML, CSS, font and image files are licensed as follows:
-
-    *.html                     : 2-Clause BSD license
-
-    app/styles/*.css           : 2-Clause BSD license
-
-    app/styles/Orbitron*       : SIL Open Font License 1.1
-                                 (Copyright 2009 Matt McInerney)
-
-    app/images/                : Creative Commons Attribution-ShareAlike
-                                 http://creativecommons.org/licenses/by-sa/3.0/
-
-Some portions of noVNC are copyright to their individual authors.
-Please refer to the individual source files and/or to the noVNC commit
-history: https://github.com/novnc/noVNC/commits/master
-
-The are several files and projects that have been incorporated into
-the noVNC core library. Here is a list of those files and the original
-licenses (all MPL 2.0 compatible):
-
-    core/base64.js          : MPL 2.0
-
-    core/des.js             : Various BSD style licenses
-
-    vendor/pako/            : MIT
-
-    vendor/browser-es-module-loader/src/ : MIT
-
-    vendor/browser-es-module-loader/dist/ : Various BSD style licenses
-
-    vendor/promise.js       : MIT
-
-Any other files not mentioned above are typically marked with
-a copyright/license header at the top of the file. The default noVNC
-license is MPL-2.0.
-
-The following license texts are included:
-
-    docs/LICENSE.MPL-2.0
-    docs/LICENSE.OFL-1.1
-    docs/LICENSE.BSD-3-Clause (New BSD)
-    docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
-    vendor/pako/LICENSE (MIT)
-
-Or alternatively the license texts may be found here:
-
-    http://www.mozilla.org/MPL/2.0/
-    http://scripts.sil.org/OFL
-    http://en.wikipedia.org/wiki/BSD_licenses
-    https://opensource.org/licenses/MIT
diff --git a/systemvm/agent/noVNC/README.md b/systemvm/agent/noVNC/README.md
deleted file mode 100644
index 566b8e4..0000000
--- a/systemvm/agent/noVNC/README.md
+++ /dev/null
@@ -1,152 +0,0 @@
-## noVNC: HTML VNC Client Library and Application
-
-[![Build Status](https://travis-ci.org/novnc/noVNC.svg?branch=master)](https://travis-ci.org/novnc/noVNC)
-
-### Description
-
-noVNC is both a HTML VNC client JavaScript library and an application built on
-top of that library. noVNC runs well in any modern browser including mobile
-browsers (iOS and Android).
-
-Many companies, projects and products have integrated noVNC including
-[OpenStack](http://www.openstack.org),
-[OpenNebula](http://opennebula.org/),
-[LibVNCServer](http://libvncserver.sourceforge.net), and
-[ThinLinc](https://cendio.com/thinlinc). See
-[the Projects and Companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC)
-for a more complete list with additional info and links.
-
-### Table of Contents
-
-- [News/help/contact](#newshelpcontact)
-- [Features](#features)
-- [Screenshots](#screenshots)
-- [Browser Requirements](#browser-requirements)
-- [Server Requirements](#server-requirements)
-- [Quick Start](#quick-start)
-- [Integration and Deployment](#integration-and-deployment)
-- [Authors/Contributors](#authorscontributors)
-
-### News/help/contact
-
-The project website is found at [novnc.com](http://novnc.com).
-Notable commits, announcements and news are posted to
-[@noVNC](http://www.twitter.com/noVNC).
-
-If you are a noVNC developer/integrator/user (or want to be) please join the
-[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
-
-Bugs and feature requests can be submitted via
-[github issues](https://github.com/novnc/noVNC/issues). If you have questions
-about using noVNC then please first use the
-[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
-We also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of
-helpful information.
-
-If you are looking for a place to start contributing to noVNC, a good place to
-start would be the issues that are marked as
-["patchwelcome"](https://github.com/novnc/noVNC/issues?labels=patchwelcome).
-Please check our
-[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though.
-
-If you want to show appreciation for noVNC you could donate to a great non-
-profits such as:
-[Compassion International](http://www.compassion.com/),
-[SIL](http://www.sil.org),
-[Habitat for Humanity](http://www.habitat.org),
-[Electronic Frontier Foundation](https://www.eff.org/),
-[Against Malaria Foundation](http://www.againstmalaria.com/),
-[Nothing But Nets](http://www.nothingbutnets.net/), etc.
-Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do.
-
-
-### Features
-
-* Supports all modern browsers including mobile (iOS, Android)
-* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
-* Supports scaling, clipping and resizing the desktop
-* Local cursor rendering
-* Clipboard copy/paste
-* Translations
-* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
-  [the license document](LICENSE.txt) for details
-
-### Screenshots
-
-Running in Firefox before and after connecting:
-
-<img src="http://novnc.com/img/noVNC-1-login.png" width=400>&nbsp;
-<img src="http://novnc.com/img/noVNC-3-connected.png" width=400>
-
-See more screenshots
-[here](http://novnc.com/screenshots.html).
-
-
-### Browser Requirements
-
-noVNC uses many modern web technologies so a formal requirement list is
-not available. However these are the minimum versions we are currently
-aware of:
-
-* Chrome 49, Firefox 44, Safari 10, Opera 36, IE 11, Edge 12
-
-
-### Server Requirements
-
-noVNC follows the standard VNC protocol, but unlike other VNC clients it does
-require WebSockets support. Many servers include support (e.g.
-[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
-[QEMU](http://www.qemu.org/), and
-[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to
-use a WebSockets to TCP socket proxy. noVNC has a sister project
-[websockify](https://github.com/novnc/websockify) that provides a simple such
-proxy.
-
-
-### Quick Start
-
-* Use the launch script to automatically download and start websockify, which
-  includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
-  used to specify the location of a running VNC server:
-
-    `./utils/launch.sh --vnc localhost:5901`
-
-* Point your browser to the cut-and-paste URL that is output by the launch
-  script. Hit the Connect button, enter a password if the VNC server has one
-  configured, and enjoy!
-
-
-### Integration and Deployment
-
-Please see our other documents for how to integrate noVNC in your own software,
-or deploying the noVNC application in production environments:
-
-* [Embedding](docs/EMBEDDING.md) - For the noVNC application
-* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library
-
-
-### Authors/Contributors
-
-See [AUTHORS](AUTHORS) for a (full-ish) list of authors.  If you're not on
-that list and you think you should be, feel free to send a PR to fix that.
-
-* Core team:
-    * [Joel Martin](https://github.com/kanaka)
-    * [Samuel Mannehed](https://github.com/samhed) (Cendio)
-    * [Peter Åstrand](https://github.com/astrand) (Cendio)
-    * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
-    * [Pierre Ossman](https://github.com/CendioOssman) (Cendio)
-
-* Notable contributions:
-    * UI and Icons : Pierre Ossman, Chris Gordon
-    * Original Logo : Michael Sersen
-    * tight encoding : Michael Tinglof (Mercuri.ca)
-
-* Included libraries:
-    * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
-    * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
-    * Pako : Vitaly Puzrin (https://github.com/nodeca/pako)
-
-Do you want to be on this list? Check out our
-[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and
-start hacking!
diff --git a/systemvm/agent/noVNC/VERSION b/systemvm/agent/noVNC/VERSION
deleted file mode 100644
index 9084fa2..0000000
--- a/systemvm/agent/noVNC/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-1.1.0
diff --git a/systemvm/agent/noVNC/app/error-handler.js b/systemvm/agent/noVNC/app/error-handler.js
index 8e29416..81a6cba 100644
--- a/systemvm/agent/noVNC/app/error-handler.js
+++ b/systemvm/agent/noVNC/app/error-handler.js
@@ -1,3 +1,11 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2019 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
 // 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.
diff --git a/systemvm/agent/noVNC/app/images/alt.png b/systemvm/agent/noVNC/app/images/alt.png
new file mode 100644
index 0000000..2d5e35e
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/alt.png differ
diff --git a/systemvm/agent/noVNC/app/images/alt.svg b/systemvm/agent/noVNC/app/images/alt.svg
deleted file mode 100644
index e5bb461..0000000
--- a/systemvm/agent/noVNC/app/images/alt.svg
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/clipboard.png b/systemvm/agent/noVNC/app/images/clipboard.png
new file mode 100644
index 0000000..d7fe507
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/clipboard.png differ
diff --git a/systemvm/agent/noVNC/app/images/clipboard.svg b/systemvm/agent/noVNC/app/images/clipboard.svg
deleted file mode 100644
index 79af275..0000000
--- a/systemvm/agent/noVNC/app/images/clipboard.svg
+++ /dev/null
@@ -1,106 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/connect.png b/systemvm/agent/noVNC/app/images/connect.png
new file mode 100644
index 0000000..abdbe42
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/connect.png differ
diff --git a/systemvm/agent/noVNC/app/images/connect.svg b/systemvm/agent/noVNC/app/images/connect.svg
deleted file mode 100644
index 56cde41..0000000
--- a/systemvm/agent/noVNC/app/images/connect.svg
+++ /dev/null
@@ -1,96 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/ctrl.png b/systemvm/agent/noVNC/app/images/ctrl.png
new file mode 100644
index 0000000..fbc9e13
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/ctrl.png differ
diff --git a/systemvm/agent/noVNC/app/images/ctrl.svg b/systemvm/agent/noVNC/app/images/ctrl.svg
deleted file mode 100644
index 856e939..0000000
--- a/systemvm/agent/noVNC/app/images/ctrl.svg
+++ /dev/null
@@ -1,96 +0,0 @@
-<?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 [...]
-         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>
diff --git a/systemvm/agent/noVNC/app/images/ctrlaltdel.png b/systemvm/agent/noVNC/app/images/ctrlaltdel.png
new file mode 100644
index 0000000..dd04978
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/ctrlaltdel.png differ
diff --git a/systemvm/agent/noVNC/app/images/ctrlaltdel.svg b/systemvm/agent/noVNC/app/images/ctrlaltdel.svg
deleted file mode 100644
index d7744ea..0000000
--- a/systemvm/agent/noVNC/app/images/ctrlaltdel.svg
+++ /dev/null
@@ -1,100 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/disconnect.png b/systemvm/agent/noVNC/app/images/disconnect.png
new file mode 100644
index 0000000..97eb1a9
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/disconnect.png differ
diff --git a/systemvm/agent/noVNC/app/images/disconnect.svg b/systemvm/agent/noVNC/app/images/disconnect.svg
deleted file mode 100644
index 6be7d18..0000000
--- a/systemvm/agent/noVNC/app/images/disconnect.svg
+++ /dev/null
@@ -1,94 +0,0 @@
-<?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.00 [...]
-         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:non [...]
-         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>
diff --git a/systemvm/agent/noVNC/app/images/drag.png b/systemvm/agent/noVNC/app/images/drag.png
new file mode 100644
index 0000000..f006202
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/drag.png differ
diff --git a/systemvm/agent/noVNC/app/images/drag.svg b/systemvm/agent/noVNC/app/images/drag.svg
deleted file mode 100644
index 139caf9..0000000
--- a/systemvm/agent/noVNC/app/images/drag.svg
+++ /dev/null
@@ -1,76 +0,0 @@
-<?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="drag.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="9.8789407"
-     inkscape:cy="9.5008608"
-     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 7.039733,1049.3037 c -0.4309106,-0.1233 -0.7932634,-0.4631 -0.9705434,-0.9103 -0.04922,-0.1241 -0.057118,-0.2988 -0.071321,-1.5771 l -0.015972,-1.4375 -0.328125,-0.082 c -0.7668138,-0.1927 -1.1897046,-0.4275 -1.7031253,-0.9457 -0.4586773,-0.4629 -0.6804297,-0.8433 -0.867034,-1.4875 -0.067215,-0.232 -0.068001,-0.2642 -0.078682,-3.2188 -0.012078,-3.341 -0.020337,-3.2012 0.2099452,-3.5555 0.2246623,-0.3458 0.5798271,-0.5892 0.9667343,-0.6626 0.092506,-0.017 0.531898,-0.032 0.976 [...]
-       id="path4379"
-       inkscape:connector-curvature="0" />
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/error.png b/systemvm/agent/noVNC/app/images/error.png
new file mode 100644
index 0000000..04e78e1
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/error.png differ
diff --git a/systemvm/agent/noVNC/app/images/error.svg b/systemvm/agent/noVNC/app/images/error.svg
deleted file mode 100644
index 8356d3f..0000000
--- a/systemvm/agent/noVNC/app/images/error.svg
+++ /dev/null
@@ -1,81 +0,0 @@
-<?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. [...]
-       transform="translate(0,1027.3622)"
-       id="rect4135" />
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/esc.png b/systemvm/agent/noVNC/app/images/esc.png
new file mode 100644
index 0000000..e1b1bfa
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/esc.png differ
diff --git a/systemvm/agent/noVNC/app/images/esc.svg b/systemvm/agent/noVNC/app/images/esc.svg
deleted file mode 100644
index 830152b..0000000
--- a/systemvm/agent/noVNC/app/images/esc.svg
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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, [...]
-         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.314 [...]
-         style="font-size:10px;fill:#ffffff;fill-opacity:1"
-         id="path5318" />
-    </g>
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/expander.png b/systemvm/agent/noVNC/app/images/expander.png
new file mode 100644
index 0000000..2793721
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/expander.png differ
diff --git a/systemvm/agent/noVNC/app/images/expander.svg b/systemvm/agent/noVNC/app/images/expander.svg
deleted file mode 100644
index e163535..0000000
--- a/systemvm/agent/noVNC/app/images/expander.svg
+++ /dev/null
@@ -1,69 +0,0 @@
-<?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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/fullscreen.png b/systemvm/agent/noVNC/app/images/fullscreen.png
new file mode 100644
index 0000000..a7f2634
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/fullscreen.png differ
diff --git a/systemvm/agent/noVNC/app/images/fullscreen.svg b/systemvm/agent/noVNC/app/images/fullscreen.svg
deleted file mode 100644
index 29bd05d..0000000
--- a/systemvm/agent/noVNC/app/images/fullscreen.svg
+++ /dev/null
@@ -1,93 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/handle.png b/systemvm/agent/noVNC/app/images/handle.png
new file mode 100644
index 0000000..cf0e5d5
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/handle.png differ
diff --git a/systemvm/agent/noVNC/app/images/handle.svg b/systemvm/agent/noVNC/app/images/handle.svg
deleted file mode 100644
index 4a7a126..0000000
--- a/systemvm/agent/noVNC/app/images/handle.svg
+++ /dev/null
@@ -1,82 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/handle_bg.png b/systemvm/agent/noVNC/app/images/handle_bg.png
new file mode 100644
index 0000000..efb0357
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/handle_bg.png differ
diff --git a/systemvm/agent/noVNC/app/images/handle_bg.svg b/systemvm/agent/noVNC/app/images/handle_bg.svg
deleted file mode 100644
index 7579c42..0000000
--- a/systemvm/agent/noVNC/app/images/handle_bg.svg
+++ /dev/null
@@ -1,172 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/info.png b/systemvm/agent/noVNC/app/images/info.png
new file mode 100644
index 0000000..21e3589
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/info.png differ
diff --git a/systemvm/agent/noVNC/app/images/info.svg b/systemvm/agent/noVNC/app/images/info.svg
deleted file mode 100644
index 557b772..0000000
--- a/systemvm/agent/noVNC/app/images/info.svg
+++ /dev/null
@@ -1,81 +0,0 @@
-<?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 [...]
-       transform="translate(0,1027.3622)"
-       id="path4136" />
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/keyboard.png b/systemvm/agent/noVNC/app/images/keyboard.png
new file mode 100644
index 0000000..fe8056b
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/keyboard.png differ
diff --git a/systemvm/agent/noVNC/app/images/keyboard.svg b/systemvm/agent/noVNC/app/images/keyboard.svg
deleted file mode 100644
index 137b350..0000000
--- a/systemvm/agent/noVNC/app/images/keyboard.svg
+++ /dev/null
@@ -1,88 +0,0 @@
-<?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="keyboard.svg"
-   inkscape:export-filename="/home/ossman/devel/noVNC/images/keyboard.png"
-   inkscape:export-xdpi="90"
-   inkscape:export-ydpi="90">
-  <defs
-     id="defs4" />
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#717171"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageopacity="0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="1"
-     inkscape:cx="31.285341"
-     inkscape:cy="8.8028469"
-     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:snap-bbox-midpoints="false"
-     inkscape:window-width="1920"
-     inkscape:window-height="1136"
-     inkscape:window-x="1920"
-     inkscape:window-y="27"
-     inkscape:window-maximized="1"
-     inkscape:object-paths="true"
-     inkscape:snap-intersection-paths="true"
-     inkscape:object-nodes="true"
-     inkscape:snap-midpoints="true"
-     inkscape:snap-smooth-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 />
-      </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:nonze [...]
-       d="M 7,3 C 4.8012876,3 3,4.8013 3,7 3,11.166667 3,15.333333 3,19.5 3,20.8764 4.1236413,22 5.5,22 l 14,0 C 20.876358,22 22,20.8764 22,19.5 22,15.333333 22,11.166667 22,7 22,4.8013 20.198712,3 18,3 Z m 0,2 11,0 c 1.125307,0 2,0.8747 2,2 L 20,12 5,12 5,7 C 5,5.8747 5.8746931,5 7,5 Z M 6.5,14 C 6.777,14 7,14.223 7,14.5 7,14.777 6.777,15 6.5,15 6.223,15 6,14.777 6,14.5 6,14.223 6.223,14 6.5,14 Z m 2,0 C 8.777,14 9,14.223 9,14.5 9,14.777 8.777,15 8.5,15 8.223,15 8,14.777 8,14.5 8,14.223 [...]
-       transform="translate(0,1027.3622)"
-       id="rect4160"
-       inkscape:connector-curvature="0"
-       sodipodi:nodetypes="sccssccsssssccssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" />
-    <path
-       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
-       d="m 12.499929,1033.8622 -2,2 1.500071,0 0,2 1,0 0,-2 1.499929,0 z"
-       id="path4150"
-       inkscape:connector-curvature="0"
-       sodipodi:nodetypes="cccccccc" />
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/mouse_left.svg b/systemvm/agent/noVNC/app/images/mouse_left.svg
deleted file mode 100644
index ce4cca4..0000000
--- a/systemvm/agent/noVNC/app/images/mouse_left.svg
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/mouse_middle.svg b/systemvm/agent/noVNC/app/images/mouse_middle.svg
deleted file mode 100644
index 6603425..0000000
--- a/systemvm/agent/noVNC/app/images/mouse_middle.svg
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/mouse_none.svg b/systemvm/agent/noVNC/app/images/mouse_none.svg
deleted file mode 100644
index 3e0f838..0000000
--- a/systemvm/agent/noVNC/app/images/mouse_none.svg
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/mouse_right.svg b/systemvm/agent/noVNC/app/images/mouse_right.svg
deleted file mode 100644
index f4bad76..0000000
--- a/systemvm/agent/noVNC/app/images/mouse_right.svg
+++ /dev/null
@@ -1,92 +0,0 @@
-<?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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/power.png b/systemvm/agent/noVNC/app/images/power.png
new file mode 100644
index 0000000..7f87135
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/power.png differ
diff --git a/systemvm/agent/noVNC/app/images/power.svg b/systemvm/agent/noVNC/app/images/power.svg
deleted file mode 100644
index 4925d3e..0000000
--- a/systemvm/agent/noVNC/app/images/power.svg
+++ /dev/null
@@ -1,87 +0,0 @@
-<?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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/settings.png b/systemvm/agent/noVNC/app/images/settings.png
new file mode 100644
index 0000000..8d0c0d4
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/settings.png differ
diff --git a/systemvm/agent/noVNC/app/images/settings.svg b/systemvm/agent/noVNC/app/images/settings.svg
deleted file mode 100644
index dbb2e80..0000000
--- a/systemvm/agent/noVNC/app/images/settings.svg
+++ /dev/null
@@ -1,76 +0,0 @@
-<?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  [...]
-       transform="translate(0,1027.3622)"
-       id="rect4967" />
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/tab.png b/systemvm/agent/noVNC/app/images/tab.png
new file mode 100644
index 0000000..6adb781
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/tab.png differ
diff --git a/systemvm/agent/noVNC/app/images/tab.svg b/systemvm/agent/noVNC/app/images/tab.svg
deleted file mode 100644
index 1ccb322..0000000
--- a/systemvm/agent/noVNC/app/images/tab.svg
+++ /dev/null
@@ -1,86 +0,0 @@
-<?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>
diff --git a/systemvm/agent/noVNC/app/images/toggleextrakeys.png b/systemvm/agent/noVNC/app/images/toggleextrakeys.png
new file mode 100644
index 0000000..0f80f6d
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/toggleextrakeys.png differ
diff --git a/systemvm/agent/noVNC/app/images/toggleextrakeys.svg b/systemvm/agent/noVNC/app/images/toggleextrakeys.svg
deleted file mode 100644
index b578c0d..0000000
--- a/systemvm/agent/noVNC/app/images/toggleextrakeys.svg
+++ /dev/null
@@ -1,90 +0,0 @@
-<?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:nonze [...]
-       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>
diff --git a/systemvm/agent/noVNC/app/images/warning.png b/systemvm/agent/noVNC/app/images/warning.png
new file mode 100644
index 0000000..e9dd550
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/warning.png differ
diff --git a/systemvm/agent/noVNC/app/images/warning.svg b/systemvm/agent/noVNC/app/images/warning.svg
deleted file mode 100644
index 7114f9b..0000000
--- a/systemvm/agent/noVNC/app/images/warning.svg
+++ /dev/null
@@ -1,81 +0,0 @@
-<?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:nonze [...]
-       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 1 [...]
-       transform="translate(0,1027.3622)"
-       id="path4208" />
-  </g>
-</svg>
diff --git a/systemvm/agent/noVNC/app/images/windows.png b/systemvm/agent/noVNC/app/images/windows.png
new file mode 100644
index 0000000..036e1ae
Binary files /dev/null and b/systemvm/agent/noVNC/app/images/windows.png differ
diff --git a/systemvm/agent/noVNC/app/images/windows.svg b/systemvm/agent/noVNC/app/images/windows.svg
deleted file mode 100644
index 270405c..0000000
--- a/systemvm/agent/noVNC/app/images/windows.svg
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-
-<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"
-   version="1.1"
-   id="svg2"
-   inkscape:export-ydpi="90"
-   inkscape:export-xdpi="90"
-   sodipodi:docname="windows.svg"
-   inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
-   inkscape:version="0.92.3 (2405546, 2018-03-11)"
-   x="0px"
-   y="0px"
-   viewBox="-293 384 25 23"
-   xml:space="preserve"
-   width="25"
-   height="23"><metadata
-   id="metadata21"><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><defs
-   id="defs19" /><sodipodi:namedview
-   pagecolor="#ffffff"
-   bordercolor="#666666"
-   borderopacity="1"
-   objecttolerance="10"
-   gridtolerance="10"
-   guidetolerance="10"
-   inkscape:pageopacity="0"
-   inkscape:pageshadow="2"
-   inkscape:window-width="1920"
-   inkscape:window-height="1017"
-   id="namedview17"
-   showgrid="false"
-   inkscape:pagecheckerboard="true"
-   inkscape:zoom="9.44"
-   inkscape:cx="-0.84745763"
-   inkscape:cy="12.5"
-   inkscape:window-x="2552"
-   inkscape:window-y="122"
-   inkscape:window-maximized="1"
-   inkscape:current-layer="svg2" />
-<style
-   type="text/css"
-   id="style2">
-	.st0{fill:#FFFFFF;}
-</style>
-<g
-   id="g14"
-   transform="matrix(1.2624869,0,0,1.3601695,73.614445,-144.84322)">
-	<g
-   id="g12">
-		<path
-   class="st0"
-   d="m -277.4,396 c -0.7,0 -1.3,0 -2,0 -0.4,0 -0.5,-0.1 -0.5,-0.5 0,-1 0,-2 0,-3 0,-0.3 0.2,-0.5 0.5,-0.5 1.3,-0.1 2.6,-0.3 3.9,-0.4 0.4,0 0.7,0.1 0.7,0.6 0,1.1 0,2.2 0,3.3 0,0.4 -0.2,0.6 -0.6,0.6 -0.7,-0.1 -1.4,-0.1 -2,-0.1 z"
-   id="path4"
-   inkscape:connector-curvature="0"
-   style="fill:#ffffff" />
-		<path
-   class="st0"
-   d="m -274.9,399.3 c 0,0.6 0,1.1 0,1.7 0,0.4 -0.1,0.6 -0.6,0.6 -1.4,-0.1 -2.8,-0.3 -4.1,-0.4 -0.3,0 -0.4,-0.3 -0.4,-0.5 0,-1 0,-2 0,-3 0,-0.4 0.2,-0.5 0.6,-0.5 1.3,0 2.6,0 3.9,0 0.5,0 0.6,0.2 0.6,0.6 0,0.4 0,0.9 0,1.5 z"
-   id="path6"
-   inkscape:connector-curvature="0"
-   style="fill:#ffffff" />
-		<path
-   class="st0"
-   d="m -283.5,396 c -0.6,0 -1.3,0 -1.9,0 -0.4,0 -0.6,-0.1 -0.6,-0.6 0,-0.8 0,-1.5 0,-2.3 0,-0.4 0.2,-0.6 0.6,-0.7 1.3,-0.1 2.7,-0.3 4,-0.4 0.4,0 0.5,0.1 0.5,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -0.8,0.1 -1.5,0.1 -2.1,0.1 z"
-   id="path8"
-   inkscape:connector-curvature="0"
-   style="fill:#ffffff" />
-		<path
-   class="st0"
-   d="m -283.5,397 c 0.6,0 1.3,0 1.9,0 0.4,0 0.6,0.1 0.6,0.5 0,1 0,1.9 0,2.9 0,0.4 -0.2,0.5 -0.5,0.5 -1.3,-0.1 -2.7,-0.3 -4,-0.4 -0.4,0 -0.6,-0.2 -0.6,-0.7 0,-0.7 0,-1.5 0,-2.2 0,-0.5 0.2,-0.7 0.7,-0.7 0.6,0.1 1.2,0.1 1.9,0.1 z"
-   id="path10"
-   inkscape:connector-curvature="0"
-   style="fill:#ffffff" />
-	</g>
-</g>
-</svg>
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/README b/systemvm/agent/noVNC/app/locale/README
new file mode 100644
index 0000000..ca4f548
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/README
@@ -0,0 +1 @@
+DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.
diff --git a/systemvm/agent/noVNC/app/locale/ja.json b/systemvm/agent/noVNC/app/locale/ja.json
new file mode 100644
index 0000000..e5fe340
--- /dev/null
+++ b/systemvm/agent/noVNC/app/locale/ja.json
@@ -0,0 +1,73 @@
+{
+    "Connecting...": "接続しています...",
+    "Disconnecting...": "切断しています...",
+    "Reconnecting...": "再接続しています...",
+    "Internal error": "内部エラー",
+    "Must set host": "ホストを設定する必要があります",
+    "Connected (encrypted) to ": "接続しました (暗号化済み): ",
+    "Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
+    "Something went wrong, connection is closed": "何かが問題で、接続が閉じられました",
+    "Failed to connect to server": "サーバーへの接続に失敗しました",
+    "Disconnected": "切断しました",
+    "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
+    "New connection has been rejected": "新規接続は拒否されました",
+    "Password is required": "パスワードが必要です",
+    "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 キーを切り替え",
+    "Toggle Windows": "Windows キーを切り替え",
+    "Windows": "Windows",
+    "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": "設定",
+    "Shared Mode": "共有モード",
+    "View Only": "表示のみ",
+    "Clip to Window": "ウィンドウにクリップ",
+    "Scaling Mode:": "スケーリングモード:",
+    "None": "なし",
+    "Local Scaling": "ローカルスケーリング",
+    "Remote Resizing": "リモートでリサイズ",
+    "Advanced": "高度",
+    "Repeater ID:": "リピーター ID:",
+    "WebSocket": "WebSocket",
+    "Encrypt": "暗号化",
+    "Host:": "ホスト:",
+    "Port:": "ポート:",
+    "Path:": "パス:",
+    "Automatic Reconnect": "自動再接続",
+    "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):",
+    "Show Dot when No Cursor": "カーソルがないときにドットを表示",
+    "Logging:": "ロギング:",
+    "Disconnect": "切断",
+    "Connect": "接続",
+    "Password:": "パスワード:",
+    "Send Password": "パスワードを送信",
+    "Cancel": "キャンセル"
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/sv.json b/systemvm/agent/noVNC/app/locale/sv.json
index d49ea54..e46df45 100644
--- a/systemvm/agent/noVNC/app/locale/sv.json
+++ b/systemvm/agent/noVNC/app/locale/sv.json
@@ -11,16 +11,11 @@
     "Disconnected": "Frånkopplad",
     "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
     "New connection has been rejected": "Ny anslutning har blivit nekad",
-    "Password is required": "Lösenord krävs",
+    "Credentials are required": "Användaruppgifter krävs",
     "noVNC encountered an error:": "noVNC stötte på ett problem:",
     "Hide/Show the control bar": "Göm/Visa kontrollbaren",
+    "Drag": "Dra",
     "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",
@@ -55,6 +50,8 @@
     "Local Scaling": "Lokal Skalning",
     "Remote Resizing": "Ändra Storlek",
     "Advanced": "Avancerat",
+    "Quality:": "Kvalitet:",
+    "Compression level:": "Kompressionsnivå:",
     "Repeater ID:": "Repeater-ID:",
     "WebSocket": "WebSocket",
     "Encrypt": "Kryptera",
@@ -65,9 +62,11 @@
     "Reconnect Delay (ms):": "Fördröjning (ms):",
     "Show Dot when No Cursor": "Visa prick när ingen muspekare finns",
     "Logging:": "Loggning:",
+    "Version:": "Version:",
     "Disconnect": "Koppla från",
     "Connect": "Anslut",
+    "Username:": "Användarnamn:",
     "Password:": "Lösenord:",
-    "Send Password": "Skicka lösenord",
+    "Send Credentials": "Skicka Användaruppgifter",
     "Cancel": "Avbryt"
 }
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/locale/zh_CN.json b/systemvm/agent/noVNC/app/locale/zh_CN.json
index b669956..f0aea9a 100644
--- a/systemvm/agent/noVNC/app/locale/zh_CN.json
+++ b/systemvm/agent/noVNC/app/locale/zh_CN.json
@@ -1,19 +1,19 @@
 {
-    "Connecting...": "链接中...",
-    "Disconnecting...": "正在中断连接...",
-    "Reconnecting...": "重新链接中...",
+    "Connecting...": "连接中...",
+    "Disconnecting...": "正在断开连接...",
+    "Reconnecting...": "重新连接中...",
     "Internal error": "内部错误",
     "Must set host": "请提供主机名",
-    "Connected (encrypted) to ": "已加密链接到",
-    "Connected (unencrypted) to ": "未加密链接到",
-    "Something went wrong, connection is closed": "发生错误,链接已关闭",
-    "Failed to connect to server": "无法链接到服务器",
-    "Disconnected": "链接已中断",
-    "New connection has been rejected with reason: ": "链接被拒绝,原因:",
-    "New connection has been rejected": "链接被拒绝",
+    "Connected (encrypted) to ": "已连接到(加密)",
+    "Connected (unencrypted) to ": "已连接到(未加密)",
+    "Something went wrong, connection is closed": "发生错误,连接已关闭",
+    "Failed to connect to server": "无法连接到服务器",
+    "Disconnected": "已断开连接",
+    "New connection has been rejected with reason: ": "连接被拒绝,原因:",
+    "New connection has been rejected": "连接被拒绝",
     "Password is required": "请提供密码",
     "noVNC encountered an error:": "noVNC 遇到一个错误:",
-    "Hide/Show the control bar": "显示/隐藏控制列",
+    "Hide/Show the control bar": "显示/隐藏控制栏",
     "Move/Drag Viewport": "拖放显示范围",
     "viewport drag": "显示范围拖放",
     "Active Mouse Button": "启动鼠标按鍵",
@@ -43,10 +43,10 @@
     "Reset": "重置",
     "Clipboard": "剪贴板",
     "Clear": "清除",
-    "Fullscreen": "全屏幕",
+    "Fullscreen": "全屏",
     "Settings": "设置",
     "Shared Mode": "分享模式",
-    "View Only": "仅检视",
+    "View Only": "仅查看",
     "Clip to Window": "限制/裁切窗口大小",
     "Scaling Mode:": "缩放模式:",
     "None": "无",
@@ -59,11 +59,11 @@
     "Host:": "主机:",
     "Port:": "端口:",
     "Path:": "路径:",
-    "Automatic Reconnect": "自动重新链接",
-    "Reconnect Delay (ms):": "重新链接间隔 (ms):",
+    "Automatic Reconnect": "自动重新连接",
+    "Reconnect Delay (ms):": "重新连接间隔 (ms):",
     "Logging:": "日志级别:",
-    "Disconnect": "终端链接",
-    "Connect": "链接",
+    "Disconnect": "中断连接",
+    "Connect": "连接",
     "Password:": "密码:",
     "Cancel": "取消"
 }
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/app/styles/base.css b/systemvm/agent/noVNC/app/styles/base.css
index 3ca9894..fd78b79 100644
--- a/systemvm/agent/noVNC/app/styles/base.css
+++ b/systemvm/agent/noVNC/app/styles/base.css
@@ -1,6 +1,6 @@
 /*
  * noVNC base CSS
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
  * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
  */
@@ -83,8 +83,20 @@ html {
  * ----------------------------------------
  */
 
-input[type=input], input[type=password], input[type=number],
-input:not([type]), textarea {
+input:not([type]),
+input[type=date],
+input[type=datetime-local],
+input[type=email],
+input[type=month],
+input[type=number],
+input[type=password],
+input[type=search],
+input[type=tel],
+input[type=text],
+input[type=time],
+input[type=url],
+input[type=week],
+textarea {
   /* Disable default rendering */
   -webkit-appearance: none;
   -moz-appearance: none;
@@ -98,7 +110,11 @@ input:not([type]), textarea {
   background: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240));
 }
 
-input[type=button], input[type=submit], select {
+input[type=button],
+input[type=color],
+input[type=reset],
+input[type=submit],
+select {
   /* Disable default rendering */
   -webkit-appearance: none;
   -moz-appearance: none;
@@ -116,7 +132,10 @@ input[type=button], input[type=submit], select {
   vertical-align: middle;
 }
 
-input[type=button], input[type=submit] {
+input[type=button],
+input[type=color],
+input[type=reset],
+input[type=submit] {
   padding-left: 20px;
   padding-right: 20px;
 }
@@ -126,35 +145,72 @@ option {
   background: white;
 }
 
-input[type=input]:focus, input[type=password]:focus,
-input:not([type]):focus, input[type=button]:focus,
+input:not([type]):focus,
+input[type=button]:focus,
+input[type=color]:focus,
+input[type=date]:focus,
+input[type=datetime-local]:focus,
+input[type=email]:focus,
+input[type=month]:focus,
+input[type=number]:focus,
+input[type=password]:focus,
+input[type=reset]:focus,
+input[type=search]:focus,
 input[type=submit]:focus,
-textarea:focus, select:focus {
+input[type=tel]:focus,
+input[type=text]:focus,
+input[type=time]:focus,
+input[type=url]:focus,
+input[type=week]:focus,
+select:focus,
+textarea: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=color]::-moz-focus-inner,
+input[type=reset]::-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 {
+input:not([type]):disabled,
+input[type=button]:disabled,
+input[type=color]:disabled,
+input[type=date]:disabled,
+input[type=datetime-local]:disabled,
+input[type=email]:disabled,
+input[type=month]:disabled,
+input[type=number]:disabled,
+input[type=password]:disabled,
+input[type=reset]:disabled,
+input[type=search]:disabled,
+input[type=submit]:disabled,
+input[type=tel]:disabled,
+input[type=text]:disabled,
+input[type=time]:disabled,
+input[type=url]:disabled,
+input[type=week]:disabled,
+select:disabled,
+textarea:disabled {
   color: rgb(128, 128, 128);
   background: rgb(240, 240, 240);
 }
 
-input[type=button]:active, input[type=submit]:active,
+input[type=button]:active,
+input[type=color]:active,
+input[type=reset]: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=color]:hover:not(:disabled),
+:root:not(.noVNC_touch) input[type=reset]: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));
@@ -579,7 +635,7 @@ select:active {
 }
 
 /* Extra manual keys */
-:root:not(.noVNC_connected) #noVNC_extra_keys {
+:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {
   display: none;
 }
 
@@ -631,6 +687,16 @@ select:active {
   width: 100px;
 }
 
+/* Version */
+
+.noVNC_version_wrapper {
+  font-size: small;
+}
+
+.noVNC_version {
+  margin-left: 1rem;
+}
+
 /* Connection Controls */
 :root:not(.noVNC_connected) #noVNC_disconnect_button {
   display: none;
@@ -780,19 +846,23 @@ select:active {
  * ----------------------------------------
  */
 
-#noVNC_password_dlg {
+#noVNC_credentials_dlg {
   position: relative;
 
   transform: translateY(-50px);
 }
-#noVNC_password_dlg.noVNC_open {
+#noVNC_credentials_dlg.noVNC_open {
   transform: translateY(0);
 }
-#noVNC_password_dlg ul {
+#noVNC_credentials_dlg ul {
   list-style: none;
   margin: 0px;
   padding: 0px;
 }
+.noVNC_hidden {
+  display: none;
+}
+
 
 /* ----------------------------------------
  * Main Area
diff --git a/systemvm/agent/noVNC/app/ui.js b/systemvm/agent/noVNC/app/ui.js
index 13d1c01..9158c33 100644
--- a/systemvm/agent/noVNC/app/ui.js
+++ b/systemvm/agent/noVNC/app/ui.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -8,7 +8,7 @@
 
 import * as Log from '../core/util/logging.js';
 import _, { l10n } from './localization.js';
-import { isTouchDevice, isSafari, isIOS, isAndroid, dragThreshold }
+import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold }
     from '../core/util/browser.js';
 import { setCapture, getPointerEvent } from '../core/util/events.js';
 import KeyTable from "../core/input/keysym.js";
@@ -17,6 +17,8 @@ import Keyboard from "../core/input/keyboard.js";
 import RFB from "../core/rfb.js";
 import * as WebUtil from "./webutil.js";
 
+const PAGE_TITLE = "noVNC";
+
 const UI = {
 
     connected: false,
@@ -35,9 +37,11 @@ const UI = {
     lastKeyboardinput: null,
     defaultKeyboardinputLen: 100,
 
-    inhibit_reconnect: true,
-    reconnect_callback: null,
-    reconnect_password: null,
+    inhibitReconnect: true,
+    reconnectCallback: null,
+    reconnectPassword: null,
+
+    fullScreen: false,
 
     prime() {
         return WebUtil.initSettings().then(() => {
@@ -59,6 +63,17 @@ const UI = {
         // Translate the DOM
         l10n.translateDOM();
 
+        WebUtil.fetchJSON('./package.json')
+            .then((packageInfo) => {
+                Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
+            })
+            .catch((err) => {
+                Log.Error("Couldn't fetch package.json: " + err);
+                Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
+                    .concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
+                    .forEach(el => el.style.display = 'none');
+            });
+
         // Adapt the interface for touch screen devices
         if (isTouchDevice) {
             document.documentElement.classList.add("noVNC_touch");
@@ -145,10 +160,13 @@ const UI = {
         /* Populate the controls if defaults are provided in the URL */
         UI.initSetting('host', window.location.hostname);
         UI.initSetting('port', port);
+        UI.initSetting('token', window.location.token);
         UI.initSetting('encrypt', (window.location.protocol === "https:"));
         UI.initSetting('view_clip', false);
         UI.initSetting('resize', 'off');
-        UI.initSetting('shared', false);
+        UI.initSetting('quality', 6);
+        UI.initSetting('compression', 2);
+        UI.initSetting('shared', true);
         UI.initSetting('view_only', false);
         UI.initSetting('show_dot', false);
         UI.initSetting('path', 'websockify');
@@ -219,14 +237,6 @@ const UI = {
     },
 
     addTouchSpecificHandlers() {
-        document.getElementById("noVNC_mouse_button0")
-            .addEventListener('click', () => UI.setMouseButton(1));
-        document.getElementById("noVNC_mouse_button1")
-            .addEventListener('click', () => UI.setMouseButton(2));
-        document.getElementById("noVNC_mouse_button2")
-            .addEventListener('click', () => UI.setMouseButton(4));
-        document.getElementById("noVNC_mouse_button4")
-            .addEventListener('click', () => UI.setMouseButton(0));
         document.getElementById("noVNC_keyboard_button")
             .addEventListener('click', UI.toggleVirtualKeyboard);
 
@@ -303,17 +313,17 @@ const UI = {
         document.getElementById("noVNC_cancel_reconnect_button")
             .addEventListener('click', UI.cancelReconnect);
 
-        document.getElementById("noVNC_password_button")
-            .addEventListener('click', UI.setPassword);
+        document.getElementById("noVNC_credentials_button")
+            .addEventListener('click', UI.setCredentials);
     },
 
     addClipboardHandlers() {
         document.getElementById("noVNC_clipboard_button")
             .addEventListener('click', UI.toggleClipboardPanel);
-        document.getElementById("noVNC_clipboard_text")
-            .addEventListener('change', UI.clipboardSend);
         document.getElementById("noVNC_clipboard_clear_button")
             .addEventListener('click', UI.clipboardClear);
+        document.getElementById("noVNC_clipboard_send_button")
+            .addEventListener('click', UI.clipboardSend);
     },
 
     // Add a call to save settings when the element changes,
@@ -334,6 +344,10 @@ const UI = {
         UI.addSettingChangeHandler('resize');
         UI.addSettingChangeHandler('resize', UI.applyResizeMode);
         UI.addSettingChangeHandler('resize', UI.updateViewClip);
+        UI.addSettingChangeHandler('quality');
+        UI.addSettingChangeHandler('quality', UI.updateQuality);
+        UI.addSettingChangeHandler('compression');
+        UI.addSettingChangeHandler('compression', UI.updateCompression);
         UI.addSettingChangeHandler('view_clip');
         UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
         UI.addSettingChangeHandler('shared');
@@ -375,25 +389,25 @@ const UI = {
         document.documentElement.classList.remove("noVNC_disconnecting");
         document.documentElement.classList.remove("noVNC_reconnecting");
 
-        const transition_elem = document.getElementById("noVNC_transition_text");
+        const transitionElem = document.getElementById("noVNC_transition_text");
         switch (state) {
             case 'init':
                 break;
             case 'connecting':
-                transition_elem.textContent = _("Connecting...");
+                transitionElem.textContent = _("Connecting...");
                 document.documentElement.classList.add("noVNC_connecting");
                 break;
             case 'connected':
                 document.documentElement.classList.add("noVNC_connected");
                 break;
             case 'disconnecting':
-                transition_elem.textContent = _("Disconnecting...");
+                transitionElem.textContent = _("Disconnecting...");
                 document.documentElement.classList.add("noVNC_disconnecting");
                 break;
             case 'disconnected':
                 break;
             case 'reconnecting':
-                transition_elem.textContent = _("Reconnecting...");
+                transitionElem.textContent = _("Reconnecting...");
                 document.documentElement.classList.add("noVNC_reconnecting");
                 break;
             default:
@@ -411,7 +425,6 @@ const UI = {
             UI.disableSetting('port');
             UI.disableSetting('path');
             UI.disableSetting('repeaterID');
-            UI.setMouseButton(1);
 
             // Hide the controlbar after 2 seconds
             UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
@@ -426,38 +439,35 @@ const UI = {
             UI.keepControlbar();
         }
 
-        // State change closes the password dialog
-        document.getElementById('noVNC_password_dlg')
+        // State change closes dialogs as they may not be relevant
+        // anymore
+        UI.closeAllPanels();
+        document.getElementById('noVNC_credentials_dlg')
             .classList.remove('noVNC_open');
     },
 
-    showStatus(text, status_type, time) {
+    showStatus(text, statusType, time) {
         const statusElem = document.getElementById('noVNC_status');
 
-        clearTimeout(UI.statusTimeout);
-
-        if (typeof status_type === 'undefined') {
-            status_type = 'normal';
+        if (typeof statusType === 'undefined') {
+            statusType = 'normal';
         }
 
         // Don't overwrite more severe visible statuses and never
         // errors. Only shows the first error.
-        let visible_status_type = 'none';
         if (statusElem.classList.contains("noVNC_open")) {
             if (statusElem.classList.contains("noVNC_status_error")) {
-                visible_status_type = 'error';
-            } else if (statusElem.classList.contains("noVNC_status_warn")) {
-                visible_status_type = 'warn';
-            } else {
-                visible_status_type = 'normal';
+                return;
+            }
+            if (statusElem.classList.contains("noVNC_status_warn") &&
+                statusType === 'normal') {
+                return;
             }
         }
-        if (visible_status_type === 'error' ||
-            (visible_status_type === 'warn' && status_type === 'normal')) {
-            return;
-        }
 
-        switch (status_type) {
+        clearTimeout(UI.statusTimeout);
+
+        switch (statusType) {
             case 'error':
                 statusElem.classList.remove("noVNC_status_warn");
                 statusElem.classList.remove("noVNC_status_normal");
@@ -487,7 +497,7 @@ const UI = {
         }
 
         // Error messages do not timeout
-        if (status_type !== 'error') {
+        if (statusType !== 'error') {
             UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
         }
     },
@@ -507,6 +517,13 @@ const UI = {
     },
 
     idleControlbar() {
+        // Don't fade if a child of the control bar has focus
+        if (document.getElementById('noVNC_control_bar')
+            .contains(document.activeElement) && document.hasFocus()) {
+            UI.activateControlbar();
+            return;
+        }
+
         document.getElementById('noVNC_control_bar_anchor')
             .classList.add("noVNC_idle");
     },
@@ -524,6 +541,7 @@ const UI = {
         UI.closeAllPanels();
         document.getElementById('noVNC_control_bar')
             .classList.remove("noVNC_open");
+        UI.rfb.focus();
     },
 
     toggleControlbar() {
@@ -821,6 +839,8 @@ const UI = {
         UI.updateSetting('encrypt');
         UI.updateSetting('view_clip');
         UI.updateSetting('resize');
+        UI.updateSetting('quality');
+        UI.updateSetting('compression');
         UI.updateSetting('shared');
         UI.updateSetting('view_only');
         UI.updateSetting('path');
@@ -927,6 +947,8 @@ const UI = {
             UI.closeClipboardPanel();
         } else {
             UI.openClipboardPanel();
+            setTimeout(() => document
+                .getElementById('noVNC_clipboard_text').focus(), 100);
         }
     },
 
@@ -938,14 +960,13 @@ const UI = {
 
     clipboardClear() {
         document.getElementById('noVNC_clipboard_text').value = "";
-        UI.rfb.clipboardPasteFrom("");
     },
 
     clipboardSend() {
         const text = document.getElementById('noVNC_clipboard_text').value;
-        Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "...");
-        UI.rfb.clipboardPasteFrom(text);
-        Log.Debug("<< UI.clipboardSend");
+        UI.rfb.sendText(text);
+        UI.closeClipboardPanel();
+        UI.focusOnConsole();
     },
 
 /* ------^-------
@@ -974,10 +995,11 @@ const UI = {
         const host = UI.getSetting('host');
         const port = UI.getSetting('port');
         const path = UI.getSetting('path');
+        const token = UI.getSetting('token')
 
         if (typeof password === 'undefined') {
             password = WebUtil.getConfigVar('password');
-            UI.reconnect_password = password;
+            UI.reconnectPassword = password;
         }
 
         if (password === null) {
@@ -992,7 +1014,6 @@ const UI = {
             return;
         }
 
-        UI.closeAllPanels();
         UI.closeConnectPanel();
 
         UI.updateVisualState('connecting');
@@ -1006,16 +1027,10 @@ const UI = {
             url += ':' + port;
         }
         url += '/' + path;
-
-        var urlParams = new URLSearchParams(window.location.search);
-        var param = urlParams.get('token');
-        if (param) {
-            url += "?token=" + param
-        }
+        url += '?token=' + token;
 
         UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
                          { shared: UI.getSetting('shared'),
-                           showDotCursor: UI.getSetting('show_dot'),
                            repeaterID: UI.getSetting('repeaterID'),
                            credentials: { password: password } });
         UI.rfb.addEventListener("connect", UI.connectFinished);
@@ -1029,18 +1044,20 @@ const UI = {
         UI.rfb.clipViewport = UI.getSetting('view_clip');
         UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
         UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
+        UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
+        UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
+        UI.rfb.showDotCursor = UI.getSetting('show_dot');
 
         UI.updateViewOnly(); // requires UI.rfb
     },
 
     disconnect() {
-        UI.closeAllPanels();
         UI.rfb.disconnect();
 
         UI.connected = false;
 
         // Disable automatic reconnecting
-        UI.inhibit_reconnect = true;
+        UI.inhibitReconnect = true;
 
         UI.updateVisualState('disconnecting');
 
@@ -1048,20 +1065,20 @@ const UI = {
     },
 
     reconnect() {
-        UI.reconnect_callback = null;
+        UI.reconnectCallback = null;
 
         // if reconnect has been disabled in the meantime, do nothing.
-        if (UI.inhibit_reconnect) {
+        if (UI.inhibitReconnect) {
             return;
         }
 
-        UI.connect(null, UI.reconnect_password);
+        UI.connect(null, UI.reconnectPassword);
     },
 
     cancelReconnect() {
-        if (UI.reconnect_callback !== null) {
-            clearTimeout(UI.reconnect_callback);
-            UI.reconnect_callback = null;
+        if (UI.reconnectCallback !== null) {
+            clearTimeout(UI.reconnectCallback);
+            UI.reconnectCallback = null;
         }
 
         UI.updateVisualState('disconnected');
@@ -1072,13 +1089,13 @@ const UI = {
 
     connectFinished(e) {
         UI.connected = true;
-        UI.inhibit_reconnect = false;
+        UI.inhibitReconnect = false;
 
         let msg;
         if (UI.getSetting('encrypt')) {
-            msg = _("Connected (encrypted) to ") + UI.desktopName;
+            msg = _("Connected");
         } else {
-            msg = _("Connected (unencrypted) to ") + UI.desktopName;
+            msg = _("Connected")
         }
         UI.showStatus(msg);
         UI.updateVisualState('connected');
@@ -1106,17 +1123,19 @@ const UI = {
             } else {
                 UI.showStatus(_("Failed to connect to server"), 'error');
             }
-        } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
+        } else if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
             UI.updateVisualState('reconnecting');
 
             const delay = parseInt(UI.getSetting('reconnect_delay'));
-            UI.reconnect_callback = setTimeout(UI.reconnect, delay);
+            UI.reconnectCallback = setTimeout(UI.reconnect, delay);
             return;
         } else {
             UI.updateVisualState('disconnected');
             UI.showStatus(_("Disconnected"), 'normal');
         }
 
+        document.title = PAGE_TITLE;
+
         UI.openControlbar();
         UI.openConnectPanel();
     },
@@ -1143,27 +1162,46 @@ const UI = {
 
     credentials(e) {
         // FIXME: handle more types
-        document.getElementById('noVNC_password_dlg')
+
+        document.getElementById("noVNC_username_block").classList.remove("noVNC_hidden");
+        document.getElementById("noVNC_password_block").classList.remove("noVNC_hidden");
+
+        let inputFocus = "none";
+        if (e.detail.types.indexOf("username") === -1) {
+            document.getElementById("noVNC_username_block").classList.add("noVNC_hidden");
+        } else {
+            inputFocus = inputFocus === "none" ? "noVNC_username_input" : inputFocus;
+        }
+        if (e.detail.types.indexOf("password") === -1) {
+            document.getElementById("noVNC_password_block").classList.add("noVNC_hidden");
+        } else {
+            inputFocus = inputFocus === "none" ? "noVNC_password_input" : inputFocus;
+        }
+        document.getElementById('noVNC_credentials_dlg')
             .classList.add('noVNC_open');
 
         setTimeout(() => document
-            .getElementById('noVNC_password_input').focus(), 100);
+            .getElementById(inputFocus).focus(), 100);
 
-        Log.Warn("Server asked for a password");
-        UI.showStatus(_("Password is required"), "warning");
+        Log.Warn("Server asked for credentials");
+        UI.showStatus(_("Credentials are required"), "warning");
     },
 
-    setPassword(e) {
+    setCredentials(e) {
         // Prevent actually submitting the form
         e.preventDefault();
 
-        const inputElem = document.getElementById('noVNC_password_input');
-        const password = inputElem.value;
+        let inputElemUsername = document.getElementById('noVNC_username_input');
+        const username = inputElemUsername.value;
+
+        let inputElemPassword = document.getElementById('noVNC_password_input');
+        const password = inputElemPassword.value;
         // Clear the input after reading the password
-        inputElem.value = "";
-        UI.rfb.sendCredentials({ password: password });
-        UI.reconnect_password = password;
-        document.getElementById('noVNC_password_dlg')
+        inputElemPassword.value = "";
+
+        UI.rfb.sendCredentials({ username: username, password: password });
+        UI.reconnectPassword = password;
+        document.getElementById('noVNC_credentials_dlg')
             .classList.remove('noVNC_open');
     },
 
@@ -1174,38 +1212,14 @@ const UI = {
  * ------v------*/
 
     toggleFullscreen() {
-        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.updateFullscreenButton();
+        this.fullScreen = !this.fullScreen
+        UI.rfb.scaleViewport = this.fullScreen
+        UI.updateFullscreenButton(this.fullScreen);
+        UI.focusOnConsole();
     },
 
-    updateFullscreenButton() {
-        if (document.fullscreenElement || // alternative standard method
-            document.mozFullScreenElement || // currently working methods
-            document.webkitFullscreenElement ||
-            document.msFullscreenElement ) {
+    updateFullscreenButton(fullScreen) {
+        if (fullScreen) {
             document.getElementById('noVNC_fullscreen_button')
                 .classList.add("noVNC_selected");
         } else {
@@ -1246,8 +1260,9 @@ const UI = {
             // Can't be clipping if viewport is scaled to fit
             UI.forceSetting('view_clip', false);
             UI.rfb.clipViewport  = false;
-        } else if (isIOS() || isAndroid()) {
-            // iOS and Android usually have shit scrollbars
+        } else if (!hasScrollbarGutter) {
+            // Some platforms have scrollbars that are difficult
+            // to use in our case, so we always use our own panning
             UI.forceSetting('view_clip', true);
             UI.rfb.clipViewport = true;
         } else {
@@ -1290,30 +1305,40 @@ const UI = {
             viewDragButton.classList.remove("noVNC_selected");
         }
 
-        // Different behaviour for touch vs non-touch
-        // The button is disabled instead of hidden on touch devices
-        if (isTouchDevice) {
+        if (UI.rfb.clipViewport) {
             viewDragButton.classList.remove("noVNC_hidden");
-
-            if (UI.rfb.clipViewport) {
-                viewDragButton.disabled = false;
-            } else {
-                viewDragButton.disabled = true;
-            }
         } else {
-            viewDragButton.disabled = false;
-
-            if (UI.rfb.clipViewport) {
-                viewDragButton.classList.remove("noVNC_hidden");
-            } else {
-                viewDragButton.classList.add("noVNC_hidden");
-            }
+            viewDragButton.classList.add("noVNC_hidden");
         }
     },
 
 /* ------^-------
  *   /VIEWDRAG
  * ==============
+ *    QUALITY
+ * ------v------*/
+
+    updateQuality() {
+        if (!UI.rfb) return;
+
+        UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
+    },
+
+/* ------^-------
+ *   /QUALITY
+ * ==============
+ *  COMPRESSION
+ * ------v------*/
+
+    updateCompression() {
+        if (!UI.rfb) return;
+
+        UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
+    },
+
+/* ------^-------
+ *  /COMPRESSION
+ * ==============
  *    KEYBOARD
  * ------v------*/
 
@@ -1508,20 +1533,20 @@ const UI = {
     },
 
     sendEsc() {
-        UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
+        UI.sendKey(KeyTable.XK_Escape, "Escape");
     },
 
     sendTab() {
-        UI.rfb.sendKey(KeyTable.XK_Tab);
+        UI.sendKey(KeyTable.XK_Tab, "Tab");
     },
 
     toggleCtrl() {
         const btn = document.getElementById('noVNC_toggle_ctrl_button');
         if (btn.classList.contains("noVNC_selected")) {
-            UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+            UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
             btn.classList.remove("noVNC_selected");
         } else {
-            UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+            UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
             btn.classList.add("noVNC_selected");
         }
     },
@@ -1529,10 +1554,10 @@ const UI = {
     toggleWindows() {
         const btn = document.getElementById('noVNC_toggle_windows_button');
         if (btn.classList.contains("noVNC_selected")) {
-            UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
+            UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
             btn.classList.remove("noVNC_selected");
         } else {
-            UI.rfb.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
+            UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
             btn.classList.add("noVNC_selected");
         }
     },
@@ -1540,16 +1565,42 @@ const UI = {
     toggleAlt() {
         const btn = document.getElementById('noVNC_toggle_alt_button');
         if (btn.classList.contains("noVNC_selected")) {
-            UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+            UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
             btn.classList.remove("noVNC_selected");
         } else {
-            UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+            UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
             btn.classList.add("noVNC_selected");
         }
     },
 
     sendCtrlAltDel() {
         UI.rfb.sendCtrlAltDel();
+        // See below
+        UI.rfb.focus();
+        UI.idleControlbar();
+    },
+
+    // Move focus to the screen in order to be able to use the
+    // keyboard right after these extra keys.
+    // The exception is when a virtual keyboard is used, because
+    // if we focus the screen the virtual keyboard would be closed.
+    // In this case we focus our special virtual keyboard input
+    // element instead.
+    focusOnConsole() {
+        if (document.getElementById('noVNC_keyboard_button')
+            .classList.contains("noVNC_selected")) {
+            document.getElementById('noVNC_keyboardinput').focus();
+        } else {
+            UI.rfb.focus();
+        }
+    },
+
+    sendKey(keysym, code, down) {
+        UI.rfb.sendKey(keysym, code, down);
+        UI.focusOnConsole()
+        // fade out the controlbar to highlight that
+        // the focus has been moved to the screen
+        UI.idleControlbar();
     },
 
 /* ------^-------
@@ -1558,24 +1609,6 @@ const UI = {
  *     MISC
  * ------v------*/
 
-    setMouseButton(num) {
-        const view_only = UI.rfb.viewOnly;
-        if (UI.rfb && !view_only) {
-            UI.rfb.touchButton = num;
-        }
-
-        const blist = [0, 1, 2, 4];
-        for (let b = 0; b < blist.length; b++) {
-            const 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");
-            }
-        }
-    },
-
     updateViewOnly() {
         if (!UI.rfb) return;
         UI.rfb.viewOnly = UI.getSetting('view_only');
@@ -1586,14 +1619,14 @@ const UI = {
                 .classList.add('noVNC_hidden');
             document.getElementById('noVNC_toggle_extra_keys_button')
                 .classList.add('noVNC_hidden');
-            document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
+            document.getElementById('noVNC_clipboard_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');
-            document.getElementById('noVNC_mouse_button' + UI.rfb.touchButton)
+            document.getElementById('noVNC_clipboard_button')
                 .classList.remove('noVNC_hidden');
         }
     },
@@ -1604,13 +1637,13 @@ const UI = {
     },
 
     updateLogging() {
-        WebUtil.init_logging(UI.getSetting('logging'));
+        WebUtil.initLogging(UI.getSetting('logging'));
     },
 
     updateDesktopName(e) {
         UI.desktopName = e.detail.name;
         // Display the desktop name in the document title
-        document.title = e.detail.name + " - noVNC";
+        document.title = e.detail.name + " - " + PAGE_TITLE;
     },
 
     bell(e) {
@@ -1646,7 +1679,7 @@ const UI = {
 };
 
 // Set up translations
-const LINGUAS = ["cs", "de", "el", "es", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
+const LINGUAS = ["cs", "de", "el", "es", "ja", "ko", "nl", "pl", "ru", "sv", "tr", "zh_CN", "zh_TW"];
 l10n.setup(LINGUAS);
 if (l10n.language === "en" || l10n.dictionary !== undefined) {
     UI.prime();
diff --git a/systemvm/agent/noVNC/app/webutil.js b/systemvm/agent/noVNC/app/webutil.js
index 98e1d9e..568f0e2 100644
--- a/systemvm/agent/noVNC/app/webutil.js
+++ b/systemvm/agent/noVNC/app/webutil.js
@@ -1,21 +1,21 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
  */
 
-import { init_logging as main_init_logging } from '../core/util/logging.js';
+import { initLogging as mainInitLogging } from '../core/util/logging.js';
 
 // init log level reading the logging HTTP param
-export function init_logging(level) {
+export function initLogging(level) {
     "use strict";
     if (typeof level !== "undefined") {
-        main_init_logging(level);
+        mainInitLogging(level);
     } else {
         const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
-        main_init_logging(param || undefined);
+        mainInitLogging(param || undefined);
     }
 }
 
@@ -115,13 +115,8 @@ export function eraseCookie(name) {
 let settings = {};
 
 export function initSettings() {
-    if (!window.chrome || !window.chrome.storage) {
-        settings = {};
-        return Promise.resolve();
-    }
-
-    return new Promise(resolve => window.chrome.storage.sync.get(resolve))
-        .then((cfg) => { settings = cfg; });
+    settings = {};
+    return Promise.resolve();
 }
 
 // Update the settings cache, but do not write to permanent storage
@@ -134,22 +129,13 @@ export function writeSetting(name, value) {
     "use strict";
     if (settings[name] === value) return;
     settings[name] = value;
-    if (window.chrome && window.chrome.storage) {
-        window.chrome.storage.sync.set(settings);
-    } else {
-        localStorage.setItem(name, value);
-    }
 }
 
 export function readSetting(name, defaultValue) {
     "use strict";
     let value;
-    if ((name in settings) || (window.chrome && window.chrome.storage)) {
-        value = settings[name];
-    } else {
-        value = localStorage.getItem(name);
-        settings[name] = value;
-    }
+    value = settings[name];
+
     if (typeof value === "undefined") {
         value = null;
     }
@@ -169,11 +155,6 @@ export function eraseSetting(name) {
     // between this delete and the next read, it could lead to an unexpected
     // value change.
     delete settings[name];
-    if (window.chrome && window.chrome.storage) {
-        window.chrome.storage.sync.remove(name);
-    } else {
-        localStorage.removeItem(name);
-    }
 }
 
 export function injectParamIfMissing(path, param, value) {
@@ -184,7 +165,7 @@ export function injectParamIfMissing(path, param, value) {
     const elem = document.createElement('a');
     elem.href = path;
 
-    const param_eq = encodeURIComponent(param) + "=";
+    const paramEq = encodeURIComponent(param) + "=";
     let query;
     if (elem.search) {
         query = elem.search.slice(1).split('&');
@@ -192,8 +173,8 @@ export function injectParamIfMissing(path, param, value) {
         query = [];
     }
 
-    if (!query.some(v => v.startsWith(param_eq))) {
-        query.push(param_eq + encodeURIComponent(value));
+    if (!query.some(v => v.startsWith(paramEq))) {
+        query.push(paramEq + encodeURIComponent(value));
         elem.search = "?" + query.join("&");
     }
 
diff --git a/systemvm/agent/noVNC/core/base64.js b/systemvm/agent/noVNC/core/base64.js
index 88e7454..db572c2 100644
--- a/systemvm/agent/noVNC/core/base64.js
+++ b/systemvm/agent/noVNC/core/base64.js
@@ -57,12 +57,12 @@ export default {
     /* eslint-enable comma-spacing */
 
     decode(data, offset = 0) {
-        let data_length = data.indexOf('=') - offset;
-        if (data_length < 0) { data_length = data.length - offset; }
+        let dataLength = data.indexOf('=') - offset;
+        if (dataLength < 0) { dataLength = data.length - offset; }
 
         /* Every four characters is 3 resulting numbers */
-        const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
-        const result = new Array(result_length);
+        const resultLength = (dataLength >> 2) * 3 + Math.floor((dataLength % 4) / 1.5);
+        const result = new Array(resultLength);
 
         // Convert one by one.
 
diff --git a/systemvm/agent/noVNC/core/decoders/copyrect.js b/systemvm/agent/noVNC/core/decoders/copyrect.js
index a78ded7..9e6391a 100644
--- a/systemvm/agent/noVNC/core/decoders/copyrect.js
+++ b/systemvm/agent/noVNC/core/decoders/copyrect.js
@@ -1,8 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -17,6 +15,11 @@ export default class CopyRectDecoder {
 
         let deltaX = sock.rQshift16();
         let deltaY = sock.rQshift16();
+
+        if ((width === 0) || (height === 0)) {
+            return true;
+        }
+
         display.copyImage(deltaX, deltaY, x, y, width, height);
 
         return true;
diff --git a/systemvm/agent/noVNC/core/decoders/hextile.js b/systemvm/agent/noVNC/core/decoders/hextile.js
index aa76d2f..ac21eff 100644
--- a/systemvm/agent/noVNC/core/decoders/hextile.js
+++ b/systemvm/agent/noVNC/core/decoders/hextile.js
@@ -1,8 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -15,14 +13,15 @@ export default class HextileDecoder {
     constructor() {
         this._tiles = 0;
         this._lastsubencoding = 0;
+        this._tileBuffer = new Uint8Array(16 * 16 * 4);
     }
 
     decodeRect(x, y, width, height, sock, display, depth) {
         if (this._tiles === 0) {
-            this._tiles_x = Math.ceil(width / 16);
-            this._tiles_y = Math.ceil(height / 16);
-            this._total_tiles = this._tiles_x * this._tiles_y;
-            this._tiles = this._total_tiles;
+            this._tilesX = Math.ceil(width / 16);
+            this._tilesY = Math.ceil(height / 16);
+            this._totalTiles = this._tilesX * this._tilesY;
+            this._tiles = this._totalTiles;
         }
 
         while (this._tiles > 0) {
@@ -41,11 +40,11 @@ export default class HextileDecoder {
                             subencoding + ")");
             }
 
-            const curr_tile = this._total_tiles - this._tiles;
-            const tile_x = curr_tile % this._tiles_x;
-            const tile_y = Math.floor(curr_tile / this._tiles_x);
-            const tx = x + tile_x * 16;
-            const ty = y + tile_y * 16;
+            const currTile = this._totalTiles - this._tiles;
+            const tileX = currTile % this._tilesX;
+            const tileY = Math.floor(currTile / this._tilesX);
+            const tx = x + tileX * 16;
+            const ty = y + tileY * 16;
             const tw = Math.min(16, (x + width) - tx);
             const th = Math.min(16, (y + height) - ty);
 
@@ -89,6 +88,11 @@ export default class HextileDecoder {
                     display.fillRect(tx, ty, tw, th, this._background);
                 }
             } else if (subencoding & 0x01) {  // Raw
+                let pixels = tw * th;
+                // Max sure the image is fully opaque
+                for (let i = 0;i <  pixels;i++) {
+                    rQ[rQi + i * 4 + 3] = 255;
+                }
                 display.blitImage(tx, ty, tw, th, rQ, rQi);
                 rQi += bytes - 1;
             } else {
@@ -101,7 +105,7 @@ export default class HextileDecoder {
                     rQi += 4;
                 }
 
-                display.startTile(tx, ty, tw, th, this._background);
+                this._startTile(tx, ty, tw, th, this._background);
                 if (subencoding & 0x08) {  // AnySubrects
                     let subrects = rQ[rQi];
                     rQi++;
@@ -124,10 +128,10 @@ export default class HextileDecoder {
                         const sw = (wh >> 4) + 1;
                         const sh = (wh & 0x0f) + 1;
 
-                        display.subTile(sx, sy, sw, sh, color);
+                        this._subTile(sx, sy, sw, sh, color);
                     }
                 }
-                display.finishTile();
+                this._finishTile(display);
             }
             sock.rQi = rQi;
             this._lastsubencoding = subencoding;
@@ -136,4 +140,52 @@ export default class HextileDecoder {
 
         return true;
     }
+
+    // start updating a tile
+    _startTile(x, y, width, height, color) {
+        this._tileX = x;
+        this._tileY = y;
+        this._tileW = width;
+        this._tileH = height;
+
+        const red = color[0];
+        const green = color[1];
+        const blue = color[2];
+
+        const data = this._tileBuffer;
+        for (let i = 0; i < width * height * 4; i += 4) {
+            data[i]     = red;
+            data[i + 1] = green;
+            data[i + 2] = blue;
+            data[i + 3] = 255;
+        }
+    }
+
+    // update sub-rectangle of the current tile
+    _subTile(x, y, w, h, color) {
+        const red = color[0];
+        const green = color[1];
+        const blue = color[2];
+        const xend = x + w;
+        const yend = y + h;
+
+        const data = this._tileBuffer;
+        const width = this._tileW;
+        for (let j = y; j < yend; j++) {
+            for (let i = x; i < xend; i++) {
+                const p = (i + (j * width)) * 4;
+                data[p]     = red;
+                data[p + 1] = green;
+                data[p + 2] = blue;
+                data[p + 3] = 255;
+            }
+        }
+    }
+
+    // draw the current tile to the screen
+    _finishTile(display) {
+        display.blitImage(this._tileX, this._tileY,
+                          this._tileW, this._tileH,
+                          this._tileBuffer, 0);
+    }
 }
diff --git a/systemvm/agent/noVNC/core/decoders/raw.js b/systemvm/agent/noVNC/core/decoders/raw.js
index f676e0d..e8ea178 100644
--- a/systemvm/agent/noVNC/core/decoders/raw.js
+++ b/systemvm/agent/noVNC/core/decoders/raw.js
@@ -1,8 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -15,6 +13,10 @@ export default class RawDecoder {
     }
 
     decodeRect(x, y, width, height, sock, display, depth) {
+        if ((width === 0) || (height === 0)) {
+            return true;
+        }
+
         if (this._lines === 0) {
             this._lines = height;
         }
@@ -26,29 +28,35 @@ export default class RawDecoder {
             return false;
         }
 
-        const cur_y = y + (height - this._lines);
-        const curr_height = Math.min(this._lines,
-                                     Math.floor(sock.rQlen / bytesPerLine));
+        const curY = y + (height - this._lines);
+        const currHeight = Math.min(this._lines,
+                                    Math.floor(sock.rQlen / bytesPerLine));
+        const pixels = width * currHeight;
+
         let data = sock.rQ;
         let index = sock.rQi;
 
         // Convert data if needed
         if (depth == 8) {
-            const pixels = width * curr_height;
             const newdata = new Uint8Array(pixels * 4);
             for (let i = 0; i < pixels; i++) {
                 newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
                 newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
                 newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
-                newdata[i * 4 + 4] = 0;
+                newdata[i * 4 + 3] = 255;
             }
             data = newdata;
             index = 0;
         }
 
-        display.blitImage(x, cur_y, width, curr_height, data, index);
-        sock.rQskipBytes(curr_height * bytesPerLine);
-        this._lines -= curr_height;
+        // Max sure the image is fully opaque
+        for (let i = 0; i < pixels; i++) {
+            data[i * 4 + 3] = 255;
+        }
+
+        display.blitImage(x, curY, width, currHeight, data, index);
+        sock.rQskipBytes(currHeight * bytesPerLine);
+        this._lines -= currHeight;
         if (this._lines > 0) {
             return false;
         }
diff --git a/systemvm/agent/noVNC/core/decoders/rre.js b/systemvm/agent/noVNC/core/decoders/rre.js
index 57414a0..6219369 100644
--- a/systemvm/agent/noVNC/core/decoders/rre.js
+++ b/systemvm/agent/noVNC/core/decoders/rre.js
@@ -1,8 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
diff --git a/systemvm/agent/noVNC/core/decoders/tight.js b/systemvm/agent/noVNC/core/decoders/tight.js
index bcda04c..7952707 100644
--- a/systemvm/agent/noVNC/core/decoders/tight.js
+++ b/systemvm/agent/noVNC/core/decoders/tight.js
@@ -1,9 +1,7 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2019 The noVNC Authors
  * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -58,7 +56,7 @@ export default class TightDecoder {
         } else if (this._ctl === 0x0A) {
             ret = this._pngRect(x, y, width, height,
                                 sock, display, depth);
-        } else if ((this._ctl & 0x80) == 0) {
+        } else if ((this._ctl & 0x08) == 0) {
             ret = this._basicRect(this._ctl, x, y, width, height,
                                   sock, display, depth);
         } else {
@@ -82,7 +80,7 @@ export default class TightDecoder {
         const rQ = sock.rQ;
 
         display.fillRect(x, y, width, height,
-                         [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false);
+                         [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
         sock.rQskipBytes(3);
 
         return true;
@@ -94,7 +92,7 @@ export default class TightDecoder {
             return false;
         }
 
-        display.imageRect(x, y, "image/jpeg", data);
+        display.imageRect(x, y, width, height, "image/jpeg", data);
 
         return true;
     }
@@ -150,6 +148,10 @@ export default class TightDecoder {
         const uncompressedSize = width * height * 3;
         let data;
 
+        if (uncompressedSize === 0) {
+            return true;
+        }
+
         if (uncompressedSize < 12) {
             if (sock.rQwait("TIGHT", uncompressedSize)) {
                 return false;
@@ -162,13 +164,20 @@ export default class TightDecoder {
                 return false;
             }
 
-            data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
-            if (data.length != uncompressedSize) {
-                throw new Error("Incomplete zlib block");
-            }
+            this._zlibs[streamId].setInput(data);
+            data = this._zlibs[streamId].inflate(uncompressedSize);
+            this._zlibs[streamId].setInput(null);
+        }
+
+        let rgbx = new Uint8Array(width * height * 4);
+        for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
+            rgbx[i]     = data[j];
+            rgbx[i + 1] = data[j + 1];
+            rgbx[i + 2] = data[j + 2];
+            rgbx[i + 3] = 255;  // Alpha
         }
 
-        display.blitRgbImage(x, y, width, height, data, 0, false);
+        display.blitImage(x, y, width, height, rgbx, 0, false);
 
         return true;
     }
@@ -198,6 +207,10 @@ export default class TightDecoder {
 
         let data;
 
+        if (uncompressedSize === 0) {
+            return true;
+        }
+
         if (uncompressedSize < 12) {
             if (sock.rQwait("TIGHT", uncompressedSize)) {
                 return false;
@@ -210,10 +223,9 @@ export default class TightDecoder {
                 return false;
             }
 
-            data = this._zlibs[streamId].inflate(data, true, uncompressedSize);
-            if (data.length != uncompressedSize) {
-                throw new Error("Incomplete zlib block");
-            }
+            this._zlibs[streamId].setInput(data);
+            data = this._zlibs[streamId].inflate(uncompressedSize);
+            this._zlibs[streamId].setInput(null);
         }
 
         // Convert indexed (palette based) image data to RGB
@@ -241,7 +253,7 @@ export default class TightDecoder {
                 for (let 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]     = palette[sp];
                     dest[dp + 1] = palette[sp + 1];
                     dest[dp + 2] = palette[sp + 2];
                     dest[dp + 3] = 255;
@@ -251,14 +263,14 @@ export default class TightDecoder {
             for (let 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]     = palette[sp];
                 dest[dp + 1] = palette[sp + 1];
                 dest[dp + 2] = palette[sp + 2];
                 dest[dp + 3] = 255;
             }
         }
 
-        display.blitRgbxImage(x, y, width, height, dest, 0, false);
+        display.blitImage(x, y, width, height, dest, 0, false);
     }
 
     _paletteRect(x, y, width, height, data, palette, display) {
@@ -267,13 +279,13 @@ export default class TightDecoder {
         const total = width * height * 4;
         for (let i = 0, j = 0; i < total; i += 4, j++) {
             const sp = data[j] * 3;
-            dest[i] = palette[sp];
+            dest[i]     = palette[sp];
             dest[i + 1] = palette[sp + 1];
             dest[i + 2] = palette[sp + 2];
             dest[i + 3] = 255;
         }
 
-        display.blitRgbxImage(x, y, width, height, dest, 0, false);
+        display.blitImage(x, y, width, height, dest, 0, false);
     }
 
     _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
diff --git a/systemvm/agent/noVNC/core/decoders/tightpng.js b/systemvm/agent/noVNC/core/decoders/tightpng.js
index 7bbde3a..82f492d 100644
--- a/systemvm/agent/noVNC/core/decoders/tightpng.js
+++ b/systemvm/agent/noVNC/core/decoders/tightpng.js
@@ -1,8 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2012 Joel Martin
- * Copyright (C) 2018 Samuel Mannehed for Cendio AB
- * Copyright (C) 2018 Pierre Ossman for Cendio AB
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -18,7 +16,7 @@ export default class TightPNGDecoder extends TightDecoder {
             return false;
         }
 
-        display.imageRect(x, y, "image/png", data);
+        display.imageRect(x, y, width, height, "image/png", data);
 
         return true;
     }
diff --git a/systemvm/agent/noVNC/core/deflator.js b/systemvm/agent/noVNC/core/deflator.js
new file mode 100644
index 0000000..fe2a8f7
--- /dev/null
+++ b/systemvm/agent/noVNC/core/deflator.js
@@ -0,0 +1,85 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2020 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
+import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
+import ZStream from "../vendor/pako/lib/zlib/zstream.js";
+
+export default class Deflator {
+    constructor() {
+        this.strm = new ZStream();
+        this.chunkSize = 1024 * 10 * 10;
+        this.outputBuffer = new Uint8Array(this.chunkSize);
+        this.windowBits = 5;
+
+        deflateInit(this.strm, this.windowBits);
+    }
+
+    deflate(inData) {
+        /* eslint-disable camelcase */
+        this.strm.input = inData;
+        this.strm.avail_in = this.strm.input.length;
+        this.strm.next_in = 0;
+        this.strm.output = this.outputBuffer;
+        this.strm.avail_out = this.chunkSize;
+        this.strm.next_out = 0;
+        /* eslint-enable camelcase */
+
+        let lastRet = deflate(this.strm, Z_FULL_FLUSH);
+        let outData = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
+
+        if (lastRet < 0) {
+            throw new Error("zlib deflate failed");
+        }
+
+        if (this.strm.avail_in > 0) {
+            // Read chunks until done
+
+            let chunks = [outData];
+            let totalLen = outData.length;
+            do {
+                /* eslint-disable camelcase */
+                this.strm.output = new Uint8Array(this.chunkSize);
+                this.strm.next_out = 0;
+                this.strm.avail_out = this.chunkSize;
+                /* eslint-enable camelcase */
+
+                lastRet = deflate(this.strm, Z_FULL_FLUSH);
+
+                if (lastRet < 0) {
+                    throw new Error("zlib deflate failed");
+                }
+
+                let chunk = new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
+                totalLen += chunk.length;
+                chunks.push(chunk);
+            } while (this.strm.avail_in > 0);
+
+            // Combine chunks into a single data
+
+            let newData = new Uint8Array(totalLen);
+            let offset = 0;
+
+            for (let i = 0; i < chunks.length; i++) {
+                newData.set(chunks[i], offset);
+                offset += chunks[i].length;
+            }
+
+            outData = newData;
+        }
+
+        /* eslint-disable camelcase */
+        this.strm.input = null;
+        this.strm.avail_in = 0;
+        this.strm.next_in = 0;
+        /* eslint-enable camelcase */
+
+        return outData;
+    }
+
+}
diff --git a/systemvm/agent/noVNC/core/display.js b/systemvm/agent/noVNC/core/display.js
index 1528384..8eaa800 100644
--- a/systemvm/agent/noVNC/core/display.js
+++ b/systemvm/agent/noVNC/core/display.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -9,24 +9,20 @@
 import * as Log from './util/logging.js';
 import Base64 from "./base64.js";
 import { supportsImageMetadata } from './util/browser.js';
+import { toSigned32bit } from './util/int.js';
 
 export default class Display {
     constructor(target) {
         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._fbWidth = 0;
+        this._fbHeight = 0;
 
         this._prevDrawStyle = "";
-        this._tile = null;
-        this._tile16x16 = null;
-        this._tile_x = 0;
-        this._tile_y = 0;
 
         Log.Debug(">> Display.constructor");
 
@@ -60,21 +56,17 @@ export default class Display {
 
         Log.Debug("User Agent: " + navigator.userAgent);
 
-        this.clear();
-
         // Check canvas features
         if (!('createImageData' in this._drawCtx)) {
             throw new Error("Canvas does not support createImageData");
         }
 
-        this._tile16x16 = this._drawCtx.createImageData(16, 16);
         Log.Debug("<< Display.constructor");
 
         // ===== PROPERTIES =====
 
         this._scale = 1.0;
         this._clipViewport = false;
-        this.logo = null;
 
         // ===== EVENT HANDLERS =====
 
@@ -98,11 +90,11 @@ export default class Display {
     }
 
     get width() {
-        return this._fb_width;
+        return this._fbWidth;
     }
 
     get height() {
-        return this._fb_height;
+        return this._fbHeight;
     }
 
     // ===== PUBLIC METHODS =====
@@ -125,15 +117,15 @@ export default class Display {
         if (deltaX < 0 && vp.x + deltaX < 0) {
             deltaX = -vp.x;
         }
-        if (vx2 + deltaX >= this._fb_width) {
-            deltaX -= vx2 + deltaX - this._fb_width + 1;
+        if (vx2 + deltaX >= this._fbWidth) {
+            deltaX -= vx2 + deltaX - this._fbWidth + 1;
         }
 
         if (vp.y + deltaY < 0) {
             deltaY = -vp.y;
         }
-        if (vy2 + deltaY >= this._fb_height) {
-            deltaY -= (vy2 + deltaY - this._fb_height + 1);
+        if (vy2 + deltaY >= this._fbHeight) {
+            deltaY -= (vy2 + deltaY - this._fbHeight + 1);
         }
 
         if (deltaX === 0 && deltaY === 0) {
@@ -156,18 +148,18 @@ export default class Display {
             typeof(height) === "undefined") {
 
             Log.Debug("Setting viewport to full display region");
-            width = this._fb_width;
-            height = this._fb_height;
+            width = this._fbWidth;
+            height = this._fbHeight;
         }
 
         width = Math.floor(width);
         height = Math.floor(height);
 
-        if (width > this._fb_width) {
-            width = this._fb_width;
+        if (width > this._fbWidth) {
+            width = this._fbWidth;
         }
-        if (height > this._fb_height) {
-            height = this._fb_height;
+        if (height > this._fbHeight) {
+            height = this._fbHeight;
         }
 
         const vp = this._viewportLoc;
@@ -194,21 +186,21 @@ export default class Display {
         if (this._scale === 0) {
             return 0;
         }
-        return x / this._scale + this._viewportLoc.x;
+        return toSigned32bit(x / this._scale + this._viewportLoc.x);
     }
 
     absY(y) {
         if (this._scale === 0) {
             return 0;
         }
-        return y / this._scale + this._viewportLoc.y;
+        return toSigned32bit(y / this._scale + this._viewportLoc.y);
     }
 
     resize(width, height) {
         this._prevDrawStyle = "";
 
-        this._fb_width = width;
-        this._fb_height = height;
+        this._fbWidth = width;
+        this._fbHeight = height;
 
         const canvas = this._backbuffer;
         if (canvas.width !== width || canvas.height !== height) {
@@ -256,9 +248,9 @@ export default class Display {
 
     // Update the visible canvas with the contents of the
     // rendering canvas
-    flip(from_queue) {
-        if (this._renderQ.length !== 0 && !from_queue) {
-            this._renderQ_push({
+    flip(fromQueue) {
+        if (this._renderQ.length !== 0 && !fromQueue) {
+            this._renderQPush({
                 'type': 'flip'
             });
         } else {
@@ -302,17 +294,6 @@ export default class Display {
         }
     }
 
-    clear() {
-        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() {
         return this._renderQ.length > 0;
     }
@@ -325,9 +306,9 @@ export default class Display {
         }
     }
 
-    fillRect(x, y, width, height, color, from_queue) {
-        if (this._renderQ.length !== 0 && !from_queue) {
-            this._renderQ_push({
+    fillRect(x, y, width, height, color, fromQueue) {
+        if (this._renderQ.length !== 0 && !fromQueue) {
+            this._renderQPush({
                 'type': 'fill',
                 'x': x,
                 'y': y,
@@ -342,14 +323,14 @@ export default class Display {
         }
     }
 
-    copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
-        if (this._renderQ.length !== 0 && !from_queue) {
-            this._renderQ_push({
+    copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
+        if (this._renderQ.length !== 0 && !fromQueue) {
+            this._renderQPush({
                 'type': 'copy',
-                'old_x': old_x,
-                'old_y': old_y,
-                'x': new_x,
-                'y': new_y,
+                'oldX': oldX,
+                'oldY': oldY,
+                'x': newX,
+                'y': newY,
                 'width': w,
                 'height': h,
             });
@@ -367,131 +348,60 @@ export default class Display {
             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);
+                                    oldX, oldY, w, h,
+                                    newX, newY, w, h);
+            this._damage(newX, newY, w, h);
         }
     }
 
-    imageRect(x, y, mime, arr) {
+    imageRect(x, y, width, height, mime, arr) {
+        /* The internal logic cannot handle empty images, so bail early */
+        if ((width === 0) || (height === 0)) {
+            return;
+        }
+
         const img = new Image();
         img.src = "data: " + mime + ";base64," + Base64.encode(arr);
-        this._renderQ_push({
+
+        this._renderQPush({
             'type': 'img',
             'img': img,
             'x': x,
-            'y': y
+            'y': y,
+            'width': width,
+            'height': height
         });
     }
 
-    // start updating a tile
-    startTile(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);
-        }
-
-        const red = color[2];
-        const green = color[1];
-        const blue = color[0];
-
-        const data = this._tile.data;
-        for (let i = 0; i < width * height * 4; i += 4) {
-            data[i] = red;
-            data[i + 1] = green;
-            data[i + 2] = blue;
-            data[i + 3] = 255;
-        }
-    }
-
-    // update sub-rectangle of the current tile
-    subTile(x, y, w, h, color) {
-        const red = color[2];
-        const green = color[1];
-        const blue = color[0];
-        const xend = x + w;
-        const yend = y + h;
-
-        const data = this._tile.data;
-        const width = this._tile.width;
-        for (let j = y; j < yend; j++) {
-            for (let i = x; i < xend; i++) {
-                const p = (i + (j * width)) * 4;
-                data[p] = red;
-                data[p + 1] = green;
-                data[p + 2] = blue;
-                data[p + 3] = 255;
-            }
-        }
-    }
-
-    // draw the current tile to the screen
-    finishTile() {
-        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);
-    }
-
-    blitImage(x, y, width, height, arr, offset, from_queue) {
-        if (this._renderQ.length !== 0 && !from_queue) {
+    blitImage(x, y, width, height, arr, offset, fromQueue) {
+        if (this._renderQ.length !== 0 && !fromQueue) {
             // 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
-            const new_arr = new Uint8Array(width * height * 4);
-            new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
-            this._renderQ_push({
+            const newArr = new Uint8Array(width * height * 4);
+            newArr.set(new Uint8Array(arr.buffer, 0, newArr.length));
+            this._renderQPush({
                 'type': 'blit',
-                'data': new_arr,
+                'data': newArr,
                 'x': x,
                 'y': y,
                 'width': width,
                 'height': height,
             });
         } else {
-            this._bgrxImageData(x, y, width, height, arr, offset);
-        }
-    }
-
-    blitRgbImage(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
-            const 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(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
-            const 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);
+            // NB(directxman12): arr must be an Type Array view
+            let data = new Uint8ClampedArray(arr.buffer,
+                                             arr.byteOffset + offset,
+                                             width * height * 4);
+            let img;
+            if (supportsImageMetadata) {
+                img = new ImageData(data, width, height);
+            } else {
+                img = this._drawCtx.createImageData(width, height);
+                img.data.set(data);
+            }
+            this._drawCtx.putImageData(img, x, y);
+            this._damage(x, y, width, height);
         }
     }
 
@@ -543,69 +453,30 @@ export default class Display {
     }
 
     _setFillColor(color) {
-        const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
+        const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
         if (newStyle !== this._prevDrawStyle) {
             this._drawCtx.fillStyle = newStyle;
             this._prevDrawStyle = newStyle;
         }
     }
 
-    _rgbImageData(x, y, width, height, arr, offset) {
-        const img = this._drawCtx.createImageData(width, height);
-        const data = img.data;
-        for (let 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(x, y, width, height, arr, offset) {
-        const img = this._drawCtx.createImageData(width, height);
-        const data = img.data;
-        for (let 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(x, y, width, height, arr, offset) {
-        // NB(directxman12): arr must be an Type Array view
-        let img;
-        if (supportsImageMetadata) {
-            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(action) {
+    _renderQPush(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();
+            this._scanRenderQ();
         }
     }
 
-    _resume_renderQ() {
+    _resumeRenderQ() {
         // "this" is the object that is ready, not the
         // display object
-        this.removeEventListener('load', this._noVNC_display._resume_renderQ);
-        this._noVNC_display._scan_renderQ();
+        this.removeEventListener('load', this._noVNCDisplay._resumeRenderQ);
+        this._noVNCDisplay._scanRenderQ();
     }
 
-    _scan_renderQ() {
+    _scanRenderQ() {
         let ready = true;
         while (ready && this._renderQ.length > 0) {
             const a = this._renderQ[0];
@@ -614,7 +485,7 @@ export default class Display {
                     this.flip(true);
                     break;
                 case 'copy':
-                    this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
+                    this.copyImage(a.oldX, a.oldY, 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);
@@ -622,18 +493,19 @@ export default class Display {
                 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) {
+                    /* IE tends to set "complete" prematurely, so check dimensions */
+                    if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) {
+                        if (a.img.width !== a.width || a.img.height !== a.height) {
+                            Log.Error("Decoded image has incorrect dimensions. Got " +
+                                      a.img.width + "x" + a.img.height + ". Expected " +
+                                      a.width + "x" + a.height + ".");
+                            return;
+                        }
                         this.drawImage(a.img, a.x, a.y);
                     } else {
-                        a.img._noVNC_display = this;
-                        a.img.addEventListener('load', this._resume_renderQ);
+                        a.img._noVNCDisplay = this;
+                        a.img.addEventListener('load', this._resumeRenderQ);
                         // We need to wait for this image to 'load'
                         // to keep things in-order
                         ready = false;
diff --git a/systemvm/agent/noVNC/core/encodings.js b/systemvm/agent/noVNC/core/encodings.js
index 9fd38d5..51c0992 100644
--- a/systemvm/agent/noVNC/core/encodings.js
+++ b/systemvm/agent/noVNC/core/encodings.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -20,12 +20,15 @@ export const encodings = {
     pseudoEncodingLastRect: -224,
     pseudoEncodingCursor: -239,
     pseudoEncodingQEMUExtendedKeyEvent: -258,
+    pseudoEncodingDesktopName: -307,
     pseudoEncodingExtendedDesktopSize: -308,
     pseudoEncodingXvp: -309,
     pseudoEncodingFence: -312,
     pseudoEncodingContinuousUpdates: -313,
     pseudoEncodingCompressLevel9: -247,
     pseudoEncodingCompressLevel0: -256,
+    pseudoEncodingVMwareCursor: 0x574d5664,
+    pseudoEncodingExtendedClipboard: 0xc0a1e5ce
 };
 
 export function encodingName(num) {
diff --git a/systemvm/agent/noVNC/core/inflator.js b/systemvm/agent/noVNC/core/inflator.js
index 0eab8fe..4b33760 100644
--- a/systemvm/agent/noVNC/core/inflator.js
+++ b/systemvm/agent/noVNC/core/inflator.js
@@ -1,3 +1,11 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2020 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
 import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
 import ZStream from "../vendor/pako/lib/zlib/zstream.js";
 
@@ -11,12 +19,22 @@ export default class Inflate {
         inflateInit(this.strm, this.windowBits);
     }
 
-    inflate(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;
+    setInput(data) {
+        if (!data) {
+            //FIXME: flush remaining data.
+            /* eslint-disable camelcase */
+            this.strm.input = null;
+            this.strm.avail_in = 0;
+            this.strm.next_in = 0;
+        } else {
+            this.strm.input = data;
+            this.strm.avail_in = this.strm.input.length;
+            this.strm.next_in = 0;
+            /* eslint-enable camelcase */
+        }
+    }
 
+    inflate(expected) {
         // 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)
@@ -25,9 +43,19 @@ export default class Inflate {
             this.strm.output = new Uint8Array(this.chunkSize);
         }
 
-        this.strm.avail_out = this.chunkSize;
+        /* eslint-disable camelcase */
+        this.strm.next_out = 0;
+        this.strm.avail_out = expected;
+        /* eslint-enable camelcase */
+
+        let ret = inflate(this.strm, 0); // Flush argument not used.
+        if (ret < 0) {
+            throw new Error("zlib inflate failed");
+        }
 
-        inflate(this.strm, flush);
+        if (this.strm.next_out != expected) {
+            throw new Error("Incomplete zlib block");
+        }
 
         return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
     }
diff --git a/systemvm/agent/noVNC/core/input/domkeytable.js b/systemvm/agent/noVNC/core/input/domkeytable.js
index 60ae3f9..b84ad45 100644
--- a/systemvm/agent/noVNC/core/input/domkeytable.js
+++ b/systemvm/agent/noVNC/core/input/domkeytable.js
@@ -43,12 +43,10 @@ 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
 
@@ -72,6 +70,9 @@ addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
 // 2.5. Editing Keys
 
 addStandard("Backspace", KeyTable.XK_BackSpace);
+// Browsers send "Clear" for the numpad 5 without NumLock because
+// Windows uses VK_Clear for that key. But Unix expects KP_Begin for
+// that scenario.
 addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
 addStandard("Copy", KeyTable.XF86XK_Copy);
 // - CrSel
@@ -194,7 +195,8 @@ addStandard("F35", KeyTable.XK_F35);
 addStandard("Close", KeyTable.XF86XK_Close);
 addStandard("MailForward", KeyTable.XF86XK_MailForward);
 addStandard("MailReply", KeyTable.XF86XK_Reply);
-addStandard("MainSend", KeyTable.XF86XK_Send);
+addStandard("MailSend", KeyTable.XF86XK_Send);
+// - MediaClose
 addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
 addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
 addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
@@ -218,11 +220,9 @@ addStandard("SpellCheck", KeyTable.XF86XK_Spell);
 
 // - AudioBalanceLeft
 // - AudioBalanceRight
-// - AudioBassDown
 // - AudioBassBoostDown
 // - AudioBassBoostToggle
 // - AudioBassBoostUp
-// - AudioBassUp
 // - AudioFaderFront
 // - AudioFaderRear
 // - AudioSurroundModeNext
@@ -243,12 +243,12 @@ addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
 
 // 2.14. Application Keys
 
-addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
+addStandard("LaunchApplication1", KeyTable.XF86XK_MyComputer);
+addStandard("LaunchApplication2", 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);
diff --git a/systemvm/agent/noVNC/core/input/gesturehandler.js b/systemvm/agent/noVNC/core/input/gesturehandler.js
new file mode 100644
index 0000000..6fa72d2
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/gesturehandler.js
@@ -0,0 +1,567 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2020 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ *
+ */
+
+const GH_NOGESTURE = 0;
+const GH_ONETAP    = 1;
+const GH_TWOTAP    = 2;
+const GH_THREETAP  = 4;
+const GH_DRAG      = 8;
+const GH_LONGPRESS = 16;
+const GH_TWODRAG   = 32;
+const GH_PINCH     = 64;
+
+const GH_INITSTATE = 127;
+
+const GH_MOVE_THRESHOLD = 50;
+const GH_ANGLE_THRESHOLD = 90; // Degrees
+
+// Timeout when waiting for gestures (ms)
+const GH_MULTITOUCH_TIMEOUT = 250;
+
+// Maximum time between press and release for a tap (ms)
+const GH_TAP_TIMEOUT = 1000;
+
+// Timeout when waiting for longpress (ms)
+const GH_LONGPRESS_TIMEOUT = 1000;
+
+// Timeout when waiting to decide between PINCH and TWODRAG (ms)
+const GH_TWOTOUCH_TIMEOUT = 50;
+
+export default class GestureHandler {
+    constructor() {
+        this._target = null;
+
+        this._state = GH_INITSTATE;
+
+        this._tracked = [];
+        this._ignored = [];
+
+        this._waitingRelease = false;
+        this._releaseStart = 0.0;
+
+        this._longpressTimeoutId = null;
+        this._twoTouchTimeoutId = null;
+
+        this._boundEventHandler = this._eventHandler.bind(this);
+    }
+
+    attach(target) {
+        this.detach();
+
+        this._target = target;
+        this._target.addEventListener('touchstart',
+                                      this._boundEventHandler);
+        this._target.addEventListener('touchmove',
+                                      this._boundEventHandler);
+        this._target.addEventListener('touchend',
+                                      this._boundEventHandler);
+        this._target.addEventListener('touchcancel',
+                                      this._boundEventHandler);
+    }
+
+    detach() {
+        if (!this._target) {
+            return;
+        }
+
+        this._stopLongpressTimeout();
+        this._stopTwoTouchTimeout();
+
+        this._target.removeEventListener('touchstart',
+                                         this._boundEventHandler);
+        this._target.removeEventListener('touchmove',
+                                         this._boundEventHandler);
+        this._target.removeEventListener('touchend',
+                                         this._boundEventHandler);
+        this._target.removeEventListener('touchcancel',
+                                         this._boundEventHandler);
+        this._target = null;
+    }
+
+    _eventHandler(e) {
+        let fn;
+
+        e.stopPropagation();
+        e.preventDefault();
+
+        switch (e.type) {
+            case 'touchstart':
+                fn = this._touchStart;
+                break;
+            case 'touchmove':
+                fn = this._touchMove;
+                break;
+            case 'touchend':
+            case 'touchcancel':
+                fn = this._touchEnd;
+                break;
+        }
+
+        for (let i = 0; i < e.changedTouches.length; i++) {
+            let touch = e.changedTouches[i];
+            fn.call(this, touch.identifier, touch.clientX, touch.clientY);
+        }
+    }
+
+    _touchStart(id, x, y) {
+        // Ignore any new touches if there is already an active gesture,
+        // or we're in a cleanup state
+        if (this._hasDetectedGesture() || (this._state === GH_NOGESTURE)) {
+            this._ignored.push(id);
+            return;
+        }
+
+        // Did it take too long between touches that we should no longer
+        // consider this a single gesture?
+        if ((this._tracked.length > 0) &&
+            ((Date.now() - this._tracked[0].started) > GH_MULTITOUCH_TIMEOUT)) {
+            this._state = GH_NOGESTURE;
+            this._ignored.push(id);
+            return;
+        }
+
+        // If we're waiting for fingers to release then we should no longer
+        // recognize new touches
+        if (this._waitingRelease) {
+            this._state = GH_NOGESTURE;
+            this._ignored.push(id);
+            return;
+        }
+
+        this._tracked.push({
+            id: id,
+            started: Date.now(),
+            active: true,
+            firstX: x,
+            firstY: y,
+            lastX: x,
+            lastY: y,
+            angle: 0
+        });
+
+        switch (this._tracked.length) {
+            case 1:
+                this._startLongpressTimeout();
+                break;
+
+            case 2:
+                this._state &= ~(GH_ONETAP | GH_DRAG | GH_LONGPRESS);
+                this._stopLongpressTimeout();
+                break;
+
+            case 3:
+                this._state &= ~(GH_TWOTAP | GH_TWODRAG | GH_PINCH);
+                break;
+
+            default:
+                this._state = GH_NOGESTURE;
+        }
+    }
+
+    _touchMove(id, x, y) {
+        let touch = this._tracked.find(t => t.id === id);
+
+        // If this is an update for a touch we're not tracking, ignore it
+        if (touch === undefined) {
+            return;
+        }
+
+        // Update the touches last position with the event coordinates
+        touch.lastX = x;
+        touch.lastY = y;
+
+        let deltaX = x - touch.firstX;
+        let deltaY = y - touch.firstY;
+
+        // Update angle when the touch has moved
+        if ((touch.firstX !== touch.lastX) ||
+            (touch.firstY !== touch.lastY)) {
+            touch.angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI;
+        }
+
+        if (!this._hasDetectedGesture()) {
+            // Ignore moves smaller than the minimum threshold
+            if (Math.hypot(deltaX, deltaY) < GH_MOVE_THRESHOLD) {
+                return;
+            }
+
+            // Can't be a tap or long press as we've seen movement
+            this._state &= ~(GH_ONETAP | GH_TWOTAP | GH_THREETAP | GH_LONGPRESS);
+            this._stopLongpressTimeout();
+
+            if (this._tracked.length !== 1) {
+                this._state &= ~(GH_DRAG);
+            }
+            if (this._tracked.length !== 2) {
+                this._state &= ~(GH_TWODRAG | GH_PINCH);
+            }
+
+            // We need to figure out which of our different two touch gestures
+            // this might be
+            if (this._tracked.length === 2) {
+
+                // The other touch is the one where the id doesn't match
+                let prevTouch = this._tracked.find(t => t.id !== id);
+
+                // How far the previous touch point has moved since start
+                let prevDeltaMove = Math.hypot(prevTouch.firstX - prevTouch.lastX,
+                                               prevTouch.firstY - prevTouch.lastY);
+
+                // We know that the current touch moved far enough,
+                // but unless both touches moved further than their
+                // threshold we don't want to disqualify any gestures
+                if (prevDeltaMove > GH_MOVE_THRESHOLD) {
+
+                    // The angle difference between the direction of the touch points
+                    let deltaAngle = Math.abs(touch.angle - prevTouch.angle);
+                    deltaAngle = Math.abs(((deltaAngle + 180) % 360) - 180);
+
+                    // PINCH or TWODRAG can be eliminated depending on the angle
+                    if (deltaAngle > GH_ANGLE_THRESHOLD) {
+                        this._state &= ~GH_TWODRAG;
+                    } else {
+                        this._state &= ~GH_PINCH;
+                    }
+
+                    if (this._isTwoTouchTimeoutRunning()) {
+                        this._stopTwoTouchTimeout();
+                    }
+                } else if (!this._isTwoTouchTimeoutRunning()) {
+                    // We can't determine the gesture right now, let's
+                    // wait and see if more events are on their way
+                    this._startTwoTouchTimeout();
+                }
+            }
+
+            if (!this._hasDetectedGesture()) {
+                return;
+            }
+
+            this._pushEvent('gesturestart');
+        }
+
+        this._pushEvent('gesturemove');
+    }
+
+    _touchEnd(id, x, y) {
+        // Check if this is an ignored touch
+        if (this._ignored.indexOf(id) !== -1) {
+            // Remove this touch from ignored
+            this._ignored.splice(this._ignored.indexOf(id), 1);
+
+            // And reset the state if there are no more touches
+            if ((this._ignored.length === 0) &&
+                (this._tracked.length === 0)) {
+                this._state = GH_INITSTATE;
+                this._waitingRelease = false;
+            }
+            return;
+        }
+
+        // We got a touchend before the timer triggered,
+        // this cannot result in a gesture anymore.
+        if (!this._hasDetectedGesture() &&
+            this._isTwoTouchTimeoutRunning()) {
+            this._stopTwoTouchTimeout();
+            this._state = GH_NOGESTURE;
+        }
+
+        // Some gestures don't trigger until a touch is released
+        if (!this._hasDetectedGesture()) {
+            // Can't be a gesture that relies on movement
+            this._state &= ~(GH_DRAG | GH_TWODRAG | GH_PINCH);
+            // Or something that relies on more time
+            this._state &= ~GH_LONGPRESS;
+            this._stopLongpressTimeout();
+
+            if (!this._waitingRelease) {
+                this._releaseStart = Date.now();
+                this._waitingRelease = true;
+
+                // Can't be a tap that requires more touches than we current have
+                switch (this._tracked.length) {
+                    case 1:
+                        this._state &= ~(GH_TWOTAP | GH_THREETAP);
+                        break;
+
+                    case 2:
+                        this._state &= ~(GH_ONETAP | GH_THREETAP);
+                        break;
+                }
+            }
+        }
+
+        // Waiting for all touches to release? (i.e. some tap)
+        if (this._waitingRelease) {
+            // Were all touches released at roughly the same time?
+            if ((Date.now() - this._releaseStart) > GH_MULTITOUCH_TIMEOUT) {
+                this._state = GH_NOGESTURE;
+            }
+
+            // Did too long time pass between press and release?
+            if (this._tracked.some(t => (Date.now() - t.started) > GH_TAP_TIMEOUT)) {
+                this._state = GH_NOGESTURE;
+            }
+
+            let touch = this._tracked.find(t => t.id === id);
+            touch.active = false;
+
+            // Are we still waiting for more releases?
+            if (this._hasDetectedGesture()) {
+                this._pushEvent('gesturestart');
+            } else {
+                // Have we reached a dead end?
+                if (this._state !== GH_NOGESTURE) {
+                    return;
+                }
+            }
+        }
+
+        if (this._hasDetectedGesture()) {
+            this._pushEvent('gestureend');
+        }
+
+        // Ignore any remaining touches until they are ended
+        for (let i = 0; i < this._tracked.length; i++) {
+            if (this._tracked[i].active) {
+                this._ignored.push(this._tracked[i].id);
+            }
+        }
+        this._tracked = [];
+
+        this._state = GH_NOGESTURE;
+
+        // Remove this touch from ignored if it's in there
+        if (this._ignored.indexOf(id) !== -1) {
+            this._ignored.splice(this._ignored.indexOf(id), 1);
+        }
+
+        // We reset the state if ignored is empty
+        if ((this._ignored.length === 0)) {
+            this._state = GH_INITSTATE;
+            this._waitingRelease = false;
+        }
+    }
+
+    _hasDetectedGesture() {
+        if (this._state === GH_NOGESTURE) {
+            return false;
+        }
+        // Check to see if the bitmask value is a power of 2
+        // (i.e. only one bit set). If it is, we have a state.
+        if (this._state & (this._state - 1)) {
+            return false;
+        }
+
+        // For taps we also need to have all touches released
+        // before we've fully detected the gesture
+        if (this._state & (GH_ONETAP | GH_TWOTAP | GH_THREETAP)) {
+            if (this._tracked.some(t => t.active)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    _startLongpressTimeout() {
+        this._stopLongpressTimeout();
+        this._longpressTimeoutId = setTimeout(() => this._longpressTimeout(),
+                                              GH_LONGPRESS_TIMEOUT);
+    }
+
+    _stopLongpressTimeout() {
+        clearTimeout(this._longpressTimeoutId);
+        this._longpressTimeoutId = null;
+    }
+
+    _longpressTimeout() {
+        if (this._hasDetectedGesture()) {
+            throw new Error("A longpress gesture failed, conflict with a different gesture");
+        }
+
+        this._state = GH_LONGPRESS;
+        this._pushEvent('gesturestart');
+    }
+
+    _startTwoTouchTimeout() {
+        this._stopTwoTouchTimeout();
+        this._twoTouchTimeoutId = setTimeout(() => this._twoTouchTimeout(),
+                                             GH_TWOTOUCH_TIMEOUT);
+    }
+
+    _stopTwoTouchTimeout() {
+        clearTimeout(this._twoTouchTimeoutId);
+        this._twoTouchTimeoutId = null;
+    }
+
+    _isTwoTouchTimeoutRunning() {
+        return this._twoTouchTimeoutId !== null;
+    }
+
+    _twoTouchTimeout() {
+        if (this._tracked.length === 0) {
+            throw new Error("A pinch or two drag gesture failed, no tracked touches");
+        }
+
+        // How far each touch point has moved since start
+        let avgM = this._getAverageMovement();
+        let avgMoveH = Math.abs(avgM.x);
+        let avgMoveV = Math.abs(avgM.y);
+
+        // The difference in the distance between where
+        // the touch points started and where they are now
+        let avgD = this._getAverageDistance();
+        let deltaTouchDistance = Math.abs(Math.hypot(avgD.first.x, avgD.first.y) -
+                                          Math.hypot(avgD.last.x, avgD.last.y));
+
+        if ((avgMoveV < deltaTouchDistance) &&
+            (avgMoveH < deltaTouchDistance)) {
+            this._state = GH_PINCH;
+        } else {
+            this._state = GH_TWODRAG;
+        }
+
+        this._pushEvent('gesturestart');
+        this._pushEvent('gesturemove');
+    }
+
+    _pushEvent(type) {
+        let detail = { type: this._stateToGesture(this._state) };
+
+        // For most gesture events the current (average) position is the
+        // most useful
+        let avg = this._getPosition();
+        let pos = avg.last;
+
+        // However we have a slight distance to detect gestures, so for the
+        // first gesture event we want to use the first positions we saw
+        if (type === 'gesturestart') {
+            pos = avg.first;
+        }
+
+        // For these gestures, we always want the event coordinates
+        // to be where the gesture began, not the current touch location.
+        switch (this._state) {
+            case GH_TWODRAG:
+            case GH_PINCH:
+                pos = avg.first;
+                break;
+        }
+
+        detail['clientX'] = pos.x;
+        detail['clientY'] = pos.y;
+
+        // FIXME: other coordinates?
+
+        // Some gestures also have a magnitude
+        if (this._state === GH_PINCH) {
+            let distance = this._getAverageDistance();
+            if (type === 'gesturestart') {
+                detail['magnitudeX'] = distance.first.x;
+                detail['magnitudeY'] = distance.first.y;
+            } else {
+                detail['magnitudeX'] = distance.last.x;
+                detail['magnitudeY'] = distance.last.y;
+            }
+        } else if (this._state === GH_TWODRAG) {
+            if (type === 'gesturestart') {
+                detail['magnitudeX'] = 0.0;
+                detail['magnitudeY'] = 0.0;
+            } else {
+                let movement = this._getAverageMovement();
+                detail['magnitudeX'] = movement.x;
+                detail['magnitudeY'] = movement.y;
+            }
+        }
+
+        let gev = new CustomEvent(type, { detail: detail });
+        this._target.dispatchEvent(gev);
+    }
+
+    _stateToGesture(state) {
+        switch (state) {
+            case GH_ONETAP:
+                return 'onetap';
+            case GH_TWOTAP:
+                return 'twotap';
+            case GH_THREETAP:
+                return 'threetap';
+            case GH_DRAG:
+                return 'drag';
+            case GH_LONGPRESS:
+                return 'longpress';
+            case GH_TWODRAG:
+                return 'twodrag';
+            case GH_PINCH:
+                return 'pinch';
+        }
+
+        throw new Error("Unknown gesture state: " + state);
+    }
+
+    _getPosition() {
+        if (this._tracked.length === 0) {
+            throw new Error("Failed to get gesture position, no tracked touches");
+        }
+
+        let size = this._tracked.length;
+        let fx = 0, fy = 0, lx = 0, ly = 0;
+
+        for (let i = 0; i < this._tracked.length; i++) {
+            fx += this._tracked[i].firstX;
+            fy += this._tracked[i].firstY;
+            lx += this._tracked[i].lastX;
+            ly += this._tracked[i].lastY;
+        }
+
+        return { first: { x: fx / size,
+                          y: fy / size },
+                 last: { x: lx / size,
+                         y: ly / size } };
+    }
+
+    _getAverageMovement() {
+        if (this._tracked.length === 0) {
+            throw new Error("Failed to get gesture movement, no tracked touches");
+        }
+
+        let totalH, totalV;
+        totalH = totalV = 0;
+        let size = this._tracked.length;
+
+        for (let i = 0; i < this._tracked.length; i++) {
+            totalH += this._tracked[i].lastX - this._tracked[i].firstX;
+            totalV += this._tracked[i].lastY - this._tracked[i].firstY;
+        }
+
+        return { x: totalH / size,
+                 y: totalV / size };
+    }
+
+    _getAverageDistance() {
+        if (this._tracked.length === 0) {
+            throw new Error("Failed to get gesture distance, no tracked touches");
+        }
+
+        // Distance between the first and last tracked touches
+
+        let first = this._tracked[0];
+        let last = this._tracked[this._tracked.length - 1];
+
+        let fdx = Math.abs(last.firstX - first.firstX);
+        let fdy = Math.abs(last.firstY - first.firstY);
+
+        let ldx = Math.abs(last.lastX - first.lastX);
+        let ldy = Math.abs(last.lastY - first.lastY);
+
+        return { first: { x: fdx, y: fdy },
+                 last: { x: ldx, y: ldy } };
+    }
+}
diff --git a/systemvm/agent/noVNC/core/input/keyboard.js b/systemvm/agent/noVNC/core/input/keyboard.js
index 9dbc8d6..9e6af2a 100644
--- a/systemvm/agent/noVNC/core/input/keyboard.js
+++ b/systemvm/agent/noVNC/core/input/keyboard.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
  */
 
@@ -118,9 +118,7 @@ export default class Keyboard {
 
         // We cannot handle keys we cannot track, but we also need
         // to deal with virtual keyboards which omit key info
-        // (iOS omits tracking info on keyup events, which forces us to
-        // special treat that platform here)
-        if ((code === 'Unidentified') || browser.isIOS()) {
+        if (code === 'Unidentified') {
             if (keysym) {
                 // If it's a virtual keyboard then it should be
                 // sufficient to just send press and release right
@@ -137,7 +135,7 @@ export default class Keyboard {
         // 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 (browser.isMac()) {
+        if (browser.isMac() || browser.isIOS()) {
             switch (keysym) {
                 case KeyTable.XK_Super_L:
                     keysym = KeyTable.XK_Alt_L;
@@ -164,7 +162,7 @@ export default class Keyboard {
         // 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 (browser.isMac() && (code === 'CapsLock')) {
+        if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
             this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
             this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
             stopEvent(e);
@@ -276,13 +274,28 @@ export default class Keyboard {
         }
 
         // See comment in _handleKeyDown()
-        if (browser.isMac() && (code === 'CapsLock')) {
+        if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
             this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
             this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
             return;
         }
 
         this._sendKeyEvent(this._keyDownList[code], code, false);
+
+        // Windows has a rather nasty bug where it won't send key
+        // release events for a Shift button if the other Shift is still
+        // pressed
+        if (browser.isWindows() && ((code === 'ShiftLeft') ||
+                                    (code === 'ShiftRight'))) {
+            if ('ShiftRight' in this._keyDownList) {
+                this._sendKeyEvent(this._keyDownList['ShiftRight'],
+                                   'ShiftRight', false);
+            }
+            if ('ShiftLeft' in this._keyDownList) {
+                this._sendKeyEvent(this._keyDownList['ShiftLeft'],
+                                   'ShiftLeft', false);
+            }
+        }
     }
 
     _handleAltGrTimeout() {
@@ -299,8 +312,11 @@ export default class Keyboard {
         Log.Debug("<< Keyboard.allKeysUp");
     }
 
-    // Firefox Alt workaround, see below
+    // Alt workaround for Firefox on Windows, see below
     _checkAlt(e) {
+        if (e.skipCheckAlt) {
+            return;
+        }
         if (e.altKey) {
             return;
         }
@@ -315,6 +331,7 @@ export default class Keyboard {
             const event = new KeyboardEvent('keyup',
                                             { key: downList[code],
                                               code: code });
+            event.skipCheckAlt = true;
             target.dispatchEvent(event);
         });
     }
@@ -331,9 +348,10 @@ export default class Keyboard {
         // Release (key up) if window loses focus
         window.addEventListener('blur', this._eventHandlers.blur);
 
-        // Firefox has broken handling of Alt, so we need to poll as
-        // best we can for releases (still doesn't prevent the menu
-        // from popping up though as we can't call preventDefault())
+        // Firefox on Windows has broken handling of Alt, so we need to
+        // poll as best we can for releases (still doesn't prevent the
+        // menu from popping up though as we can't call
+        // preventDefault())
         if (browser.isWindows() && browser.isFirefox()) {
             const handler = this._eventHandlers.checkalt;
             ['mousedown', 'mouseup', 'mousemove', 'wheel',
diff --git a/systemvm/agent/noVNC/core/input/mouse.js b/systemvm/agent/noVNC/core/input/mouse.js
deleted file mode 100644
index 58a2982..0000000
--- a/systemvm/agent/noVNC/core/input/mouse.js
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
- * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
- */
-
-import * as Log from '../util/logging.js';
-import { isTouchDevice } from '../util/browser.js';
-import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
-
-const WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
-const WHEEL_STEP_TIMEOUT = 50; // ms
-const WHEEL_LINE_HEIGHT = 19;
-
-export default class Mouse {
-    constructor(target) {
-        this._target = target || document;
-
-        this._doubleClickTimer = null;
-        this._lastTouchPos = null;
-
-        this._pos = null;
-        this._wheelStepXTimer = null;
-        this._wheelStepYTimer = null;
-        this._accumulatedWheelDeltaX = 0;
-        this._accumulatedWheelDeltaY = 0;
-
-        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)
-        };
-
-        // ===== PROPERTIES =====
-
-        this.touchButton = 1;                 // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
-
-        // ===== EVENT HANDLERS =====
-
-        this.onmousebutton = () => {}; // Handler for mouse button click/release
-        this.onmousemove = () => {}; // Handler for mouse movement
-    }
-
-    // ===== PRIVATE METHODS =====
-
-    _resetDoubleClickTimer() {
-        this._doubleClickTimer = null;
-    }
-
-    _handleMouseButton(e, down) {
-        this._updateMousePosition(e);
-        let pos = this._pos;
-
-        let 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.
-
-                    const xs = this._lastTouchPos.x - pos.x;
-                    const ys = this._lastTouchPos.y - pos.y;
-                    const 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.
-                    const 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
-        }
-
-        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(e) {
-        // Touch events have implicit capture
-        if (e.type === "mousedown") {
-            setCapture(this._target);
-        }
-
-        this._handleMouseButton(e, 1);
-    }
-
-    _handleMouseUp(e) {
-        this._handleMouseButton(e, 0);
-    }
-
-    // Mouse wheel events are sent in steps over VNC. This means that the VNC
-    // protocol can't handle a wheel event with specific distance or speed.
-    // Therefor, if we get a lot of small mouse wheel events we combine them.
-    _generateWheelStepX() {
-
-        if (this._accumulatedWheelDeltaX < 0) {
-            this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
-            this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
-        } else if (this._accumulatedWheelDeltaX > 0) {
-            this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
-            this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
-        }
-
-        this._accumulatedWheelDeltaX = 0;
-    }
-
-    _generateWheelStepY() {
-
-        if (this._accumulatedWheelDeltaY < 0) {
-            this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
-            this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
-        } else if (this._accumulatedWheelDeltaY > 0) {
-            this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
-            this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
-        }
-
-        this._accumulatedWheelDeltaY = 0;
-    }
-
-    _resetWheelStepTimers() {
-        window.clearTimeout(this._wheelStepXTimer);
-        window.clearTimeout(this._wheelStepYTimer);
-        this._wheelStepXTimer = null;
-        this._wheelStepYTimer = null;
-    }
-
-    _handleMouseWheel(e) {
-        this._resetWheelStepTimers();
-
-        this._updateMousePosition(e);
-
-        let dX = e.deltaX;
-        let dY = e.deltaY;
-
-        // Pixel units unless it's non-zero.
-        // Note that if deltamode is line or page won't matter since we aren't
-        // sending the mouse wheel delta to the server anyway.
-        // The difference between pixel and line can be important however since
-        // we have a threshold that can be smaller than the line height.
-        if (e.deltaMode !== 0) {
-            dX *= WHEEL_LINE_HEIGHT;
-            dY *= WHEEL_LINE_HEIGHT;
-        }
-
-        this._accumulatedWheelDeltaX += dX;
-        this._accumulatedWheelDeltaY += dY;
-
-        // Generate a mouse wheel step event when the accumulated delta
-        // for one of the axes is large enough.
-        // Small delta events that do not pass the threshold get sent
-        // after a timeout.
-        if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
-            this._generateWheelStepX();
-        } else {
-            this._wheelStepXTimer =
-                window.setTimeout(this._generateWheelStepX.bind(this),
-                                  WHEEL_STEP_TIMEOUT);
-        }
-        if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
-            this._generateWheelStepY();
-        } else {
-            this._wheelStepYTimer =
-                window.setTimeout(this._generateWheelStepY.bind(this),
-                                  WHEEL_STEP_TIMEOUT);
-        }
-
-        stopEvent(e);
-    }
-
-    _handleMouseMove(e) {
-        this._updateMousePosition(e);
-        this.onmousemove(this._pos.x, this._pos.y);
-        stopEvent(e);
-    }
-
-    _handleMouseDisable(e) {
-        /*
-         * 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);
-        }
-    }
-
-    // Update coordinates relative to target
-    _updateMousePosition(e) {
-        e = getPointerEvent(e);
-        const bounds = this._target.getBoundingClientRect();
-        let x;
-        let 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;
-        }
-        this._pos = {x: x, y: y};
-    }
-
-    // ===== PUBLIC METHODS =====
-
-    grab() {
-        if (isTouchDevice) {
-            this._target.addEventListener('touchstart', this._eventHandlers.mousedown);
-            this._target.addEventListener('touchend', this._eventHandlers.mouseup);
-            this._target.addEventListener('touchmove', this._eventHandlers.mousemove);
-        }
-        this._target.addEventListener('mousedown', this._eventHandlers.mousedown);
-        this._target.addEventListener('mouseup', this._eventHandlers.mouseup);
-        this._target.addEventListener('mousemove', this._eventHandlers.mousemove);
-        this._target.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 */
-        this._target.addEventListener('contextmenu', this._eventHandlers.mousedisable);
-    }
-
-    ungrab() {
-        this._resetWheelStepTimers();
-
-        if (isTouchDevice) {
-            this._target.removeEventListener('touchstart', this._eventHandlers.mousedown);
-            this._target.removeEventListener('touchend', this._eventHandlers.mouseup);
-            this._target.removeEventListener('touchmove', this._eventHandlers.mousemove);
-        }
-        this._target.removeEventListener('mousedown', this._eventHandlers.mousedown);
-        this._target.removeEventListener('mouseup', this._eventHandlers.mouseup);
-        this._target.removeEventListener('mousemove', this._eventHandlers.mousemove);
-        this._target.removeEventListener('wheel', this._eventHandlers.mousewheel);
-
-        document.removeEventListener('click', this._eventHandlers.mousedisable);
-
-        this._target.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
-    }
-}
diff --git a/systemvm/agent/noVNC/core/input/uskeysym.js b/systemvm/agent/noVNC/core/input/uskeysym.js
new file mode 100644
index 0000000..97c5ae4
--- /dev/null
+++ b/systemvm/agent/noVNC/core/input/uskeysym.js
@@ -0,0 +1,57 @@
+export default {
+    '1': 0x0031, /* U+0031 DIGIT ONE */
+    '2': 0x0032, /* U+0032 DIGIT TWO */
+    '3': 0x0033, /* U+0033 DIGIT THREE */
+    '4': 0x0034, /* U+0034 DIGIT FOUR */
+    '5': 0x0035, /* U+0035 DIGIT FIVE */
+    '6': 0x0036, /* U+0036 DIGIT SIX */
+    '7': 0x0037, /* U+0037 DIGIT SEVEN */
+    '8': 0x0038, /* U+0038 DIGIT EIGHT */
+    '9': 0x0039, /* U+0039 DIGIT NINE */
+    '0': 0x0030, /* U+0030 DIGIT ZERO */
+
+    'a': 0x0061, /* U+0061 LATIN SMALL LETTER A */
+    'b': 0x0062, /* U+0062 LATIN SMALL LETTER B */
+    'c': 0x0063, /* U+0063 LATIN SMALL LETTER C */
+    'd': 0x0064, /* U+0064 LATIN SMALL LETTER D */
+    'e': 0x0065, /* U+0065 LATIN SMALL LETTER E */
+    'f': 0x0066, /* U+0066 LATIN SMALL LETTER F */
+    'g': 0x0067, /* U+0067 LATIN SMALL LETTER G */
+    'h': 0x0068, /* U+0068 LATIN SMALL LETTER H */
+    'i': 0x0069, /* U+0069 LATIN SMALL LETTER I */
+    'j': 0x006a, /* U+006A LATIN SMALL LETTER J */
+    'k': 0x006b, /* U+006B LATIN SMALL LETTER K */
+    'l': 0x006c, /* U+006C LATIN SMALL LETTER L */
+    'm': 0x006d, /* U+006D LATIN SMALL LETTER M */
+    'n': 0x006e, /* U+006E LATIN SMALL LETTER N */
+    'o': 0x006f, /* U+006F LATIN SMALL LETTER O */
+    'p': 0x0070, /* U+0070 LATIN SMALL LETTER P */
+    'q': 0x0071, /* U+0071 LATIN SMALL LETTER Q */
+    'r': 0x0072, /* U+0072 LATIN SMALL LETTER R */
+    's': 0x0073, /* U+0073 LATIN SMALL LETTER S */
+    't': 0x0074, /* U+0074 LATIN SMALL LETTER T */
+    'u': 0x0075, /* U+0075 LATIN SMALL LETTER U */
+    'v': 0x0076, /* U+0076 LATIN SMALL LETTER V */
+    'w': 0x0077, /* U+0077 LATIN SMALL LETTER W */
+    'x': 0x0078, /* U+0078 LATIN SMALL LETTER X */
+    'y': 0x0079, /* U+0079 LATIN SMALL LETTER Y */
+    'z': 0x007a, /* U+007A LATIN SMALL LETTER Z */
+
+    '`': 0x0060, /* U+0060 GRAVE ACCENT */
+    '-': 0x002d, /* U+002D HYPHEN-MINUS */
+    '=': 0x003d, /* U+003D EQUALS SIGN */
+
+    '[': 0x005b, /* U+005B LEFT SQUARE BRACKET */
+    ']': 0x005d, /* U+005D RIGHT SQUARE BRACKET */
+    '\\': 0x005c, /* U+005C REVERSE SOLIDUS */
+
+    ';': 0x003b, /* U+003B SEMICOLON */
+    '\'': 0x0027, /* U+0027 APOSTROPHE */
+
+    ',': 0x002c, /* U+002C COMMA */
+    '.': 0x002e, /* U+002E FULL STOP */
+    '/': 0x002f, /* U+002F SOLIDUS */
+
+    ' ': 0x0020, /* U+0020 SPACE */
+    '\n': 0xff0d
+}
\ No newline at end of file
diff --git a/systemvm/agent/noVNC/core/input/util.js b/systemvm/agent/noVNC/core/input/util.js
index f177ef5..1b98040 100644
--- a/systemvm/agent/noVNC/core/input/util.js
+++ b/systemvm/agent/noVNC/core/input/util.js
@@ -1,3 +1,4 @@
+import KeyTable from "./keysym.js";
 import keysyms from "./keysymdef.js";
 import vkeys from "./vkeys.js";
 import fixedkeys from "./fixedkeys.js";
@@ -91,6 +92,8 @@ export function getKey(evt) {
         // Mozilla isn't fully in sync with the spec yet
         switch (evt.key) {
             case 'OS': return 'Meta';
+            case 'LaunchMyComputer': return 'LaunchApplication1';
+            case 'LaunchCalculator': return 'LaunchApplication2';
         }
 
         // iOS leaks some OS names
@@ -102,9 +105,21 @@ export function getKey(evt) {
             case 'UIKeyInputEscape': return 'Escape';
         }
 
-        // IE and Edge have broken handling of AltGraph so we cannot
-        // trust them for printable characters
-        if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
+        // Broken behaviour in Chrome
+        if ((evt.key === '\x00') && (evt.code === 'NumpadDecimal')) {
+            return 'Delete';
+        }
+
+        // IE and Edge need special handling, but for everyone else we
+        // can trust the value provided
+        if (!browser.isIE() && !browser.isEdge()) {
+            return evt.key;
+        }
+
+        // IE and Edge have broken handling of AltGraph so we can only
+        // trust them for non-printable characters (and unfortunately
+        // they also specify 'Unidentified' for some problem keys)
+        if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
             return evt.key;
         }
     }
@@ -141,10 +156,39 @@ export function getKeysym(evt) {
             location = 2;
         }
 
+        // And for Clear
+        if ((key === 'Clear') && (location === 3)) {
+            let code = getKeycode(evt);
+            if (code === 'NumLock') {
+                location = 0;
+            }
+        }
+
         if ((location === undefined) || (location > 3)) {
             location = 0;
         }
 
+        // The original Meta key now gets confused with the Windows key
+        // https://bugs.chromium.org/p/chromium/issues/detail?id=1020141
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=1232918
+        if (key === 'Meta') {
+            let code = getKeycode(evt);
+            if (code === 'AltLeft') {
+                return KeyTable.XK_Meta_L;
+            } else if (code === 'AltRight') {
+                return KeyTable.XK_Meta_R;
+            }
+        }
+
+        // macOS has Clear instead of NumLock, but the remote system is
+        // probably not macOS, so lying here is probably best...
+        if (key === 'Clear') {
+            let code = getKeycode(evt);
+            if (code === 'NumLock') {
+                return KeyTable.XK_Num_Lock;
+            }
+        }
+
         return DOMKeyTable[key][location];
     }
 
diff --git a/systemvm/agent/noVNC/core/rfb.js b/systemvm/agent/noVNC/core/rfb.js
index e40df66..eda1597 100644
--- a/systemvm/agent/noVNC/core/rfb.js
+++ b/systemvm/agent/noVNC/core/rfb.js
@@ -1,23 +1,29 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2020 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
  *
  */
 
+import { toUnsigned32bit, toSigned32bit } from './util/int.js';
 import * as Log from './util/logging.js';
-import { decodeUTF8 } from './util/strings.js';
+import { encodeUTF8, decodeUTF8 } from './util/strings.js';
 import { dragThreshold } from './util/browser.js';
+import { clientToElement } from './util/element.js';
+import { setCapture } from './util/events.js';
 import EventTargetMixin from './util/eventtarget.js';
 import Display from "./display.js";
+import Inflator from "./inflator.js";
+import Deflator from "./deflator.js";
 import Keyboard from "./input/keyboard.js";
-import Mouse from "./input/mouse.js";
+import GestureHandler from "./input/gesturehandler.js";
 import Cursor from "./util/cursor.js";
 import Websock from "./websock.js";
 import DES from "./des.js";
 import KeyTable from "./input/keysym.js";
+import USKeyTable from "./input/uskeysym.js";
 import XtScancode from "./input/xtscancodes.js";
 import { encodings } from "./encodings.js";
 import "./util/polyfill.js";
@@ -33,6 +39,36 @@ import TightPNGDecoder from "./decoders/tightpng.js";
 const DISCONNECT_TIMEOUT = 3;
 const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
 
+// Minimum wait (ms) between two mouse moves
+const MOUSE_MOVE_DELAY = 17;
+
+// Wheel thresholds
+const WHEEL_STEP = 50; // Pixels needed for one step
+const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
+
+// Gesture thresholds
+const GESTURE_ZOOMSENS = 75;
+const GESTURE_SCRLSENS = 50;
+const DOUBLE_TAP_TIMEOUT = 1000;
+const DOUBLE_TAP_THRESHOLD = 50;
+
+// Extended clipboard pseudo-encoding formats
+const extendedClipboardFormatText   = 1;
+/*eslint-disable no-unused-vars */
+const extendedClipboardFormatRtf    = 1 << 1;
+const extendedClipboardFormatHtml   = 1 << 2;
+const extendedClipboardFormatDib    = 1 << 3;
+const extendedClipboardFormatFiles  = 1 << 4;
+/*eslint-enable */
+
+// Extended clipboard pseudo-encoding actions
+const extendedClipboardActionCaps    = 1 << 24;
+const extendedClipboardActionRequest = 1 << 25;
+const extendedClipboardActionPeek    = 1 << 26;
+const extendedClipboardActionNotify  = 1 << 27;
+const extendedClipboardActionProvide = 1 << 28;
+
+
 export default class RFB extends EventTargetMixin {
     constructor(target, url, options) {
         if (!target) {
@@ -49,27 +85,28 @@ export default class RFB extends EventTargetMixin {
 
         // Connection details
         options = options || {};
-        this._rfb_credentials = options.credentials || {};
-        this._shared = false;
+        this._rfbCredentials = options.credentials || {};
+        this._shared = 'shared' in options ? !!options.shared : true;
         this._repeaterID = options.repeaterID || '';
-        this._showDotCursor = options.showDotCursor || false;
+        this._wsProtocols = ['binary'];
 
         // Internal state
-        this._rfb_connection_state = '';
-        this._rfb_init_state = '';
-        this._rfb_auth_scheme = -1;
-        this._rfb_clean_disconnect = true;
+        this._rfbConnectionState = '';
+        this._rfbInitState = '';
+        this._rfbAuthScheme = -1;
+        this._rfbCleanDisconnect = true;
 
         // Server capabilities
-        this._rfb_version = 0;
-        this._rfb_max_version = 3.8;
-        this._rfb_tightvnc = false;
-        this._rfb_xvp_ver = 0;
+        this._rfbVersion = 0;
+        this._rfbMaxVersion = 3.8;
+        this._rfbTightVNC = false;
+        this._rfbVeNCryptState = 0;
+        this._rfbXvpVer = 0;
 
-        this._fb_width = 0;
-        this._fb_height = 0;
+        this._fbWidth = 0;
+        this._fbHeight = 0;
 
-        this._fb_name = "";
+        this._fbName = "";
 
         this._capabilities = { power: false };
 
@@ -79,21 +116,26 @@ export default class RFB extends EventTargetMixin {
         this._enabledContinuousUpdates = false;
 
         this._supportsSetDesktopSize = false;
-        this._screen_id = 0;
-        this._screen_flags = 0;
+        this._screenID = 0;
+        this._screenFlags = 0;
 
         this._qemuExtKeyEventSupported = false;
 
+        this._clipboardText = null;
+        this._clipboardServerCapabilitiesActions = {};
+        this._clipboardServerCapabilitiesFormats = {};
+
         // Internal objects
         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._gestures = null;          // Gesture input handler object
 
         // Timers
         this._disconnTimer = null;      // disconnection timer
         this._resizeTimeout = null;     // resize rate limiting
+        this._mouseMoveTimer = null;
 
         // Decoder states
         this._decoders = {};
@@ -108,16 +150,28 @@ export default class RFB extends EventTargetMixin {
         };
 
         // Mouse state
-        this._mouse_buttonMask = 0;
-        this._mouse_arr = [];
+        this._mousePos = {};
+        this._mouseButtonMask = 0;
+        this._mouseLastMoveTime = 0;
         this._viewportDragging = false;
         this._viewportDragPos = {};
         this._viewportHasMoved = false;
+        this._accumulatedWheelDeltaX = 0;
+        this._accumulatedWheelDeltaY = 0;
+
+        // Gesture state
+        this._gestureLastTapTime = null;
+        this._gestureFirstDoubleTapEv = null;
+        this._gestureLastMagnitudeX = 0;
+        this._gestureLastMagnitudeY = 0;
 
         // Bound event handlers
         this._eventHandlers = {
             focusCanvas: this._focusCanvas.bind(this),
             windowResize: this._windowResize.bind(this),
+            handleMouse: this._handleMouse.bind(this),
+            handleWheel: this._handleWheel.bind(this),
+            handleGesture: this._handleGesture.bind(this),
         };
 
         // main setup
@@ -172,27 +226,24 @@ export default class RFB extends EventTargetMixin {
             throw exc;
         }
         this._display.onflush = this._onFlush.bind(this);
-        this._display.clear();
 
         this._keyboard = new Keyboard(this._canvas);
         this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
 
-        this._mouse = new Mouse(this._canvas);
-        this._mouse.onmousebutton = this._handleMouseButton.bind(this);
-        this._mouse.onmousemove = this._handleMouseMove.bind(this);
+        this._gestures = new GestureHandler();
 
         this._sock = new Websock();
         this._sock.on('message', () => {
-            this._handle_message();
+            this._handleMessage();
         });
         this._sock.on('open', () => {
-            if ((this._rfb_connection_state === 'connecting') &&
-                (this._rfb_init_state === '')) {
-                this._rfb_init_state = 'ProtocolVersion';
+            if ((this._rfbConnectionState === 'connecting') &&
+                (this._rfbInitState === '')) {
+                this._rfbInitState = 'ProtocolVersion';
                 Log.Debug("Starting VNC handshake");
             } else {
                 this._fail("Unexpected server connection while " +
-                           this._rfb_connection_state);
+                           this._rfbConnectionState);
             }
         });
         this._sock.on('close', (e) => {
@@ -205,7 +256,7 @@ export default class RFB extends EventTargetMixin {
                 }
                 msg += ")";
             }
-            switch (this._rfb_connection_state) {
+            switch (this._rfbConnectionState) {
                 case 'connecting':
                     this._fail("Connection closed " + msg);
                     break;
@@ -246,6 +297,15 @@ export default class RFB extends EventTargetMixin {
         this._clipViewport = false;
         this._scaleViewport = false;
         this._resizeSession = false;
+
+        this._showDotCursor = false;
+        if (options.showDotCursor !== undefined) {
+            Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
+            this._showDotCursor = options.showDotCursor;
+        }
+
+        this._qualityLevel = 6;
+        this._compressionLevel = 2;
     }
 
     // ===== PROPERTIES =====
@@ -254,22 +314,20 @@ export default class RFB extends EventTargetMixin {
     set viewOnly(viewOnly) {
         this._viewOnly = viewOnly;
 
-        if (this._rfb_connection_state === "connecting" ||
-            this._rfb_connection_state === "connected") {
+        if (this._rfbConnectionState === "connecting" ||
+            this._rfbConnectionState === "connected") {
             if (viewOnly) {
                 this._keyboard.ungrab();
-                this._mouse.ungrab();
             } else {
                 this._keyboard.grab();
-                this._mouse.grab();
             }
         }
     }
 
     get capabilities() { return this._capabilities; }
 
-    get touchButton() { return this._mouse.touchButton; }
-    set touchButton(button) { this._mouse.touchButton = button; }
+    get touchButton() { return 0; }
+    set touchButton(button) { Log.Warn("Using old API!"); }
 
     get clipViewport() { return this._clipViewport; }
     set clipViewport(viewport) {
@@ -308,6 +366,46 @@ export default class RFB extends EventTargetMixin {
     get background() { return this._screen.style.background; }
     set background(cssValue) { this._screen.style.background = cssValue; }
 
+    get qualityLevel() {
+        return this._qualityLevel;
+    }
+    set qualityLevel(qualityLevel) {
+        if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {
+            Log.Error("qualityLevel must be an integer between 0 and 9");
+            return;
+        }
+
+        if (this._qualityLevel === qualityLevel) {
+            return;
+        }
+
+        this._qualityLevel = qualityLevel;
+
+        if (this._rfbConnectionState === 'connected') {
+            this._sendEncodings();
+        }
+    }
+
+    get compressionLevel() {
+        return this._compressionLevel;
+    }
+    set compressionLevel(compressionLevel) {
+        if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) {
+            Log.Error("compressionLevel must be an integer between 0 and 9");
+            return;
+        }
+
+        if (this._compressionLevel === compressionLevel) {
+            return;
+        }
+
+        this._compressionLevel = compressionLevel;
+
+        if (this._rfbConnectionState === 'connected') {
+            this._sendEncodings();
+        }
+    }
+
     // ===== PUBLIC METHODS =====
 
     disconnect() {
@@ -318,12 +416,29 @@ export default class RFB extends EventTargetMixin {
     }
 
     sendCredentials(creds) {
-        this._rfb_credentials = creds;
-        setTimeout(this._init_msg.bind(this), 0);
+        this._rfbCredentials = creds;
+        setTimeout(this._initMsg.bind(this), 0);
+    }
+
+    sendText(text) {
+        for (var i = 0; i < text.length; i++) {
+            const character = text.charAt(i);
+            var charCode = USKeyTable[character] || false;
+            if (charCode) {
+                this.sendKey(charCode, character, true);
+                this.sendKey(charCode, character, false);
+            } else {
+                charCode = text.charCodeAt(i)
+                this.sendKey(KeyTable.XK_Shift_L, "ShiftLeft", true);
+                this.sendKey(charCode, character, true);
+                this.sendKey(charCode, character, false);
+                this.sendKey(KeyTable.XK_Shift_L, "ShiftLeft", false);
+            }
+        }
     }
 
     sendCtrlAltDel() {
-        if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
         Log.Info("Sending Ctrl-Alt-Del");
 
         this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
@@ -359,7 +474,7 @@ export default class RFB extends EventTargetMixin {
     // Send a key press. If 'down' is not specified then send a down key
     // followed by an up key.
     sendKey(keysym, code, down) {
-        if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
+        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
 
         if (down === undefined) {
             this.sendKey(keysym, code, true);
@@ -394,8 +509,22 @@ export default class RFB extends EventTargetMixin {
     }
 
     clipboardPasteFrom(text) {
-        if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
-        RFB.messages.clientCutText(this._sock, text);
+        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
+
+        if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&
+            this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
+
+            this._clipboardText = text;
+            RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
+        } else {
+            let data = new Uint8Array(text.length);
+            for (let i = 0; i < text.length; i++) {
+                // FIXME: text can have values outside of Latin1/Uint8
+                data[i] = text.charCodeAt(i);
+            }
+
+            RFB.messages.clientCutText(this._sock, data);
+        }
     }
 
     // ===== PRIVATE METHODS =====
@@ -407,7 +536,7 @@ export default class RFB extends EventTargetMixin {
 
         try {
             // WebSocket.onopen transitions to the RFB init states
-            this._sock.open(this._url, ['binary']);
+            this._sock.open(this._url, this._wsProtocols);
         } catch (e) {
             if (e.name === 'SyntaxError') {
                 this._fail("Invalid host or port (" + e + ")");
@@ -419,6 +548,8 @@ export default class RFB extends EventTargetMixin {
         // Make our elements part of the page
         this._target.appendChild(this._screen);
 
+        this._gestures.attach(this._canvas);
+
         this._cursor.attach(this._canvas);
         this._refreshCursor();
 
@@ -430,17 +561,44 @@ export default class RFB extends EventTargetMixin {
         this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
         this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
 
+        // Mouse events
+        this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);
+        this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);
+        this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);
+        // Prevent middle-click pasting (see handler for why we bind to document)
+        this._canvas.addEventListener('click', this._eventHandlers.handleMouse);
+        // preventDefault() on mousedown doesn't stop this event for some
+        // reason so we have to explicitly block it
+        this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
+
+        // Wheel events
+        this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
+
+        // Gesture events
+        this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);
+        this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);
+        this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture);
+
         Log.Debug("<< RFB.connect");
     }
 
     _disconnect() {
         Log.Debug(">> RFB.disconnect");
         this._cursor.detach();
+        this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);
+        this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);
+        this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);
+        this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);
+        this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);
+        this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);
+        this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
+        this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
+        this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
         this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
         this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
         window.removeEventListener('resize', this._eventHandlers.windowResize);
         this._keyboard.ungrab();
-        this._mouse.ungrab();
+        this._gestures.detach();
         this._sock.close();
         try {
             this._target.removeChild(this._screen);
@@ -453,15 +611,11 @@ export default class RFB extends EventTargetMixin {
             }
         }
         clearTimeout(this._resizeTimeout);
+        clearTimeout(this._mouseMoveTimer);
         Log.Debug("<< RFB.disconnect");
     }
 
     _focusCanvas(event) {
-        // Respect earlier handlers' request to not do side-effects
-        if (event.defaultPrevented) {
-            return;
-        }
-
         if (!this.focusOnClick) {
             return;
         }
@@ -469,6 +623,13 @@ export default class RFB extends EventTargetMixin {
         this.focus();
     }
 
+    _setDesktopName(name) {
+        this._fbName = name;
+        this.dispatchEvent(new CustomEvent(
+            "desktopname",
+            { detail: { name: this._fbName } }));
+    }
+
     _windowResize(event) {
         // If the window resized then our screen element might have
         // as well. Update the viewport dimensions.
@@ -491,19 +652,19 @@ export default class RFB extends EventTargetMixin {
     // Update state of clipping in Display object, and make sure the
     // configured viewport matches the current screen size
     _updateClip() {
-        const cur_clip = this._display.clipViewport;
-        let new_clip = this._clipViewport;
+        const curClip = this._display.clipViewport;
+        let newClip = this._clipViewport;
 
         if (this._scaleViewport) {
             // Disable viewport clipping if we are scaling
-            new_clip = false;
+            newClip = false;
         }
 
-        if (cur_clip !== new_clip) {
-            this._display.clipViewport = new_clip;
+        if (curClip !== newClip) {
+            this._display.clipViewport = newClip;
         }
 
-        if (new_clip) {
+        if (newClip) {
             // When clipping is enabled, the screen is limited to
             // the size of the container.
             const size = this._screenSize();
@@ -536,7 +697,7 @@ export default class RFB extends EventTargetMixin {
         const size = this._screenSize();
         RFB.messages.setDesktopSize(this._sock,
                                     Math.floor(size.w), Math.floor(size.h),
-                                    this._screen_id, this._screen_flags);
+                                    this._screenID, this._screenFlags);
 
         Log.Debug('Requested new desktop size: ' +
                    size.w + 'x' + size.h);
@@ -568,7 +729,7 @@ export default class RFB extends EventTargetMixin {
      *   disconnected - permanent state
      */
     _updateConnectionState(state) {
-        const oldstate = this._rfb_connection_state;
+        const oldstate = this._rfbConnectionState;
 
         if (state === oldstate) {
             Log.Debug("Already in state '" + state + "', ignoring");
@@ -622,7 +783,7 @@ export default class RFB extends EventTargetMixin {
 
         // State change actions
 
-        this._rfb_connection_state = state;
+        this._rfbConnectionState = state;
 
         Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
 
@@ -656,7 +817,7 @@ export default class RFB extends EventTargetMixin {
             case 'disconnected':
                 this.dispatchEvent(new CustomEvent(
                     "disconnect", { detail:
-                                    { clean: this._rfb_clean_disconnect } }));
+                                    { clean: this._rfbCleanDisconnect } }));
                 break;
         }
     }
@@ -667,7 +828,7 @@ export default class RFB extends EventTargetMixin {
      * should be logged but not sent to the user interface.
      */
     _fail(details) {
-        switch (this._rfb_connection_state) {
+        switch (this._rfbConnectionState) {
             case 'disconnecting':
                 Log.Error("Failed when disconnecting: " + details);
                 break;
@@ -681,7 +842,7 @@ export default class RFB extends EventTargetMixin {
                 Log.Error("RFB failure: " + details);
                 break;
         }
-        this._rfb_clean_disconnect = false; //This is sent to the UI
+        this._rfbCleanDisconnect = false; //This is sent to the UI
 
         // Transition to disconnected without waiting for socket to close
         this._updateConnectionState('disconnecting');
@@ -696,13 +857,13 @@ export default class RFB extends EventTargetMixin {
                                            { detail: { capabilities: this._capabilities } }));
     }
 
-    _handle_message() {
+    _handleMessage() {
         if (this._sock.rQlen === 0) {
-            Log.Warn("handle_message called on an empty receive queue");
+            Log.Warn("handleMessage called on an empty receive queue");
             return;
         }
 
-        switch (this._rfb_connection_state) {
+        switch (this._rfbConnectionState) {
             case 'disconnected':
                 Log.Error("Got data while disconnected");
                 break;
@@ -711,7 +872,7 @@ export default class RFB extends EventTargetMixin {
                     if (this._flushing) {
                         break;
                     }
-                    if (!this._normal_msg()) {
+                    if (!this._normalMsg()) {
                         break;
                     }
                     if (this._sock.rQlen === 0) {
@@ -720,7 +881,7 @@ export default class RFB extends EventTargetMixin {
                 }
                 break;
             default:
-                this._init_msg();
+                this._initMsg();
                 break;
         }
     }
@@ -729,13 +890,52 @@ export default class RFB extends EventTargetMixin {
         this.sendKey(keysym, code, down);
     }
 
-    _handleMouseButton(x, y, down, bmask) {
-        if (down) {
-            this._mouse_buttonMask |= bmask;
-        } else {
-            this._mouse_buttonMask &= ~bmask;
+    _handleMouse(ev) {
+        /*
+         * We don't check connection status or viewOnly here as the
+         * mouse events might be used to control the viewport
+         */
+
+        if (ev.type === 'click') {
+            /*
+             * 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 (ev.target !== this._canvas) {
+                return;
+            }
         }
 
+        // FIXME: if we're in view-only and not dragging,
+        //        should we stop events?
+        ev.stopPropagation();
+        ev.preventDefault();
+
+        if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
+            return;
+        }
+
+        let pos = clientToElement(ev.clientX, ev.clientY,
+                                  this._canvas);
+
+        switch (ev.type) {
+            case 'mousedown':
+                setCapture(this._canvas);
+                this._handleMouseButton(pos.x, pos.y,
+                                        true, 1 << ev.button);
+                break;
+            case 'mouseup':
+                this._handleMouseButton(pos.x, pos.y,
+                                        false, 1 << ev.button);
+                break;
+            case 'mousemove':
+                this._handleMouseMove(pos.x, pos.y);
+                break;
+        }
+    }
+
+    _handleMouseButton(x, y, down, bmask) {
         if (this.dragViewport) {
             if (down && !this._viewportDragging) {
                 this._viewportDragging = true;
@@ -756,17 +956,24 @@ export default class RFB extends EventTargetMixin {
                 // Otherwise we treat this as a mouse click event.
                 // Send the button down event here, as the button up
                 // event is sent at the end of this function.
-                RFB.messages.pointerEvent(this._sock,
-                                          this._display.absX(x),
-                                          this._display.absY(y),
-                                          bmask);
+                this._sendMouse(x, y, bmask);
             }
         }
 
-        if (this._viewOnly) { return; } // View only, skip mouse events
+        // Flush waiting move event first
+        if (this._mouseMoveTimer !== null) {
+            clearTimeout(this._mouseMoveTimer);
+            this._mouseMoveTimer = null;
+            this._sendMouse(x, y, this._mouseButtonMask);
+        }
+
+        if (down) {
+            this._mouseButtonMask |= bmask;
+        } else {
+            this._mouseButtonMask &= ~bmask;
+        }
 
-        if (this._rfb_connection_state !== 'connected') { return; }
-        RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+        this._sendMouse(x, y, this._mouseButtonMask);
     }
 
     _handleMouseMove(x, y) {
@@ -786,66 +993,304 @@ export default class RFB extends EventTargetMixin {
             return;
         }
 
+        this._mousePos = { 'x': x, 'y': y };
+
+        // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
+        if (this._mouseMoveTimer == null) {
+
+            const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;
+            if (timeSinceLastMove > MOUSE_MOVE_DELAY) {
+                this._sendMouse(x, y, this._mouseButtonMask);
+                this._mouseLastMoveTime = Date.now();
+            } else {
+                // Too soon since the latest move, wait the remaining time
+                this._mouseMoveTimer = setTimeout(() => {
+                    this._handleDelayedMouseMove();
+                }, MOUSE_MOVE_DELAY - timeSinceLastMove);
+            }
+        }
+    }
+
+    _handleDelayedMouseMove() {
+        this._mouseMoveTimer = null;
+        this._sendMouse(this._mousePos.x, this._mousePos.y,
+                        this._mouseButtonMask);
+        this._mouseLastMoveTime = Date.now();
+    }
+
+    _sendMouse(x, y, mask) {
+        if (this._rfbConnectionState !== 'connected') { return; }
         if (this._viewOnly) { 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);
+        RFB.messages.pointerEvent(this._sock, this._display.absX(x),
+                                  this._display.absY(y), mask);
+    }
+
+    _handleWheel(ev) {
+        if (this._rfbConnectionState !== 'connected') { return; }
+        if (this._viewOnly) { return; } // View only, skip mouse events
+
+        ev.stopPropagation();
+        ev.preventDefault();
+
+        let pos = clientToElement(ev.clientX, ev.clientY,
+                                  this._canvas);
+
+        let dX = ev.deltaX;
+        let dY = ev.deltaY;
+
+        // Pixel units unless it's non-zero.
+        // Note that if deltamode is line or page won't matter since we aren't
+        // sending the mouse wheel delta to the server anyway.
+        // The difference between pixel and line can be important however since
+        // we have a threshold that can be smaller than the line height.
+        if (ev.deltaMode !== 0) {
+            dX *= WHEEL_LINE_HEIGHT;
+            dY *= WHEEL_LINE_HEIGHT;
+        }
+
+        // Mouse wheel events are sent in steps over VNC. This means that the VNC
+        // protocol can't handle a wheel event with specific distance or speed.
+        // Therefor, if we get a lot of small mouse wheel events we combine them.
+        this._accumulatedWheelDeltaX += dX;
+        this._accumulatedWheelDeltaY += dY;
+
+        // Generate a mouse wheel step event when the accumulated delta
+        // for one of the axes is large enough.
+        if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
+            if (this._accumulatedWheelDeltaX < 0) {
+                this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
+                this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
+            } else if (this._accumulatedWheelDeltaX > 0) {
+                this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
+                this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
+            }
+
+            this._accumulatedWheelDeltaX = 0;
+        }
+        if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
+            if (this._accumulatedWheelDeltaY < 0) {
+                this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
+                this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
+            } else if (this._accumulatedWheelDeltaY > 0) {
+                this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
+                this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
+            }
+
+            this._accumulatedWheelDeltaY = 0;
+        }
+    }
+
+    _fakeMouseMove(ev, elementX, elementY) {
+        this._handleMouseMove(elementX, elementY);
+        this._cursor.move(ev.detail.clientX, ev.detail.clientY);
+    }
+
+    _handleTapEvent(ev, bmask) {
+        let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
+                                  this._canvas);
+
+        // If the user quickly taps multiple times we assume they meant to
+        // hit the same spot, so slightly adjust coordinates
+
+        if ((this._gestureLastTapTime !== null) &&
+            ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&
+            (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {
+            let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;
+            let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;
+            let distance = Math.hypot(dx, dy);
+
+            if (distance < DOUBLE_TAP_THRESHOLD) {
+                pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,
+                                      this._gestureFirstDoubleTapEv.detail.clientY,
+                                      this._canvas);
+            } else {
+                this._gestureFirstDoubleTapEv = ev;
+            }
+        } else {
+            this._gestureFirstDoubleTapEv = ev;
+        }
+        this._gestureLastTapTime = Date.now();
+
+        this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);
+        this._handleMouseButton(pos.x, pos.y, true, bmask);
+        this._handleMouseButton(pos.x, pos.y, false, bmask);
+    }
+
+    _handleGesture(ev) {
+        let magnitude;
+
+        let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
+                                  this._canvas);
+        switch (ev.type) {
+            case 'gesturestart':
+                switch (ev.detail.type) {
+                    case 'onetap':
+                        this._handleTapEvent(ev, 0x1);
+                        break;
+                    case 'twotap':
+                        this._handleTapEvent(ev, 0x4);
+                        break;
+                    case 'threetap':
+                        this._handleTapEvent(ev, 0x2);
+                        break;
+                    case 'drag':
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        this._handleMouseButton(pos.x, pos.y, true, 0x1);
+                        break;
+                    case 'longpress':
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        this._handleMouseButton(pos.x, pos.y, true, 0x4);
+                        break;
+
+                    case 'twodrag':
+                        this._gestureLastMagnitudeX = ev.detail.magnitudeX;
+                        this._gestureLastMagnitudeY = ev.detail.magnitudeY;
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        break;
+                    case 'pinch':
+                        this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,
+                                                                 ev.detail.magnitudeY);
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        break;
+                }
+                break;
+
+            case 'gesturemove':
+                switch (ev.detail.type) {
+                    case 'onetap':
+                    case 'twotap':
+                    case 'threetap':
+                        break;
+                    case 'drag':
+                    case 'longpress':
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        break;
+                    case 'twodrag':
+                        // Always scroll in the same position.
+                        // We don't know if the mouse was moved so we need to move it
+                        // every update.
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
+                            this._handleMouseButton(pos.x, pos.y, true, 0x8);
+                            this._handleMouseButton(pos.x, pos.y, false, 0x8);
+                            this._gestureLastMagnitudeY += GESTURE_SCRLSENS;
+                        }
+                        while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {
+                            this._handleMouseButton(pos.x, pos.y, true, 0x10);
+                            this._handleMouseButton(pos.x, pos.y, false, 0x10);
+                            this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;
+                        }
+                        while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {
+                            this._handleMouseButton(pos.x, pos.y, true, 0x20);
+                            this._handleMouseButton(pos.x, pos.y, false, 0x20);
+                            this._gestureLastMagnitudeX += GESTURE_SCRLSENS;
+                        }
+                        while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {
+                            this._handleMouseButton(pos.x, pos.y, true, 0x40);
+                            this._handleMouseButton(pos.x, pos.y, false, 0x40);
+                            this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;
+                        }
+                        break;
+                    case 'pinch':
+                        // Always scroll in the same position.
+                        // We don't know if the mouse was moved so we need to move it
+                        // every update.
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
+                        if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
+                            this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
+                            while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
+                                this._handleMouseButton(pos.x, pos.y, true, 0x8);
+                                this._handleMouseButton(pos.x, pos.y, false, 0x8);
+                                this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;
+                            }
+                            while ((magnitude -  this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {
+                                this._handleMouseButton(pos.x, pos.y, true, 0x10);
+                                this._handleMouseButton(pos.x, pos.y, false, 0x10);
+                                this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;
+                            }
+                        }
+                        this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
+                        break;
+                }
+                break;
+
+            case 'gestureend':
+                switch (ev.detail.type) {
+                    case 'onetap':
+                    case 'twotap':
+                    case 'threetap':
+                    case 'pinch':
+                    case 'twodrag':
+                        break;
+                    case 'drag':
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        this._handleMouseButton(pos.x, pos.y, false, 0x1);
+                        break;
+                    case 'longpress':
+                        this._fakeMouseMove(ev, pos.x, pos.y);
+                        this._handleMouseButton(pos.x, pos.y, false, 0x4);
+                        break;
+                }
+                break;
+        }
     }
 
     // Message Handlers
 
-    _negotiate_protocol_version() {
+    _negotiateProtocolVersion() {
         if (this._sock.rQwait("version", 12)) {
             return false;
         }
 
         const sversion = this._sock.rQshiftStr(12).substr(4, 7);
         Log.Info("Server ProtocolVersion: " + sversion);
-        let is_repeater = 0;
+        let isRepeater = 0;
         switch (sversion) {
             case "000.000":  // UltraVNC repeater
-                is_repeater = 1;
+                isRepeater = 1;
                 break;
             case "003.003":
             case "003.006":  // UltraVNC
             case "003.889":  // Apple Remote Desktop
-                this._rfb_version = 3.3;
+                this._rfbVersion = 3.3;
                 break;
             case "003.007":
-                this._rfb_version = 3.7;
+                this._rfbVersion = 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;
+                this._rfbVersion = 3.8;
                 break;
             default:
                 return this._fail("Invalid server version " + sversion);
         }
 
-        if (is_repeater) {
+        if (isRepeater) {
             let repeaterID = "ID:" + this._repeaterID;
             while (repeaterID.length < 250) {
                 repeaterID += "\0";
             }
-            this._sock.send_string(repeaterID);
+            this._sock.sendString(repeaterID);
             return true;
         }
 
-        if (this._rfb_version > this._rfb_max_version) {
-            this._rfb_version = this._rfb_max_version;
+        if (this._rfbVersion > this._rfbMaxVersion) {
+            this._rfbVersion = this._rfbMaxVersion;
         }
 
-        const cversion = "00" + parseInt(this._rfb_version, 10) +
-                       ".00" + ((this._rfb_version * 10) % 10);
-        this._sock.send_string("RFB " + cversion + "\n");
+        const cversion = "00" + parseInt(this._rfbVersion, 10) +
+                       ".00" + ((this._rfbVersion * 10) % 10);
+        this._sock.sendString("RFB " + cversion + "\n");
         Log.Debug('Sent ProtocolVersion: ' + cversion);
 
-        this._rfb_init_state = 'Security';
+        this._rfbInitState = 'Security';
     }
 
-    _negotiate_security() {
+    _negotiateSecurity() {
         // Polyfill since IE and PhantomJS doesn't have
         // TypedArray.includes()
         function includes(item, array) {
@@ -857,55 +1302,57 @@ export default class RFB extends EventTargetMixin {
             return false;
         }
 
-        if (this._rfb_version >= 3.7) {
+        if (this._rfbVersion >= 3.7) {
             // Server sends supported list, client decides
-            const num_types = this._sock.rQshift8();
-            if (this._sock.rQwait("security type", num_types, 1)) { return false; }
-
-            if (num_types === 0) {
-                this._rfb_init_state = "SecurityReason";
-                this._security_context = "no security types";
-                this._security_status = 1;
-                return this._init_msg();
+            const numTypes = this._sock.rQshift8();
+            if (this._sock.rQwait("security type", numTypes, 1)) { return false; }
+
+            if (numTypes === 0) {
+                this._rfbInitState = "SecurityReason";
+                this._securityContext = "no security types";
+                this._securityStatus = 1;
+                return this._initMsg();
             }
 
-            const types = this._sock.rQshiftBytes(num_types);
+            const types = this._sock.rQshiftBytes(numTypes);
             Log.Debug("Server security types: " + types);
 
             // Look for each auth in preferred order
             if (includes(1, types)) {
-                this._rfb_auth_scheme = 1; // None
+                this._rfbAuthScheme = 1; // None
             } else if (includes(22, types)) {
-                this._rfb_auth_scheme = 22; // XVP
+                this._rfbAuthScheme = 22; // XVP
             } else if (includes(16, types)) {
-                this._rfb_auth_scheme = 16; // Tight
+                this._rfbAuthScheme = 16; // Tight
             } else if (includes(2, types)) {
-                this._rfb_auth_scheme = 2; // VNC Auth
+                this._rfbAuthScheme = 2; // VNC Auth
+            } else if (includes(19, types)) {
+                this._rfbAuthScheme = 19; // VeNCrypt Auth
             } else {
                 return this._fail("Unsupported security types (types: " + types + ")");
             }
 
-            this._sock.send([this._rfb_auth_scheme]);
+            this._sock.send([this._rfbAuthScheme]);
         } else {
             // Server decides
             if (this._sock.rQwait("security scheme", 4)) { return false; }
-            this._rfb_auth_scheme = this._sock.rQshift32();
+            this._rfbAuthScheme = this._sock.rQshift32();
 
-            if (this._rfb_auth_scheme == 0) {
-                this._rfb_init_state = "SecurityReason";
-                this._security_context = "authentication scheme";
-                this._security_status = 1;
-                return this._init_msg();
+            if (this._rfbAuthScheme == 0) {
+                this._rfbInitState = "SecurityReason";
+                this._securityContext = "authentication scheme";
+                this._securityStatus = 1;
+                return this._initMsg();
             }
         }
 
-        this._rfb_init_state = 'Authentication';
-        Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
+        this._rfbInitState = 'Authentication';
+        Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
 
-        return this._init_msg(); // jump to authentication
+        return this._initMsg(); // jump to authentication
     }
 
-    _handle_security_reason() {
+    _handleSecurityReason() {
         if (this._sock.rQwait("reason length", 4)) {
             return false;
         }
@@ -920,46 +1367,134 @@ export default class RFB extends EventTargetMixin {
         if (reason !== "") {
             this.dispatchEvent(new CustomEvent(
                 "securityfailure",
-                { detail: { status: this._security_status,
+                { detail: { status: this._securityStatus,
                             reason: reason } }));
 
             return this._fail("Security negotiation failed on " +
-                              this._security_context +
+                              this._securityContext +
                               " (reason: " + reason + ")");
         } else {
             this.dispatchEvent(new CustomEvent(
                 "securityfailure",
-                { detail: { status: this._security_status } }));
+                { detail: { status: this._securityStatus } }));
 
             return this._fail("Security negotiation failed on " +
-                              this._security_context);
+                              this._securityContext);
         }
     }
 
     // authentication
-    _negotiate_xvp_auth() {
-        if (!this._rfb_credentials.username ||
-            !this._rfb_credentials.password ||
-            !this._rfb_credentials.target) {
+    _negotiateXvpAuth() {
+        if (this._rfbCredentials.username === undefined ||
+            this._rfbCredentials.password === undefined ||
+            this._rfbCredentials.target === undefined) {
             this.dispatchEvent(new CustomEvent(
                 "credentialsrequired",
                 { detail: { types: ["username", "password", "target"] } }));
             return false;
         }
 
-        const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
-                           String.fromCharCode(this._rfb_credentials.target.length) +
-                           this._rfb_credentials.username +
-                           this._rfb_credentials.target;
-        this._sock.send_string(xvp_auth_str);
-        this._rfb_auth_scheme = 2;
-        return this._negotiate_authentication();
+        const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) +
+                           String.fromCharCode(this._rfbCredentials.target.length) +
+                           this._rfbCredentials.username +
+                           this._rfbCredentials.target;
+        this._sock.sendString(xvpAuthStr);
+        this._rfbAuthScheme = 2;
+        return this._negotiateAuthentication();
     }
 
-    _negotiate_std_vnc_auth() {
+    // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
+    _negotiateVeNCryptAuth() {
+
+        // waiting for VeNCrypt version
+        if (this._rfbVeNCryptState == 0) {
+            if (this._sock.rQwait("vencrypt version", 2)) { return false; }
+
+            const major = this._sock.rQshift8();
+            const minor = this._sock.rQshift8();
+
+            if (!(major == 0 && minor == 2)) {
+                return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
+            }
+
+            this._sock.send([0, 2]);
+            this._rfbVeNCryptState = 1;
+        }
+
+        // waiting for ACK
+        if (this._rfbVeNCryptState == 1) {
+            if (this._sock.rQwait("vencrypt ack", 1)) { return false; }
+
+            const res = this._sock.rQshift8();
+
+            if (res != 0) {
+                return this._fail("VeNCrypt failure " + res);
+            }
+
+            this._rfbVeNCryptState = 2;
+        }
+        // must fall through here (i.e. no "else if"), beacause we may have already received
+        // the subtypes length and won't be called again
+
+        if (this._rfbVeNCryptState == 2) { // waiting for subtypes length
+            if (this._sock.rQwait("vencrypt subtypes length", 1)) { return false; }
+
+            const subtypesLength = this._sock.rQshift8();
+            if (subtypesLength < 1) {
+                return this._fail("VeNCrypt subtypes empty");
+            }
+
+            this._rfbVeNCryptSubtypesLength = subtypesLength;
+            this._rfbVeNCryptState = 3;
+        }
+
+        // waiting for subtypes list
+        if (this._rfbVeNCryptState == 3) {
+            if (this._sock.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength)) { return false; }
+
+            const subtypes = [];
+            for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) {
+                subtypes.push(this._sock.rQshift32());
+            }
+
+            // 256 = Plain subtype
+            if (subtypes.indexOf(256) != -1) {
+                // 0x100 = 256
+                this._sock.send([0, 0, 1, 0]);
+                this._rfbVeNCryptState = 4;
+            } else {
+                return this._fail("VeNCrypt Plain subtype not offered by server");
+            }
+        }
+
+        // negotiated Plain subtype, server waits for password
+        if (this._rfbVeNCryptState == 4) {
+            if (!this._rfbCredentials.username ||
+                !this._rfbCredentials.password) {
+                this.dispatchEvent(new CustomEvent(
+                    "credentialsrequired",
+                    { detail: { types: ["username", "password"] } }));
+                return false;
+            }
+
+            const user = encodeUTF8(this._rfbCredentials.username);
+            const pass = encodeUTF8(this._rfbCredentials.password);
+
+            // XXX we assume lengths are <= 255 (should not be an issue in the real world)
+            this._sock.send([0, 0, 0, user.length]);
+            this._sock.send([0, 0, 0, pass.length]);
+            this._sock.sendString(user);
+            this._sock.sendString(pass);
+
+            this._rfbInitState = "SecurityResult";
+            return true;
+        }
+    }
+
+    _negotiateStdVNCAuth() {
         if (this._sock.rQwait("auth challenge", 16)) { return false; }
 
-        if (!this._rfb_credentials.password) {
+        if (this._rfbCredentials.password === undefined) {
             this.dispatchEvent(new CustomEvent(
                 "credentialsrequired",
                 { detail: { types: ["password"] } }));
@@ -968,23 +1503,40 @@ export default class RFB extends EventTargetMixin {
 
         // TODO(directxman12): make genDES not require an Array
         const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
-        const response = RFB.genDES(this._rfb_credentials.password, challenge);
+        const response = RFB.genDES(this._rfbCredentials.password, challenge);
         this._sock.send(response);
-        this._rfb_init_state = "SecurityResult";
+        this._rfbInitState = "SecurityResult";
+        return true;
+    }
+
+    _negotiateTightUnixAuth() {
+        if (this._rfbCredentials.username === undefined ||
+            this._rfbCredentials.password === undefined) {
+            this.dispatchEvent(new CustomEvent(
+                "credentialsrequired",
+                { detail: { types: ["username", "password"] } }));
+            return false;
+        }
+
+        this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
+        this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
+        this._sock.sendString(this._rfbCredentials.username);
+        this._sock.sendString(this._rfbCredentials.password);
+        this._rfbInitState = "SecurityResult";
         return true;
     }
 
-    _negotiate_tight_tunnels(numTunnels) {
+    _negotiateTightTunnels(numTunnels) {
         const clientSupportedTunnelTypes = {
             0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
         };
         const serverSupportedTunnelTypes = {};
         // receive tunnel capabilities
         for (let i = 0; i < numTunnels; i++) {
-            const cap_code = this._sock.rQshift32();
-            const cap_vendor = this._sock.rQshiftStr(4);
-            const cap_signature = this._sock.rQshiftStr(8);
-            serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
+            const capCode = this._sock.rQshift32();
+            const capVendor = this._sock.rQshiftStr(4);
+            const capSignature = this._sock.rQshiftStr(8);
+            serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };
         }
 
         Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
@@ -1015,16 +1567,16 @@ export default class RFB extends EventTargetMixin {
         }
     }
 
-    _negotiate_tight_auth() {
-        if (!this._rfb_tightvnc) {  // first pass, do the tunnel negotiation
+    _negotiateTightAuth() {
+        if (!this._rfbTightVNC) {  // first pass, do the tunnel negotiation
             if (this._sock.rQwait("num tunnels", 4)) { return false; }
             const numTunnels = this._sock.rQshift32();
             if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
 
-            this._rfb_tightvnc = true;
+            this._rfbTightVNC = true;
 
             if (numTunnels > 0) {
-                this._negotiate_tight_tunnels(numTunnels);
+                this._negotiateTightTunnels(numTunnels);
                 return false;  // wait until we receive the sub auth to continue
             }
         }
@@ -1033,7 +1585,7 @@ export default class RFB extends EventTargetMixin {
         if (this._sock.rQwait("sub auth count", 4)) { return false; }
         const subAuthCount = this._sock.rQshift32();
         if (subAuthCount === 0) {  // empty sub-auth list received means 'no auth' subtype selected
-            this._rfb_init_state = 'SecurityResult';
+            this._rfbInitState = 'SecurityResult';
             return true;
         }
 
@@ -1041,7 +1593,8 @@ export default class RFB extends EventTargetMixin {
 
         const clientSupportedTypes = {
             'STDVNOAUTH__': 1,
-            'STDVVNCAUTH_': 2
+            'STDVVNCAUTH_': 2,
+            'TGHTULGNAUTH': 129
         };
 
         const serverSupportedTypes = [];
@@ -1061,11 +1614,14 @@ export default class RFB extends EventTargetMixin {
 
                 switch (authType) {
                     case 'STDVNOAUTH__':  // no auth
-                        this._rfb_init_state = 'SecurityResult';
+                        this._rfbInitState = 'SecurityResult';
                         return true;
                     case 'STDVVNCAUTH_': // VNC auth
-                        this._rfb_auth_scheme = 2;
-                        return this._init_msg();
+                        this._rfbAuthScheme = 2;
+                        return this._initMsg();
+                    case 'TGHTULGNAUTH': // UNIX auth
+                        this._rfbAuthScheme = 129;
+                        return this._initMsg();
                     default:
                         return this._fail("Unsupported tiny auth scheme " +
                                           "(scheme: " + authType + ")");
@@ -1076,46 +1632,52 @@ export default class RFB extends EventTargetMixin {
         return this._fail("No supported sub-auth types!");
     }
 
-    _negotiate_authentication() {
-        switch (this._rfb_auth_scheme) {
+    _negotiateAuthentication() {
+        switch (this._rfbAuthScheme) {
             case 1:  // no auth
-                if (this._rfb_version >= 3.8) {
-                    this._rfb_init_state = 'SecurityResult';
+                if (this._rfbVersion >= 3.8) {
+                    this._rfbInitState = 'SecurityResult';
                     return true;
                 }
-                this._rfb_init_state = 'ClientInitialisation';
-                return this._init_msg();
+                this._rfbInitState = 'ClientInitialisation';
+                return this._initMsg();
 
             case 22:  // XVP auth
-                return this._negotiate_xvp_auth();
+                return this._negotiateXvpAuth();
 
             case 2:  // VNC authentication
-                return this._negotiate_std_vnc_auth();
+                return this._negotiateStdVNCAuth();
 
             case 16:  // TightVNC Security Type
-                return this._negotiate_tight_auth();
+                return this._negotiateTightAuth();
+
+            case 19:  // VeNCrypt Security Type
+                return this._negotiateVeNCryptAuth();
+
+            case 129:  // TightVNC UNIX Security Type
+                return this._negotiateTightUnixAuth();
 
             default:
                 return this._fail("Unsupported auth scheme (scheme: " +
-                                  this._rfb_auth_scheme + ")");
+                                  this._rfbAuthScheme + ")");
         }
     }
 
-    _handle_security_result() {
+    _handleSecurityResult() {
         if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
 
         const status = this._sock.rQshift32();
 
         if (status === 0) { // OK
-            this._rfb_init_state = 'ClientInitialisation';
+            this._rfbInitState = 'ClientInitialisation';
             Log.Debug('Authentication OK');
-            return this._init_msg();
+            return this._initMsg();
         } else {
-            if (this._rfb_version >= 3.8) {
-                this._rfb_init_state = "SecurityReason";
-                this._security_context = "security result";
-                this._security_status = status;
-                return this._init_msg();
+            if (this._rfbVersion >= 3.8) {
+                this._rfbInitState = "SecurityReason";
+                this._securityContext = "security result";
+                this._securityStatus = status;
+                return this._initMsg();
             } else {
                 this.dispatchEvent(new CustomEvent(
                     "securityfailure",
@@ -1126,7 +1688,7 @@ export default class RFB extends EventTargetMixin {
         }
     }
 
-    _negotiate_server_init() {
+    _negotiateServerInit() {
         if (this._sock.rQwait("server initialization", 24)) { return false; }
 
         /* Screen size */
@@ -1136,27 +1698,28 @@ export default class RFB extends EventTargetMixin {
         /* PIXEL_FORMAT */
         const bpp         = this._sock.rQshift8();
         const depth       = this._sock.rQshift8();
-        const big_endian  = this._sock.rQshift8();
-        const true_color  = this._sock.rQshift8();
-
-        const red_max     = this._sock.rQshift16();
-        const green_max   = this._sock.rQshift16();
-        const blue_max    = this._sock.rQshift16();
-        const red_shift   = this._sock.rQshift8();
-        const green_shift = this._sock.rQshift8();
-        const blue_shift  = this._sock.rQshift8();
+        const bigEndian  = this._sock.rQshift8();
+        const trueColor  = this._sock.rQshift8();
+
+        const redMax     = this._sock.rQshift16();
+        const greenMax   = this._sock.rQshift16();
+        const blueMax    = this._sock.rQshift16();
+        const redShift   = this._sock.rQshift8();
+        const greenShift = this._sock.rQshift8();
+        const blueShift  = 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 */
-        const 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));
+        const nameLength = this._sock.rQshift32();
+        if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
+        let name = this._sock.rQshiftStr(nameLength);
+        name = decodeUTF8(name, true);
 
-        if (this._rfb_tightvnc) {
-            if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
+        if (this._rfbTightVNC) {
+            if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
             // In TightVNC mode, ServerInit message is extended
             const numServerMessages = this._sock.rQshift16();
             const numClientMessages = this._sock.rQshift16();
@@ -1164,7 +1727,7 @@ export default class RFB extends EventTargetMixin {
             this._sock.rQskipBytes(2);  // padding
 
             const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
-            if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
+            if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
 
             // we don't actually do anything with the capability information that TIGHT sends,
             // so we just skip the all of this.
@@ -1183,42 +1746,31 @@ export default class RFB extends EventTargetMixin {
         //                   if we backtrack
         Log.Info("Screen: " + width + "x" + 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");
-        }
-
+                  ", bigEndian: " + bigEndian +
+                  ", trueColor: " + trueColor +
+                  ", redMax: " + redMax +
+                  ", greenMax: " + greenMax +
+                  ", blueMax: " + blueMax +
+                  ", redShift: " + redShift +
+                  ", greenShift: " + greenShift +
+                  ", blueShift: " + blueShift);
+
+        // we're past the point where we could backtrack, so it's safe to call this
+        this._setDesktopName(name);
         this._resize(width, height);
 
         if (!this._viewOnly) { this._keyboard.grab(); }
-        if (!this._viewOnly) { this._mouse.grab(); }
 
-        this._fb_depth = 24;
+        this._fbDepth = 24;
 
-        if (this._fb_name === "Intel(r) AMT KVM") {
+        if (this._fbName === "Intel(r) AMT KVM") {
             Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
-            this._fb_depth = 8;
+            this._fbDepth = 8;
         }
 
-        RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
+        RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
         this._sendEncodings();
-        RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
+        RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
 
         this._updateConnectionState('connected');
         return true;
@@ -1230,7 +1782,7 @@ export default class RFB extends EventTargetMixin {
         // In preference order
         encs.push(encodings.encodingCopyRect);
         // Only supported with full depth support
-        if (this._fb_depth == 24) {
+        if (this._fbDepth == 24) {
             encs.push(encodings.encodingTight);
             encs.push(encodings.encodingTightPNG);
             encs.push(encodings.encodingHextile);
@@ -1239,8 +1791,8 @@ export default class RFB extends EventTargetMixin {
         encs.push(encodings.encodingRaw);
 
         // Psuedo-encoding settings
-        encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
-        encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
+        encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
+        encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
 
         encs.push(encodings.pseudoEncodingDesktopSize);
         encs.push(encodings.pseudoEncodingLastRect);
@@ -1249,8 +1801,11 @@ export default class RFB extends EventTargetMixin {
         encs.push(encodings.pseudoEncodingXvp);
         encs.push(encodings.pseudoEncodingFence);
         encs.push(encodings.pseudoEncodingContinuousUpdates);
+        encs.push(encodings.pseudoEncodingDesktopName);
+        encs.push(encodings.pseudoEncodingExtendedClipboard);
 
-        if (this._fb_depth == 24) {
+        if (this._fbDepth == 24) {
+            encs.push(encodings.pseudoEncodingVMwareCursor);
             encs.push(encodings.pseudoEncodingCursor);
         }
 
@@ -1265,63 +1820,212 @@ export default class RFB extends EventTargetMixin {
      *   ClientInitialization - not triggered by server message
      *   ServerInitialization
      */
-    _init_msg() {
-        switch (this._rfb_init_state) {
+    _initMsg() {
+        switch (this._rfbInitState) {
             case 'ProtocolVersion':
-                return this._negotiate_protocol_version();
+                return this._negotiateProtocolVersion();
 
             case 'Security':
-                return this._negotiate_security();
+                return this._negotiateSecurity();
 
             case 'Authentication':
-                return this._negotiate_authentication();
+                return this._negotiateAuthentication();
 
             case 'SecurityResult':
-                return this._handle_security_result();
+                return this._handleSecurityResult();
 
             case 'SecurityReason':
-                return this._handle_security_reason();
+                return this._handleSecurityReason();
 
             case 'ClientInitialisation':
-                this._sock.send([0]); // ClientInitialisation for exclusive access
-                this._rfb_init_state = 'ServerInitialisation';
+                this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
+                this._rfbInitState = 'ServerInitialisation';
                 return true;
 
             case 'ServerInitialisation':
-                return this._negotiate_server_init();
+                return this._negotiateServerInit();
 
             default:
                 return this._fail("Unknown init state (state: " +
-                                  this._rfb_init_state + ")");
+                                  this._rfbInitState + ")");
         }
     }
 
-    _handle_set_colour_map_msg() {
+    _handleSetColourMapMsg() {
         Log.Debug("SetColorMapEntries");
 
         return this._fail("Unexpected SetColorMapEntries message");
     }
 
-    _handle_server_cut_text() {
+    _handleServerCutText() {
         Log.Debug("ServerCutText");
 
         if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
+
         this._sock.rQskipBytes(3);  // Padding
-        const length = this._sock.rQshift32();
-        if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
 
-        const text = this._sock.rQshiftStr(length);
+        let length = this._sock.rQshift32();
+        length = toSigned32bit(length);
 
-        if (this._viewOnly) { return true; }
+        if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }
 
-        this.dispatchEvent(new CustomEvent(
-            "clipboard",
-            { detail: { text: text } }));
+        if (length >= 0) {
+            //Standard msg
+            const text = this._sock.rQshiftStr(length);
+            if (this._viewOnly) {
+                return true;
+            }
+
+            this.dispatchEvent(new CustomEvent(
+                "clipboard",
+                { detail: { text: text } }));
 
+        } else {
+            //Extended msg.
+            length = Math.abs(length);
+            const flags = this._sock.rQshift32();
+            let formats = flags & 0x0000FFFF;
+            let actions = flags & 0xFF000000;
+
+            let isCaps = (!!(actions & extendedClipboardActionCaps));
+            if (isCaps) {
+                this._clipboardServerCapabilitiesFormats = {};
+                this._clipboardServerCapabilitiesActions = {};
+
+                // Update our server capabilities for Formats
+                for (let i = 0; i <= 15; i++) {
+                    let index = 1 << i;
+
+                    // Check if format flag is set.
+                    if ((formats & index)) {
+                        this._clipboardServerCapabilitiesFormats[index] = true;
+                        // We don't send unsolicited clipboard, so we
+                        // ignore the size
+                        this._sock.rQshift32();
+                    }
+                }
+
+                // Update our server capabilities for Actions
+                for (let i = 24; i <= 31; i++) {
+                    let index = 1 << i;
+                    this._clipboardServerCapabilitiesActions[index] = !!(actions & index);
+                }
+
+                /*  Caps handling done, send caps with the clients
+                    capabilities set as a response */
+                let clientActions = [
+                    extendedClipboardActionCaps,
+                    extendedClipboardActionRequest,
+                    extendedClipboardActionPeek,
+                    extendedClipboardActionNotify,
+                    extendedClipboardActionProvide
+                ];
+                RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});
+
+            } else if (actions === extendedClipboardActionRequest) {
+                if (this._viewOnly) {
+                    return true;
+                }
+
+                // Check if server has told us it can handle Provide and there is clipboard data to send.
+                if (this._clipboardText != null &&
+                    this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {
+
+                    if (formats & extendedClipboardFormatText) {
+                        RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);
+                    }
+                }
+
+            } else if (actions === extendedClipboardActionPeek) {
+                if (this._viewOnly) {
+                    return true;
+                }
+
+                if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
+
+                    if (this._clipboardText != null) {
+                        RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
+                    } else {
+                        RFB.messages.extendedClipboardNotify(this._sock, []);
+                    }
+                }
+
+            } else if (actions === extendedClipboardActionNotify) {
+                if (this._viewOnly) {
+                    return true;
+                }
+
+                if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {
+
+                    if (formats & extendedClipboardFormatText) {
+                        RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);
+                    }
+                }
+
+            } else if (actions === extendedClipboardActionProvide) {
+                if (this._viewOnly) {
+                    return true;
+                }
+
+                if (!(formats & extendedClipboardFormatText)) {
+                    return true;
+                }
+                // Ignore what we had in our clipboard client side.
+                this._clipboardText = null;
+
+                // FIXME: Should probably verify that this data was actually requested
+                let zlibStream = this._sock.rQshiftBytes(length - 4);
+                let streamInflator = new Inflator();
+                let textData = null;
+
+                streamInflator.setInput(zlibStream);
+                for (let i = 0; i <= 15; i++) {
+                    let format = 1 << i;
+
+                    if (formats & format) {
+
+                        let size = 0x00;
+                        let sizeArray = streamInflator.inflate(4);
+
+                        size |= (sizeArray[0] << 24);
+                        size |= (sizeArray[1] << 16);
+                        size |= (sizeArray[2] << 8);
+                        size |= (sizeArray[3]);
+                        let chunk = streamInflator.inflate(size);
+
+                        if (format === extendedClipboardFormatText) {
+                            textData = chunk;
+                        }
+                    }
+                }
+                streamInflator.setInput(null);
+
+                if (textData !== null) {
+                    let tmpText = "";
+                    for (let i = 0; i < textData.length; i++) {
+                        tmpText += String.fromCharCode(textData[i]);
+                    }
+                    textData = tmpText;
+
+                    textData = decodeUTF8(textData);
+                    if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {
+                        textData = textData.slice(0, -1);
+                    }
+
+                    textData = textData.replace("\r\n", "\n");
+
+                    this.dispatchEvent(new CustomEvent(
+                        "clipboard",
+                        { detail: { text: textData } }));
+                }
+            } else {
+                return this._fail("Unexpected action in extended clipboard message: " + actions);
+            }
+        }
         return true;
     }
 
-    _handle_server_fence_msg() {
+    _handleServerFenceMsg() {
         if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
         this._sock.rQskipBytes(3); // Padding
         let flags = this._sock.rQshift32();
@@ -1363,49 +2067,49 @@ export default class RFB extends EventTargetMixin {
         return true;
     }
 
-    _handle_xvp_msg() {
+    _handleXvpMsg() {
         if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
         this._sock.rQskipBytes(1);  // Padding
-        const xvp_ver = this._sock.rQshift8();
-        const xvp_msg = this._sock.rQshift8();
+        const xvpVer = this._sock.rQshift8();
+        const xvpMsg = this._sock.rQshift8();
 
-        switch (xvp_msg) {
+        switch (xvpMsg) {
             case 0:  // XVP_FAIL
                 Log.Error("XVP Operation Failed");
                 break;
             case 1:  // XVP_INIT
-                this._rfb_xvp_ver = xvp_ver;
-                Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
+                this._rfbXvpVer = xvpVer;
+                Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")");
                 this._setCapability("power", true);
                 break;
             default:
-                this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
+                this._fail("Illegal server XVP message (msg: " + xvpMsg + ")");
                 break;
         }
 
         return true;
     }
 
-    _normal_msg() {
-        let msg_type;
+    _normalMsg() {
+        let msgType;
         if (this._FBU.rects > 0) {
-            msg_type = 0;
+            msgType = 0;
         } else {
-            msg_type = this._sock.rQshift8();
+            msgType = this._sock.rQshift8();
         }
 
         let first, ret;
-        switch (msg_type) {
+        switch (msgType) {
             case 0:  // FramebufferUpdate
                 ret = this._framebufferUpdate();
                 if (ret && !this._enabledContinuousUpdates) {
                     RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
-                                                 this._fb_width, this._fb_height);
+                                                 this._fbWidth, this._fbHeight);
                 }
                 return ret;
 
             case 1:  // SetColorMapEntries
-                return this._handle_set_colour_map_msg();
+                return this._handleSetColourMapMsg();
 
             case 2:  // Bell
                 Log.Debug("Bell");
@@ -1415,7 +2119,7 @@ export default class RFB extends EventTargetMixin {
                 return true;
 
             case 3:  // ServerCutText
-                return this._handle_server_cut_text();
+                return this._handleServerCutText();
 
             case 150: // EndOfContinuousUpdates
                 first = !this._supportsContinuousUpdates;
@@ -1432,13 +2136,13 @@ export default class RFB extends EventTargetMixin {
                 return true;
 
             case 248: // ServerFence
-                return this._handle_server_fence_msg();
+                return this._handleServerFenceMsg();
 
             case 250:  // XVP
-                return this._handle_xvp_msg();
+                return this._handleXvpMsg();
 
             default:
-                this._fail("Unexpected server message (type " + msg_type + ")");
+                this._fail("Unexpected server message (type " + msgType + ")");
                 Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
                 return true;
         }
@@ -1448,7 +2152,7 @@ export default class RFB extends EventTargetMixin {
         this._flushing = false;
         // Resume processing
         if (this._sock.rQlen > 0) {
-            this._handle_message();
+            this._handleMessage();
         }
     }
 
@@ -1500,6 +2204,9 @@ export default class RFB extends EventTargetMixin {
                 this._FBU.rects = 1; // Will be decreased when we return
                 return true;
 
+            case encodings.pseudoEncodingVMwareCursor:
+                return this._handleVMwareCursor();
+
             case encodings.pseudoEncodingCursor:
                 return this._handleCursor();
 
@@ -1515,6 +2222,9 @@ export default class RFB extends EventTargetMixin {
                 }
                 return true;
 
+            case encodings.pseudoEncodingDesktopName:
+                return this._handleDesktopName();
+
             case encodings.pseudoEncodingDesktopSize:
                 this._resize(this._FBU.width, this._FBU.height);
                 return true;
@@ -1527,6 +2237,122 @@ export default class RFB extends EventTargetMixin {
         }
     }
 
+    _handleVMwareCursor() {
+        const hotx = this._FBU.x;  // hotspot-x
+        const hoty = this._FBU.y;  // hotspot-y
+        const w = this._FBU.width;
+        const h = this._FBU.height;
+        if (this._sock.rQwait("VMware cursor encoding", 1)) {
+            return false;
+        }
+
+        const cursorType = this._sock.rQshift8();
+
+        this._sock.rQshift8(); //Padding
+
+        let rgba;
+        const bytesPerPixel = 4;
+
+        //Classic cursor
+        if (cursorType == 0) {
+            //Used to filter away unimportant bits.
+            //OR is used for correct conversion in js.
+            const PIXEL_MASK = 0xffffff00 | 0;
+            rgba = new Array(w * h * bytesPerPixel);
+
+            if (this._sock.rQwait("VMware cursor classic encoding",
+                                  (w * h * bytesPerPixel) * 2, 2)) {
+                return false;
+            }
+
+            let andMask = new Array(w * h);
+            for (let pixel = 0; pixel < (w * h); pixel++) {
+                andMask[pixel] = this._sock.rQshift32();
+            }
+
+            let xorMask = new Array(w * h);
+            for (let pixel = 0; pixel < (w * h); pixel++) {
+                xorMask[pixel] = this._sock.rQshift32();
+            }
+
+            for (let pixel = 0; pixel < (w * h); pixel++) {
+                if (andMask[pixel] == 0) {
+                    //Fully opaque pixel
+                    let bgr = xorMask[pixel];
+                    let r   = bgr >> 8  & 0xff;
+                    let g   = bgr >> 16 & 0xff;
+                    let b   = bgr >> 24 & 0xff;
+
+                    rgba[(pixel * bytesPerPixel)     ] = r;    //r
+                    rgba[(pixel * bytesPerPixel) + 1 ] = g;    //g
+                    rgba[(pixel * bytesPerPixel) + 2 ] = b;    //b
+                    rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
+
+                } else if ((andMask[pixel] & PIXEL_MASK) ==
+                           PIXEL_MASK) {
+                    //Only screen value matters, no mouse colouring
+                    if (xorMask[pixel] == 0) {
+                        //Transparent pixel
+                        rgba[(pixel * bytesPerPixel)     ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
+
+                    } else if ((xorMask[pixel] & PIXEL_MASK) ==
+                               PIXEL_MASK) {
+                        //Inverted pixel, not supported in browsers.
+                        //Fully opaque instead.
+                        rgba[(pixel * bytesPerPixel)     ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
+
+                    } else {
+                        //Unhandled xorMask
+                        rgba[(pixel * bytesPerPixel)     ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
+                        rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
+                    }
+
+                } else {
+                    //Unhandled andMask
+                    rgba[(pixel * bytesPerPixel)     ] = 0x00;
+                    rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
+                    rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
+                    rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
+                }
+            }
+
+        //Alpha cursor.
+        } else if (cursorType == 1) {
+            if (this._sock.rQwait("VMware cursor alpha encoding",
+                                  (w * h * 4), 2)) {
+                return false;
+            }
+
+            rgba = new Array(w * h * bytesPerPixel);
+
+            for (let pixel = 0; pixel < (w * h); pixel++) {
+                let data = this._sock.rQshift32();
+
+                rgba[(pixel * 4)     ] = data >> 24 & 0xff; //r
+                rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
+                rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff;  //b
+                rgba[(pixel * 4) + 3 ] = data & 0xff;       //a
+            }
+
+        } else {
+            Log.Warn("The given cursor type is not supported: "
+                      + cursorType + " given.");
+            return false;
+        }
+
+        this._updateCursor(rgba, hotx, hoty, w, h);
+
+        return true;
+    }
+
     _handleCursor() {
         const hotx = this._FBU.x;  // hotspot-x
         const hoty = this._FBU.y;  // hotspot-y
@@ -1546,16 +2372,16 @@ export default class RFB extends EventTargetMixin {
         const mask = this._sock.rQshiftBytes(masklength);
         let rgba = new Uint8Array(w * h * 4);
 
-        let pix_idx = 0;
+        let pixIdx = 0;
         for (let y = 0; y < h; y++) {
             for (let x = 0; x < w; x++) {
-                let mask_idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
-                let alpha = (mask[mask_idx] << (x % 8)) & 0x80 ? 255 : 0;
-                rgba[pix_idx    ] = pixels[pix_idx + 2];
-                rgba[pix_idx + 1] = pixels[pix_idx + 1];
-                rgba[pix_idx + 2] = pixels[pix_idx];
-                rgba[pix_idx + 3] = alpha;
-                pix_idx += 4;
+                let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);
+                let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;
+                rgba[pixIdx    ] = pixels[pixIdx + 2];
+                rgba[pixIdx + 1] = pixels[pixIdx + 1];
+                rgba[pixIdx + 2] = pixels[pixIdx];
+                rgba[pixIdx + 3] = alpha;
+                pixIdx += 4;
             }
         }
 
@@ -1564,14 +2390,33 @@ export default class RFB extends EventTargetMixin {
         return true;
     }
 
+    _handleDesktopName() {
+        if (this._sock.rQwait("DesktopName", 4)) {
+            return false;
+        }
+
+        let length = this._sock.rQshift32();
+
+        if (this._sock.rQwait("DesktopName", length, 4)) {
+            return false;
+        }
+
+        let name = this._sock.rQshiftStr(length);
+        name = decodeUTF8(name, true);
+
+        this._setDesktopName(name);
+
+        return true;
+    }
+
     _handleExtendedDesktopSize() {
         if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
             return false;
         }
 
-        const number_of_screens = this._sock.rQpeek8();
+        const numberOfScreens = this._sock.rQpeek8();
 
-        let bytes = 4 + (number_of_screens * 16);
+        let bytes = 4 + (numberOfScreens * 16);
         if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
             return false;
         }
@@ -1590,15 +2435,15 @@ export default class RFB extends EventTargetMixin {
         this._sock.rQskipBytes(1);  // number-of-screens
         this._sock.rQskipBytes(3);  // padding
 
-        for (let i = 0; i < number_of_screens; i += 1) {
+        for (let i = 0; i < numberOfScreens; i += 1) {
             // Save the id and flags of the first screen
             if (i === 0) {
-                this._screen_id = this._sock.rQshiftBytes(4);    // id
+                this._screenID = 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
+                this._screenFlags = this._sock.rQshiftBytes(4); // flags
             } else {
                 this._sock.rQskipBytes(16);
             }
@@ -1651,7 +2496,7 @@ export default class RFB extends EventTargetMixin {
             return decoder.decodeRect(this._FBU.x, this._FBU.y,
                                       this._FBU.width, this._FBU.height,
                                       this._sock, this._display,
-                                      this._fb_depth);
+                                      this._fbDepth);
         } catch (err) {
             this._fail("Error decoding rect: " + err);
             return false;
@@ -1662,14 +2507,14 @@ export default class RFB extends EventTargetMixin {
         if (!this._enabledContinuousUpdates) { return; }
 
         RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
-                                             this._fb_width, this._fb_height);
+                                             this._fbWidth, this._fbHeight);
     }
 
     _resize(width, height) {
-        this._fb_width = width;
-        this._fb_height = height;
+        this._fbWidth = width;
+        this._fbHeight = height;
 
-        this._display.resize(this._fb_width, this._fb_height);
+        this._display.resize(this._fbWidth, this._fbHeight);
 
         // Adjust the visible viewport based on the new dimensions
         this._updateClip();
@@ -1679,7 +2524,7 @@ export default class RFB extends EventTargetMixin {
     }
 
     _xvpOp(ver, op) {
-        if (this._rfb_xvp_ver < ver) { return; }
+        if (this._rfbXvpVer < ver) { return; }
         Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
         RFB.messages.xvpOp(this._sock, ver, op);
     }
@@ -1715,6 +2560,10 @@ export default class RFB extends EventTargetMixin {
     }
 
     _refreshCursor() {
+        if (this._rfbConnectionState !== "connecting" &&
+            this._rfbConnectionState !== "connected") {
+            return;
+        }
         const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
         this._cursor.change(image.rgbaPixels,
                             image.hotx, image.hoty,
@@ -1750,13 +2599,13 @@ RFB.messages = {
     },
 
     QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
-        function getRFBkeycode(xt_scancode) {
+        function getRFBkeycode(xtScanCode) {
             const upperByte = (keycode >> 8);
             const lowerByte = (keycode & 0x00ff);
             if (upperByte === 0xe0 && lowerByte < 0x7f) {
                 return lowerByte | 0x80;
             }
-            return xt_scancode;
+            return xtScanCode;
         }
 
         const buff = sock._sQ;
@@ -1802,8 +2651,102 @@ RFB.messages = {
         sock.flush();
     },
 
-    // TODO(directxman12): make this unicode compatible?
-    clientCutText(sock, text) {
+    // Used to build Notify and Request data.
+    _buildExtendedClipboardFlags(actions, formats) {
+        let data = new Uint8Array(4);
+        let formatFlag = 0x00000000;
+        let actionFlag = 0x00000000;
+
+        for (let i = 0; i < actions.length; i++) {
+            actionFlag |= actions[i];
+        }
+
+        for (let i = 0; i < formats.length; i++) {
+            formatFlag |= formats[i];
+        }
+
+        data[0] = actionFlag >> 24; // Actions
+        data[1] = 0x00;             // Reserved
+        data[2] = 0x00;             // Reserved
+        data[3] = formatFlag;       // Formats
+
+        return data;
+    },
+
+    extendedClipboardProvide(sock, formats, inData) {
+        // Deflate incomming data and their sizes
+        let deflator = new Deflator();
+        let dataToDeflate = [];
+
+        for (let i = 0; i < formats.length; i++) {
+            // We only support the format Text at this time
+            if (formats[i] != extendedClipboardFormatText) {
+                throw new Error("Unsupported extended clipboard format for Provide message.");
+            }
+
+            // Change lone \r or \n into \r\n as defined in rfbproto
+            inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");
+
+            // Check if it already has \0
+            let text = encodeUTF8(inData[i] + "\0");
+
+            dataToDeflate.push( (text.length >> 24) & 0xFF,
+                                (text.length >> 16) & 0xFF,
+                                (text.length >>  8) & 0xFF,
+                                (text.length & 0xFF));
+
+            for (let j = 0; j < text.length; j++) {
+                dataToDeflate.push(text.charCodeAt(j));
+            }
+        }
+
+        let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));
+
+        // Build data  to send
+        let data = new Uint8Array(4 + deflatedData.length);
+        data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],
+                                                           formats));
+        data.set(deflatedData, 4);
+
+        RFB.messages.clientCutText(sock, data, true);
+    },
+
+    extendedClipboardNotify(sock, formats) {
+        let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],
+                                                              formats);
+        RFB.messages.clientCutText(sock, flags, true);
+    },
+
+    extendedClipboardRequest(sock, formats) {
+        let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],
+                                                              formats);
+        RFB.messages.clientCutText(sock, flags, true);
+    },
+
+    extendedClipboardCaps(sock, actions, formats) {
+        let formatKeys = Object.keys(formats);
+        let data  = new Uint8Array(4 + (4 * formatKeys.length));
+
+        formatKeys.map(x => parseInt(x));
+        formatKeys.sort((a, b) =>  a - b);
+
+        data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));
+
+        let loopOffset = 4;
+        for (let i = 0; i < formatKeys.length; i++) {
+            data[loopOffset]     = formats[formatKeys[i]] >> 24;
+            data[loopOffset + 1] = formats[formatKeys[i]] >> 16;
+            data[loopOffset + 2] = formats[formatKeys[i]] >> 8;
+            data[loopOffset + 3] = formats[formatKeys[i]] >> 0;
+
+            loopOffset += 4;
+            data[3] |= (1 << formatKeys[i]); // Update our format flags
+        }
+
+        RFB.messages.clientCutText(sock, data, true);
+    },
+
+    clientCutText(sock, data, extended = false) {
         const buff = sock._sQ;
         const offset = sock._sQlen;
 
@@ -1813,7 +2756,12 @@ RFB.messages = {
         buff[offset + 2] = 0; // padding
         buff[offset + 3] = 0; // padding
 
-        let length = text.length;
+        let length;
+        if (extended) {
+            length = toUnsigned32bit(-data.length);
+        } else {
+            length = data.length;
+        }
 
         buff[offset + 4] = length >> 24;
         buff[offset + 5] = length >> 16;
@@ -1822,24 +2770,25 @@ RFB.messages = {
 
         sock._sQlen += 8;
 
-        // We have to keep track of from where in the text we begin creating the
+        // We have to keep track of from where in the data we begin creating the
         // buffer for the flush in the next iteration.
-        let textOffset = 0;
+        let dataOffset = 0;
 
-        let remaining = length;
+        let remaining = data.length;
         while (remaining > 0) {
 
             let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
             for (let i = 0; i < flushSize; i++) {
-                buff[sock._sQlen + i] =  text.charCodeAt(textOffset + i);
+                buff[sock._sQlen + i] = data[dataOffset + i];
             }
 
             sock._sQlen += flushSize;
             sock.flush();
 
             remaining -= flushSize;
-            textOffset += flushSize;
+            dataOffset += flushSize;
         }
+
     },
 
     setDesktopSize(sock, width, height, id, flags) {
@@ -1925,7 +2874,7 @@ RFB.messages = {
         sock.flush();
     },
 
-    pixelFormat(sock, depth, true_color) {
+    pixelFormat(sock, depth, trueColor) {
         const buff = sock._sQ;
         const offset = sock._sQlen;
 
@@ -1950,7 +2899,7 @@ RFB.messages = {
         buff[offset + 4] = bpp;                 // bits-per-pixel
         buff[offset + 5] = depth;               // depth
         buff[offset + 6] = 0;                   // little-endian
-        buff[offset + 7] = true_color ? 1 : 0;  // true-color
+        buff[offset + 7] = trueColor ? 1 : 0;  // true-color
 
         buff[offset + 8] = 0;    // red-max
         buff[offset + 9] = (1 << bits) - 1;  // red-max
@@ -1961,9 +2910,9 @@ RFB.messages = {
         buff[offset + 12] = 0;   // blue-max
         buff[offset + 13] = (1 << bits) - 1; // blue-max
 
-        buff[offset + 14] = bits * 2; // red-shift
+        buff[offset + 14] = bits * 0; // red-shift
         buff[offset + 15] = bits * 1; // green-shift
-        buff[offset + 16] = bits * 0; // blue-shift
+        buff[offset + 16] = bits * 2; // blue-shift
 
         buff[offset + 17] = 0;   // padding
         buff[offset + 18] = 0;   // padding
diff --git a/systemvm/agent/noVNC/core/util/browser.js b/systemvm/agent/noVNC/core/util/browser.js
index 8996cfe..1554801 100644
--- a/systemvm/agent/noVNC/core/util/browser.js
+++ b/systemvm/agent/noVNC/core/util/browser.js
@@ -1,9 +1,11 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
+ *
+ * Browser feature support detection
  */
 
 import * as Log from './logging.js';
@@ -31,7 +33,7 @@ try {
     const target = document.createElement('canvas');
     target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAA [...]
 
-    if (target.style.cursor) {
+    if (target.style.cursor.indexOf("url") === 0) {
         Log.Info("Data URI scheme cursor supported");
         _supportsCursorURIs = true;
     } else {
@@ -52,6 +54,38 @@ try {
 }
 export const supportsImageMetadata = _supportsImageMetadata;
 
+let _hasScrollbarGutter = true;
+try {
+    // Create invisible container
+    const container = document.createElement('div');
+    container.style.visibility = 'hidden';
+    container.style.overflow = 'scroll'; // forcing scrollbars
+    document.body.appendChild(container);
+
+    // Create a div and place it in the container
+    const child = document.createElement('div');
+    container.appendChild(child);
+
+    // Calculate the difference between the container's full width
+    // and the child's width - the difference is the scrollbars
+    const scrollbarWidth = (container.offsetWidth - child.offsetWidth);
+
+    // Clean up
+    container.parentNode.removeChild(container);
+
+    _hasScrollbarGutter = scrollbarWidth != 0;
+} catch (exc) {
+    Log.Error("Scrollbar test exception: " + exc);
+}
+export const hasScrollbarGutter = _hasScrollbarGutter;
+
+/*
+ * The functions for detection of platforms and browsers below are exported
+ * but the use of these should be minimized as much as possible.
+ *
+ * It's better to use feature detection than platform detection.
+ */
+
 export function isMac() {
     return navigator && !!(/mac/i).exec(navigator.platform);
 }
@@ -67,10 +101,6 @@ export function isIOS() {
             !!(/ipod/i).exec(navigator.platform));
 }
 
-export function isAndroid() {
-    return navigator && !!(/android/i).exec(navigator.userAgent);
-}
-
 export function isSafari() {
     return navigator && (navigator.userAgent.indexOf('Safari') !== -1 &&
                          navigator.userAgent.indexOf('Chrome') === -1);
diff --git a/systemvm/agent/noVNC/core/util/cursor.js b/systemvm/agent/noVNC/core/util/cursor.js
index 0d0b754..4db1dab 100644
--- a/systemvm/agent/noVNC/core/util/cursor.js
+++ b/systemvm/agent/noVNC/core/util/cursor.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
  */
 
@@ -20,7 +20,6 @@ export default class Cursor {
             this._canvas.style.pointerEvents = 'none';
             // Can't use "display" because of Firefox bug #1445997
             this._canvas.style.visibility = 'hidden';
-            document.body.appendChild(this._canvas);
         }
 
         this._position = { x: 0, y: 0 };
@@ -31,9 +30,6 @@ export default class Cursor {
             'mouseleave': this._handleMouseLeave.bind(this),
             'mousemove': this._handleMouseMove.bind(this),
             'mouseup': this._handleMouseUp.bind(this),
-            'touchstart': this._handleTouchStart.bind(this),
-            'touchmove': this._handleTouchMove.bind(this),
-            'touchend': this._handleTouchEnd.bind(this),
         };
     }
 
@@ -45,6 +41,8 @@ export default class Cursor {
         this._target = target;
 
         if (useFallback) {
+            document.body.appendChild(this._canvas);
+
             // FIXME: These don't fire properly except for mouse
             ///       movement in IE. We want to also capture element
             //        movement, size changes, visibility, etc.
@@ -53,17 +51,16 @@ export default class Cursor {
             this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
             this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
             this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
-
-            // There is no "touchleave" so we monitor touchstart globally
-            window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
-            this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
-            this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
         }
 
         this.clear();
     }
 
     detach() {
+        if (!this._target) {
+            return;
+        }
+
         if (useFallback) {
             const options = { capture: true, passive: true };
             this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
@@ -71,9 +68,7 @@ export default class Cursor {
             this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
             this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
 
-            window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
-            this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
-            this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
+            document.body.removeChild(this._canvas);
         }
 
         this._target = null;
@@ -124,6 +119,27 @@ export default class Cursor {
         this._hotSpot.y = 0;
     }
 
+    // Mouse events might be emulated, this allows
+    // moving the cursor in such cases
+    move(clientX, clientY) {
+        if (!useFallback) {
+            return;
+        }
+        // clientX/clientY are relative the _visual viewport_,
+        // but our position is relative the _layout viewport_,
+        // so try to compensate when we can
+        if (window.visualViewport) {
+            this._position.x = clientX + window.visualViewport.offsetLeft;
+            this._position.y = clientY + window.visualViewport.offsetTop;
+        } else {
+            this._position.x = clientX;
+            this._position.y = clientY;
+        }
+        this._updatePosition();
+        let target = document.elementFromPoint(clientX, clientY);
+        this._updateVisibility(target);
+    }
+
     _handleMouseOver(event) {
         // This event could be because we're entering the target, or
         // moving around amongst its sub elements. Let the move handler
@@ -132,7 +148,8 @@ export default class Cursor {
     }
 
     _handleMouseLeave(event) {
-        this._hideCursor();
+        // Check if we should show the cursor on the element we are leaving to
+        this._updateVisibility(event.relatedTarget);
     }
 
     _handleMouseMove(event) {
@@ -150,27 +167,29 @@ export default class Cursor {
         // now and adjust visibility based on that.
         let target = document.elementFromPoint(event.clientX, event.clientY);
         this._updateVisibility(target);
-    }
 
-    _handleTouchStart(event) {
-        // Just as for mouseover, we let the move handler deal with it
-        this._handleTouchMove(event);
-    }
-
-    _handleTouchMove(event) {
-        this._updateVisibility(event.target);
-
-        this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
-        this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
-
-        this._updatePosition();
-    }
-
-    _handleTouchEnd(event) {
-        // Same principle as for mouseup
-        let target = document.elementFromPoint(event.changedTouches[0].clientX,
-                                               event.changedTouches[0].clientY);
-        this._updateVisibility(target);
+        // Captures end with a mouseup but we can't know the event order of
+        // mouseup vs releaseCapture.
+        //
+        // In the cases when releaseCapture comes first, the code above is
+        // enough.
+        //
+        // In the cases when the mouseup comes first, we need wait for the
+        // browser to flush all events and then check again if the cursor
+        // should be visible.
+        if (this._captureIsActive()) {
+            window.setTimeout(() => {
+                // We might have detached at this point
+                if (!this._target) {
+                    return;
+                }
+                // Refresh the target from elementFromPoint since queued events
+                // might have altered the DOM
+                target = document.elementFromPoint(event.clientX,
+                                                   event.clientY);
+                this._updateVisibility(target);
+            }, 0);
+        }
     }
 
     _showCursor() {
@@ -189,6 +208,9 @@ export default class Cursor {
     // (i.e. are we over the target, or a child of the target without a
     // different cursor set)
     _shouldShowCursor(target) {
+        if (!target) {
+            return false;
+        }
         // Easy case
         if (target === this._target) {
             return true;
@@ -207,6 +229,11 @@ export default class Cursor {
     }
 
     _updateVisibility(target) {
+        // When the cursor target has capture we want to show the cursor.
+        // So, if a capture is active - look at the captured element instead.
+        if (this._captureIsActive()) {
+            target = document.captureElement;
+        }
         if (this._shouldShowCursor(target)) {
             this._showCursor();
         } else {
@@ -218,4 +245,9 @@ export default class Cursor {
         this._canvas.style.left = this._position.x + "px";
         this._canvas.style.top = this._position.y + "px";
     }
+
+    _captureIsActive() {
+        return document.captureElement &&
+            document.documentElement.contains(document.captureElement);
+    }
 }
diff --git a/systemvm/agent/noVNC/core/util/element.js b/systemvm/agent/noVNC/core/util/element.js
new file mode 100644
index 0000000..466a745
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/element.js
@@ -0,0 +1,32 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2020 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*
+ * HTML element utility functions
+ */
+
+export function clientToElement(x, y, elem) {
+    const bounds = elem.getBoundingClientRect();
+    let pos = { x: 0, y: 0 };
+    // Clip to target bounds
+    if (x < bounds.left) {
+        pos.x = 0;
+    } else if (x >= bounds.right) {
+        pos.x = bounds.width - 1;
+    } else {
+        pos.x = x - bounds.left;
+    }
+    if (y < bounds.top) {
+        pos.y = 0;
+    } else if (y >= bounds.bottom) {
+        pos.y = bounds.height - 1;
+    } else {
+        pos.y = y - bounds.top;
+    }
+    return pos;
+}
diff --git a/systemvm/agent/noVNC/core/util/events.js b/systemvm/agent/noVNC/core/util/events.js
index f122279..39eefd4 100644
--- a/systemvm/agent/noVNC/core/util/events.js
+++ b/systemvm/agent/noVNC/core/util/events.js
@@ -21,7 +21,8 @@ export function stopEvent(e) {
 
 // Emulate Element.setCapture() when not supported
 let _captureRecursion = false;
-let _captureElem = null;
+let _elementForUnflushedEvents = null;
+document.captureElement = null;
 function _captureProxy(e) {
     // Recursion protection as we'll see our own event
     if (_captureRecursion) return;
@@ -30,7 +31,11 @@ function _captureProxy(e) {
     const newEv = new e.constructor(e.type, e);
 
     _captureRecursion = true;
-    _captureElem.dispatchEvent(newEv);
+    if (document.captureElement) {
+        document.captureElement.dispatchEvent(newEv);
+    } else {
+        _elementForUnflushedEvents.dispatchEvent(newEv);
+    }
     _captureRecursion = false;
 
     // Avoid double events
@@ -48,58 +53,56 @@ function _captureProxy(e) {
 }
 
 // Follow cursor style of target element
-function _captureElemChanged() {
-    const captureElem = document.getElementById("noVNC_mouse_capture_elem");
-    captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
+function _capturedElemChanged() {
+    const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
+    proxyElem.style.cursor = window.getComputedStyle(document.captureElement).cursor;
 }
 
-const _captureObserver = new MutationObserver(_captureElemChanged);
-
-let _captureIndex = 0;
+const _captureObserver = new MutationObserver(_capturedElemChanged);
 
-export function setCapture(elem) {
-    if (elem.setCapture) {
+export function setCapture(target) {
+    if (target.setCapture) {
 
-        elem.setCapture();
+        target.setCapture();
+        document.captureElement = target;
 
         // IE releases capture on 'click' events which might not trigger
-        elem.addEventListener('mouseup', releaseCapture);
+        target.addEventListener('mouseup', releaseCapture);
 
     } else {
         // Release any existing capture in case this method is
         // called multiple times without coordination
         releaseCapture();
 
-        let captureElem = document.getElementById("noVNC_mouse_capture_elem");
+        let proxyElem = 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);
+        if (proxyElem === null) {
+            proxyElem = document.createElement("div");
+            proxyElem.id = "noVNC_mouse_capture_elem";
+            proxyElem.style.position = "fixed";
+            proxyElem.style.top = "0px";
+            proxyElem.style.left = "0px";
+            proxyElem.style.width = "100%";
+            proxyElem.style.height = "100%";
+            proxyElem.style.zIndex = 10000;
+            proxyElem.style.display = "none";
+            document.body.appendChild(proxyElem);
 
             // This is to make sure callers don't get confused by having
             // our blocking element as the target
-            captureElem.addEventListener('contextmenu', _captureProxy);
+            proxyElem.addEventListener('contextmenu', _captureProxy);
 
-            captureElem.addEventListener('mousemove', _captureProxy);
-            captureElem.addEventListener('mouseup', _captureProxy);
+            proxyElem.addEventListener('mousemove', _captureProxy);
+            proxyElem.addEventListener('mouseup', _captureProxy);
         }
 
-        _captureElem = elem;
-        _captureIndex++;
+        document.captureElement = target;
 
         // Track cursor and get initial cursor
-        _captureObserver.observe(elem, {attributes: true});
-        _captureElemChanged();
+        _captureObserver.observe(target, {attributes: true});
+        _capturedElemChanged();
 
-        captureElem.style.display = "";
+        proxyElem.style.display = "";
 
         // We listen to events on window in order to keep tracking if it
         // happens to leave the viewport
@@ -112,26 +115,26 @@ export function releaseCapture() {
     if (document.releaseCapture) {
 
         document.releaseCapture();
+        document.captureElement = null;
 
     } else {
-        if (!_captureElem) {
+        if (!document.captureElement) {
             return;
         }
 
-        // There might be events already queued, so we need to wait for
-        // them to flush. E.g. contextmenu in Microsoft Edge
-        window.setTimeout((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);
+        // There might be events already queued. The event proxy needs
+        // access to the captured element for these queued events.
+        // E.g. contextmenu (right-click) in Microsoft Edge
+        //
+        // Before removing the capturedElem pointer we save it to a
+        // temporary variable that the unflushed events can use.
+        _elementForUnflushedEvents = document.captureElement;
+        document.captureElement = null;
 
         _captureObserver.disconnect();
 
-        const captureElem = document.getElementById("noVNC_mouse_capture_elem");
-        captureElem.style.display = "none";
+        const proxyElem = document.getElementById("noVNC_mouse_capture_elem");
+        proxyElem.style.display = "none";
 
         window.removeEventListener('mousemove', _captureProxy);
         window.removeEventListener('mouseup', _captureProxy);
diff --git a/systemvm/agent/noVNC/core/util/eventtarget.js b/systemvm/agent/noVNC/core/util/eventtarget.js
index f54ca9b..a21aa54 100644
--- a/systemvm/agent/noVNC/core/util/eventtarget.js
+++ b/systemvm/agent/noVNC/core/util/eventtarget.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
diff --git a/systemvm/agent/noVNC/core/util/int.js b/systemvm/agent/noVNC/core/util/int.js
new file mode 100644
index 0000000..001f40f
--- /dev/null
+++ b/systemvm/agent/noVNC/core/util/int.js
@@ -0,0 +1,15 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2020 The noVNC Authors
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+export function toUnsigned32bit(toConvert) {
+    return toConvert >>> 0;
+}
+
+export function toSigned32bit(toConvert) {
+    return toConvert | 0;
+}
diff --git a/systemvm/agent/noVNC/core/util/logging.js b/systemvm/agent/noVNC/core/util/logging.js
index 4c8943d..fe449e9 100644
--- a/systemvm/agent/noVNC/core/util/logging.js
+++ b/systemvm/agent/noVNC/core/util/logging.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
@@ -10,18 +10,18 @@
  * Logging/debug routines
  */
 
-let _log_level = 'warn';
+let _logLevel = 'warn';
 
 let Debug = () => {};
 let Info = () => {};
 let Warn = () => {};
 let Error = () => {};
 
-export function init_logging(level) {
+export function initLogging(level) {
     if (typeof level === 'undefined') {
-        level = _log_level;
+        level = _logLevel;
     } else {
-        _log_level = level;
+        _logLevel = level;
     }
 
     Debug = Info = Warn = Error = () => {};
@@ -46,11 +46,11 @@ export function init_logging(level) {
     }
 }
 
-export function get_logging() {
-    return _log_level;
+export function getLogging() {
+    return _logLevel;
 }
 
 export { Debug, Info, Warn, Error };
 
 // Initialize logging level
-init_logging();
+initLogging();
diff --git a/systemvm/agent/noVNC/core/util/polyfill.js b/systemvm/agent/noVNC/core/util/polyfill.js
index 648ceeb..0e458c8 100644
--- a/systemvm/agent/noVNC/core/util/polyfill.js
+++ b/systemvm/agent/noVNC/core/util/polyfill.js
@@ -1,6 +1,6 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2020 The noVNC Authors
  * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
  */
 
@@ -52,3 +52,10 @@ if (typeof Object.assign != 'function') {
         window.CustomEvent = CustomEvent;
     }
 })();
+
+/* Number.isInteger() (taken from MDN) */
+Number.isInteger = Number.isInteger || function isInteger(value) {
+    return typeof value === 'number' &&
+      isFinite(value) &&
+      Math.floor(value) === value;
+};
diff --git a/systemvm/agent/noVNC/core/util/strings.js b/systemvm/agent/noVNC/core/util/strings.js
index 61f4f23..3dd4b29 100644
--- a/systemvm/agent/noVNC/core/util/strings.js
+++ b/systemvm/agent/noVNC/core/util/strings.js
@@ -1,14 +1,28 @@
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
  */
 
-/*
- * Decode from UTF-8
- */
-export function decodeUTF8(utf8string) {
-    return decodeURIComponent(escape(utf8string));
+// Decode from UTF-8
+export function decodeUTF8(utf8string, allowLatin1=false) {
+    try {
+        return decodeURIComponent(escape(utf8string));
+    } catch (e) {
+        if (e instanceof URIError) {
+            if (allowLatin1) {
+                // If we allow Latin1 we can ignore any decoding fails
+                // and in these cases return the original string
+                return utf8string;
+            }
+        }
+        throw e;
+    }
+}
+
+// Encode to UTF-8
+export function encodeUTF8(DOMString) {
+    return unescape(encodeURIComponent(DOMString));
 }
diff --git a/systemvm/agent/noVNC/core/websock.js b/systemvm/agent/noVNC/core/websock.js
index 51b9a66..3156aed 100644
--- a/systemvm/agent/noVNC/core/websock.js
+++ b/systemvm/agent/noVNC/core/websock.js
@@ -1,6 +1,6 @@
 /*
  * Websock: high-performance binary WebSockets
- * Copyright (C) 2018 The noVNC Authors
+ * Copyright (C) 2019 The noVNC Authors
  * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * Websock is similar to the standard WebSocket object but with extra
@@ -17,6 +17,8 @@ import * as Log from './util/logging.js';
 // 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.
+// Also copyWithin() for TypedArrays is not supported in IE 11 or
+// Safari 13 (at the moment we want to support Safari 11).
 const ENABLE_COPYWITHIN = false;
 const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024;  // 40 MiB
 
@@ -27,7 +29,6 @@ export default class Websock {
         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
 
@@ -143,7 +144,7 @@ export default class Websock {
 
     flush() {
         if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
-            this._websocket.send(this._encode_message());
+            this._websocket.send(this._encodeMessage());
             this._sQlen = 0;
         }
     }
@@ -154,7 +155,7 @@ export default class Websock {
         this.flush();
     }
 
-    send_string(str) {
+    sendString(str) {
         this.send(str.split('').map(chr => chr.charCodeAt(0)));
     }
 
@@ -167,13 +168,13 @@ export default class Websock {
         this._eventHandlers[evt] = handler;
     }
 
-    _allocate_buffers() {
+    _allocateBuffers() {
         this._rQ = new Uint8Array(this._rQbufferSize);
         this._sQ = new Uint8Array(this._sQbufferSize);
     }
 
     init() {
-        this._allocate_buffers();
+        this._allocateBuffers();
         this._rQi = 0;
         this._websocket = null;
     }
@@ -184,7 +185,7 @@ export default class Websock {
         this._websocket = new WebSocket(uri, protocols);
         this._websocket.binaryType = 'arraybuffer';
 
-        this._websocket.onmessage = this._recv_message.bind(this);
+        this._websocket.onmessage = this._recvMessage.bind(this);
         this._websocket.onopen = () => {
             Log.Debug('>> WebSock.onopen');
             if (this._websocket.protocol) {
@@ -219,42 +220,46 @@ export default class Websock {
     }
 
     // private methods
-    _encode_message() {
+    _encodeMessage() {
         // 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(min_fit) {
-        const resizeNeeded = min_fit || this.rQlen > this._rQbufferSize / 2;
+    // We want to move all the unread data to the start of the queue,
+    // e.g. compacting.
+    // The function also expands the receive que if needed, and for
+    // performance reasons we combine these two actions to avoid
+    // unneccessary copying.
+    _expandCompactRQ(minFit) {
+        // if we're using less than 1/8th of the buffer even with the incoming bytes, compact in place
+        // instead of resizing
+        const requiredBufferSize =  (this._rQlen - this._rQi + minFit) * 8;
+        const resizeNeeded = this._rQbufferSize < requiredBufferSize;
+
         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 + min_fit) * 8;
-            }
+            // Make sure we always *at least* double the buffer size, and have at least space for 8x
+            // the current amount of data
+            this._rQbufferSize = Math.max(this._rQbufferSize * 2, requiredBufferSize);
         }
 
         // 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 < min_fit) {
+            if (this._rQbufferSize - this.rQlen < minFit) {
                 throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
             }
         }
 
         if (resizeNeeded) {
-            const old_rQbuffer = this._rQ.buffer;
-            this._rQmax = this._rQbufferSize / 8;
+            const oldRQbuffer = this._rQ.buffer;
             this._rQ = new Uint8Array(this._rQbufferSize);
-            this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
+            this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
         } else {
             if (ENABLE_COPYWITHIN) {
-                this._rQ.copyWithin(0, this._rQi);
+                this._rQ.copyWithin(0, this._rQi, this._rQlen);
             } else {
-                this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
+                this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
             }
         }
 
@@ -262,26 +267,25 @@ export default class Websock {
         this._rQi = 0;
     }
 
-    _decode_message(data) {
-        // push arraybuffer values onto the end
+    // push arraybuffer values onto the end of the receive que
+    _DecodeMessage(data) {
         const u8 = new Uint8Array(data);
         if (u8.length > this._rQbufferSize - this._rQlen) {
-            this._expand_compact_rQ(u8.length);
+            this._expandCompactRQ(u8.length);
         }
         this._rQ.set(u8, this._rQlen);
         this._rQlen += u8.length;
     }
 
-    _recv_message(e) {
-        this._decode_message(e.data);
+    _recvMessage(e) {
+        this._DecodeMessage(e.data);
         if (this.rQlen > 0) {
             this._eventHandlers.message();
-            // Compact the receive queue
             if (this._rQlen == this._rQi) {
+                // All data has now been processed, this means we
+                // can reset the receive queue.
                 this._rQlen = 0;
                 this._rQi = 0;
-            } else if (this._rQlen > this._rQmax) {
-                this._expand_compact_rQ();
             }
         } else {
             Log.Debug("Ignoring empty message");
diff --git a/systemvm/agent/noVNC/docs/API-internal.md b/systemvm/agent/noVNC/docs/API-internal.md
deleted file mode 100644
index 0b29afb..0000000
--- a/systemvm/agent/noVNC/docs/API-internal.md
+++ /dev/null
@@ -1,122 +0,0 @@
-# 1. Internal Modules
-
-The noVNC client is composed of several internal modules that handle
-rendering, input, networking, etc. Each of the modules is designed to
-be cross-browser and independent from each other.
-
-Note however that the API of these modules is not guaranteed to be
-stable, and this documentation is not maintained as well as the
-official external API.
-
-
-## 1.1 Module List
-
-* __Mouse__ (core/input/mouse.js): Mouse input event handler with
-limited touch support.
-
-* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
-non-US keyboard support. Translates keyDown and keyUp events to X11
-keysym values.
-
-* __Display__ (core/display.js): Efficient 2D rendering abstraction
-layered on the HTML5 canvas element.
-
-* __Websock__ (core/websock.js): Websock client from websockify
-with transparent binary data support.
-[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page.
-
-
-## 1.2 Callbacks
-
-For the Mouse, Keyboard and Display objects the callback functions are
-assigned to configuration attributes, just as for the RFB object. The
-WebSock module has a method named 'on' that takes two parameters: the
-callback event name, and the callback function.
-
-## 2. Modules
-
-## 2.1 Mouse Module
-
-### 2.1.1 Configuration Attributes
-
-| name        | type | mode | default  | description
-| ----------- | ---- | ---- | -------- | ------------
-| touchButton | int  | RW   | 1        | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
-
-### 2.1.2 Methods
-
-| name   | parameters | description
-| ------ | ---------- | ------------
-| grab   | ()         | Begin capturing mouse events
-| ungrab | ()         | Stop capturing mouse events
-
-### 2.1.2 Callbacks
-
-| name          | parameters          | description
-| ------------- | ------------------- | ------------
-| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release
-| onmousemove   | (x, y)              | Handler for mouse movement
-
-
-## 2.2 Keyboard Module
-
-### 2.2.1 Configuration Attributes
-
-None
-
-### 2.2.2 Methods
-
-| name   | parameters | description
-| ------ | ---------- | ------------
-| grab   | ()         | Begin capturing keyboard events
-| ungrab | ()         | Stop capturing keyboard events
-
-### 2.2.3 Callbacks
-
-| name       | parameters           | description
-| ---------- | -------------------- | ------------
-| onkeypress | (keysym, code, down) | Handler for key press/release
-
-
-## 2.3 Display Module
-
-### 2.3.1 Configuration Attributes
-
-| name         | type  | mode | default | description
-| ------------ | ----- | ---- | ------- | ------------
-| logo         | raw   | RW   |         | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data}
-| scale        | float | RW   | 1.0     | Display area scale factor 0.0 - 1.0
-| clipViewport | bool  | RW   | false   | Use viewport clipping
-| width        | int   | RO   |         | Display area width
-| height       | int   | RO   |         | Display area height
-
-### 2.3.2 Methods
-
-| name               | parameters                                              | description
-| ------------------ | ------------------------------------------------------- | ------------
-| viewportChangePos  | (deltaX, deltaY)                                        | Move the viewport relative to the current location
-| viewportChangeSize | (width, height)                                         | Change size of the viewport
-| absX               | (x)                                                     | Return X relative to the remote display
-| absY               | (y)                                                     | Return Y relative to the remote display
-| resize             | (width, height)                                         | Set width and height
-| flip               | (from_queue)                                            | Update the visible canvas with the contents of the rendering canvas
-| clear              | ()                                                      | Clear the display (show logo if set)
-| pending            | ()                                                      | Check if there are waiting items in the render queue
-| flush              | ()                                                      | Resume processing the render queue unless it's empty
-| fillRect           | (x, y, width, height, color, from_queue)                | Draw a filled in rectangle
-| copyImage          | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area
-| imageRect          | (x, y, mime, arr)                                       | Draw a rectangle with an image
-| startTile          | (x, y, width, height, color)                            | Begin updating a tile
-| subTile            | (tile, x, y, w, h, color)                               | Update a sub-rectangle within the given tile
-| finishTile         | ()                                                      | Draw the current tile to the display
-| blitImage          | (x, y, width, height, arr, offset, from_queue)          | Blit pixels (of R,G,B,A) to the display
-| blitRgbImage       | (x, y, width, height, arr, offset, from_queue)          | Blit RGB encoded image to display
-| blitRgbxImage      | (x, y, width, height, arr, offset, from_queue)          | Blit RGBX encoded image to display
-| drawImage          | (img, x, y)                                             | Draw image and track damage
-| autoscale          | (containerWidth, containerHeight)                       | Scale the display
-
-### 2.3.3 Callbacks
-
-| name    | parameters | description
-| ------- | ---------- | ------------
-| onflush | ()         | A display flush has been requested and we are now ready to resume FBU processing
diff --git a/systemvm/agent/noVNC/docs/API.md b/systemvm/agent/noVNC/docs/API.md
deleted file mode 100644
index d587429..0000000
--- a/systemvm/agent/noVNC/docs/API.md
+++ /dev/null
@@ -1,375 +0,0 @@
-# noVNC API
-
-The interface of the noVNC client consists of a single RFB object that
-is instantiated once per connection.
-
-## RFB
-
-The `RFB` object represents a single connection to a VNC server. It
-communicates using a WebSocket that must provide a standard RFB
-protocol stream.
... 13080 lines suppressed ...