]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Merge branch 'api' of https://github.com/CendioOssman/noVNC
authorPierre Ossman <ossman@cendio.se>
Fri, 10 Nov 2017 13:19:05 +0000 (14:19 +0100)
committerPierre Ossman <ossman@cendio.se>
Fri, 10 Nov 2017 13:19:05 +0000 (14:19 +0100)
1  2 
app/ui.js
core/rfb.js
docs/API.md
vnc_lite.html

diff --combined app/ui.js
index da912f332ae03baea2ff0212cfdbcb4b2b320e8e,74735cce62af897cc90874b85c1c70f8557ce9c9..f661567458e17244acff64a654c8e37ce694ecbe
+++ b/app/ui.js
@@@ -13,7 -13,7 +13,7 @@@
  
  import * as Log from '../core/util/logging.js';
  import _, { l10n } from '../core/util/localization.js';
- import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js';
+ import { isTouchDevice } from '../core/util/browsers.js';
  import { setCapture, getPointerEvent } from '../core/util/events.js';
  import KeyTable from "../core/input/keysym.js";
  import keysyms from "../core/input/keysymdef.js";
@@@ -91,7 -91,7 +91,7 @@@ var UI = 
          UI.addControlbarHandlers();
          UI.addTouchSpecificHandlers();
          UI.addExtraKeysHandlers();
-         UI.addXvpHandlers();
+         UI.addMachineHandlers();
          UI.addConnectionControlHandlers();
          UI.addClipboardHandlers();
          UI.addSettingsHandlers();
          UI.initSetting('host', window.location.hostname);
          UI.initSetting('port', port);
          UI.initSetting('encrypt', (window.location.protocol === "https:"));
-         UI.initSetting('cursor', !isTouchDevice);
          UI.initSetting('view_clip', false);
          UI.initSetting('resize', 'off');
          UI.initSetting('shared', true);
          }
      },
  
-     initRFB: function() {
-         try {
-             UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'),
-                               'onNotification': UI.notification,
-                               'onUpdateState': UI.updateState,
-                               'onDisconnected': UI.disconnectFinished,
-                               'onPasswordRequired': UI.passwordRequired,
-                               'onXvpInit': UI.updateXvpButton,
-                               'onClipboard': UI.clipboardReceive,
-                               'onBell': UI.bell,
-                               'onFBUComplete': UI.initialResize,
-                               'onFBResize': UI.updateSessionSize,
-                               'onDesktopName': UI.updateDesktopName});
-             return true;
-         } catch (exc) {
-             var msg = "Unable to create RFB client -- " + exc;
-             Log.Error(msg);
-             UI.showStatus(msg, 'error');
-             return false;
-         }
-     },
  /* ------^-------
  *     /INIT
  * ==============
          document.getElementById("noVNC_keyboard_button")
              .addEventListener('click', UI.toggleVirtualKeyboard);
  
-         UI.touchKeyboard = new Keyboard({target: document.getElementById('noVNC_keyboardinput'),
-                                          onKeyEvent: UI.keyEvent});
+         UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
+         UI.touchKeyboard.onkeyevent = UI.keyEvent;
          UI.touchKeyboard.grab();
          document.getElementById("noVNC_keyboardinput")
              .addEventListener('input', UI.keyInput);
  
          document.documentElement
              .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
 -        document.documentElement
 -            .addEventListener('touchstart', UI.keepVirtualKeyboard, true);
  
          document.getElementById("noVNC_control_bar")
              .addEventListener('touchstart', UI.activateControlbar);
              .addEventListener('click', UI.sendCtrlAltDel);
      },
  
-     addXvpHandlers: function() {
-         document.getElementById("noVNC_xvp_shutdown_button")
-             .addEventListener('click', function() { UI.rfb.xvpShutdown(); });
-         document.getElementById("noVNC_xvp_reboot_button")
-             .addEventListener('click', function() { UI.rfb.xvpReboot(); });
-         document.getElementById("noVNC_xvp_reset_button")
-             .addEventListener('click', function() { UI.rfb.xvpReset(); });
-         document.getElementById("noVNC_xvp_button")
-             .addEventListener('click', UI.toggleXvpPanel);
+     addMachineHandlers: function() {
+         document.getElementById("noVNC_shutdown_button")
+             .addEventListener('click', function() { UI.rfb.machineShutdown(); });
+         document.getElementById("noVNC_reboot_button")
+             .addEventListener('click', function() { UI.rfb.machineReboot(); });
+         document.getElementById("noVNC_reset_button")
+             .addEventListener('click', function() { UI.rfb.machineReset(); });
+         document.getElementById("noVNC_power_button")
+             .addEventListener('click', UI.togglePowerPanel);
      },
  
      addConnectionControlHandlers: function() {
              .addEventListener('click', UI.toggleSettingsPanel);
  
          UI.addSettingChangeHandler('encrypt');
-         UI.addSettingChangeHandler('cursor');
-         UI.addSettingChangeHandler('cursor', UI.updateLocalCursor);
          UI.addSettingChangeHandler('resize');
          UI.addSettingChangeHandler('resize', UI.enableDisableViewClip);
          UI.addSettingChangeHandler('resize', UI.applyResizeMode);
   *     VISUAL
   * ------v------*/
  
-     updateState: function(rfb, state, oldstate) {
+     updateState: function(event) {
          var msg;
  
          document.documentElement.classList.remove("noVNC_connecting");
          document.documentElement.classList.remove("noVNC_disconnecting");
          document.documentElement.classList.remove("noVNC_reconnecting");
  
-         switch (state) {
+         switch (event.detail.state) {
              case 'connecting':
                  document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
                  document.documentElement.classList.add("noVNC_connecting");
              case 'connected':
                  UI.connected = true;
                  UI.inhibit_reconnect = false;
+                 UI.doneInitialResize = false;
                  document.documentElement.classList.add("noVNC_connected");
-                 if (rfb && rfb.get_encrypt()) {
+                 if (UI.getSetting('encrypt')) {
                      msg = _("Connected (encrypted) to ") + UI.desktopName;
                  } else {
                      msg = _("Connected (unencrypted) to ") + UI.desktopName;
  
          UI.enableDisableViewClip();
  
-         if (cursorURIsSupported() && !isTouchDevice) {
-             UI.enableSetting('cursor');
-         } else {
-             UI.disableSetting('cursor');
-         }
          if (UI.connected) {
              UI.disableSetting('encrypt');
              UI.disableSetting('shared');
              UI.enableSetting('port');
              UI.enableSetting('path');
              UI.enableSetting('repeaterID');
-             UI.updateXvpButton(0);
+             UI.updatePowerButton();
              UI.keepControlbar();
          }
  
          // Hide input related buttons in view only mode
-         if (UI.rfb && UI.rfb.get_view_only()) {
+         if (UI.rfb && UI.rfb.viewOnly) {
              document.getElementById('noVNC_keyboard_button')
                  .classList.add('noVNC_hidden');
              document.getElementById('noVNC_toggle_extra_keys_button')
          document.getElementById('noVNC_status').classList.remove("noVNC_open");
      },
  
-     notification: function (rfb, msg, level, options) {
-         UI.showStatus(msg, level);
+     notification: function (e) {
+         UI.showStatus(e.detail.message, e.detail.level);
      },
  
      activateControlbar: function(event) {
  
      closeAllPanels: function() {
          UI.closeSettingsPanel();
-         UI.closeXvpPanel();
+         UI.closePowerPanel();
          UI.closeClipboardPanel();
          UI.closeExtraKeys();
      },
  
          // Refresh UI elements from saved cookies
          UI.updateSetting('encrypt');
-         if (cursorURIsSupported()) {
-             UI.updateSetting('cursor');
-         } else {
-             UI.updateSetting('cursor', !isTouchDevice);
-             UI.disableSetting('cursor');
-         }
          UI.updateSetting('view_clip');
          UI.updateSetting('resize');
          UI.updateSetting('shared');
  /* ------^-------
   *   /SETTINGS
   * ==============
-  *      XVP
+  *     POWER
   * ------v------*/
  
-     openXvpPanel: function() {
+     openPowerPanel: function() {
          UI.closeAllPanels();
          UI.openControlbar();
  
-         document.getElementById('noVNC_xvp')
+         document.getElementById('noVNC_power')
              .classList.add("noVNC_open");
-         document.getElementById('noVNC_xvp_button')
+         document.getElementById('noVNC_power_button')
              .classList.add("noVNC_selected");
      },
  
-     closeXvpPanel: function() {
-         document.getElementById('noVNC_xvp')
+     closePowerPanel: function() {
+         document.getElementById('noVNC_power')
              .classList.remove("noVNC_open");
-         document.getElementById('noVNC_xvp_button')
+         document.getElementById('noVNC_power_button')
              .classList.remove("noVNC_selected");
      },
  
-     toggleXvpPanel: function() {
-         if (document.getElementById('noVNC_xvp')
+     togglePowerPanel: function() {
+         if (document.getElementById('noVNC_power')
              .classList.contains("noVNC_open")) {
-             UI.closeXvpPanel();
+             UI.closePowerPanel();
          } else {
-             UI.openXvpPanel();
+             UI.openPowerPanel();
          }
      },
  
-     // Disable/enable XVP button
-     updateXvpButton: function(ver) {
-         if (ver >= 1 && !UI.rfb.get_view_only()) {
-             document.getElementById('noVNC_xvp_button')
+     // Disable/enable power button
+     updatePowerButton: function() {
+         if (UI.connected &&
+             UI.rfb.capabilities.power &&
+             !UI.rfb.viewOnly) {
+             document.getElementById('noVNC_power_button')
                  .classList.remove("noVNC_hidden");
          } else {
-             document.getElementById('noVNC_xvp_button')
+             document.getElementById('noVNC_power_button')
                  .classList.add("noVNC_hidden");
-             // Close XVP panel if open
-             UI.closeXvpPanel();
+             // Close power panel if open
+             UI.closePowerPanel();
          }
      },
  
  /* ------^-------
-  *     /XVP
+  *    /POWER
   * ==============
   *   CLIPBOARD
   * ------v------*/
          }
      },
  
-     clipboardReceive: function(rfb, text) {
-         Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
-         document.getElementById('noVNC_clipboard_text').value = text;
+     clipboardReceive: function(e) {
+         Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0,40) + "...");
+         document.getElementById('noVNC_clipboard_text').value = e.detail.text;
          Log.Debug("<< UI.clipboardReceive");
      },
  
              return;
          }
  
-         if (!UI.initRFB()) return;
          UI.closeAllPanels();
          UI.closeConnectPanel();
  
-         UI.rfb.set_encrypt(UI.getSetting('encrypt'));
-         UI.rfb.set_shared(UI.getSetting('shared'));
-         UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
-         UI.updateLocalCursor();
          UI.updateViewOnly();
  
-         UI.rfb.connect(host, port, password, path);
+         var url;
+         url = UI.getSetting('encrypt') ? 'wss' : 'ws';
+         url += '://' + host;
+         if(port) {
+             url += ':' + port;
+         }
+         url += '/' + path;
+         UI.rfb = new RFB(document.getElementById('noVNC_canvas'), url,
+                          { shared: UI.getSetting('shared'),
+                            repeaterID: UI.getSetting('repeaterID'),
+                            credentials: { password: password } });
+         UI.rfb.addEventListener("notification", UI.notification);
+         UI.rfb.addEventListener("updatestate", UI.updateState);
+         UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
+         UI.rfb.addEventListener("credentialsrequired", UI.credentials);
+         UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); });
+         UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
+         UI.rfb.addEventListener("bell", UI.bell);
+         UI.rfb.addEventListener("fbresize", UI.updateSessionSize);
+         UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
      },
  
      disconnect: function() {
          // Disable automatic reconnecting
          UI.inhibit_reconnect = true;
  
-         // Restore the callback used for initial resize
-         UI.rfb.set_onFBUComplete(UI.initialResize);
          // Don't display the connection settings until we're actually disconnected
      },
  
          UI.connect(null, UI.reconnect_password);
      },
  
-     disconnectFinished: function (rfb, reason) {
-         if (typeof reason !== 'undefined') {
-             UI.showStatus(reason, 'error');
+     disconnectFinished: function (e) {
+         if (typeof e.detail.reason !== 'undefined') {
+             UI.showStatus(e.detail.reason, 'error');
          } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
              document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
              document.documentElement.classList.add("noVNC_reconnecting");
   *   PASSWORD
   * ------v------*/
  
-     passwordRequired: function(rfb, msg) {
+     credentials: function(e) {
+         // FIXME: handle more types
          document.getElementById('noVNC_password_dlg')
              .classList.add('noVNC_open');
  
                  document.getElementById('noVNC_password_input').focus();
              }, 100);
  
-         if (typeof msg === 'undefined') {
-             msg = _("Password is required");
-         }
+         var msg = _("Password is required");
          Log.Warn(msg);
          UI.showStatus(msg, "warning");
      },
  
      setPassword: function(e) {
+         // Prevent actually submitting the form
+         e.preventDefault();
          var inputElem = document.getElementById('noVNC_password_input');
          var password = inputElem.value;
          // Clear the input after reading the password
          inputElem.value = "";
-         UI.rfb.sendPassword(password);
+         UI.rfb.sendCredentials({ password: password });
          UI.reconnect_password = password;
          document.getElementById('noVNC_password_dlg')
              .classList.remove('noVNC_open');
-         // Prevent actually submitting the form
-         e.preventDefault();
      },
  
  /* ------^-------
  
          var screen = UI.screenSize();
  
-         if (screen && UI.connected && UI.rfb.get_display()) {
+         if (screen && UI.connected) {
  
-             var display = UI.rfb.get_display();
              var resizeMode = UI.getSetting('resize');
-             display.set_scale(1);
+             UI.rfb.viewportScale = 1.0;
  
              // Make sure the viewport is adjusted first
              UI.updateViewClip();
          if (!UI.rfb) return;
  
          var resizeMode = UI.getSetting('resize');
-         if (resizeMode !== 'scale' && resizeMode !== 'downscale') {
+         if (resizeMode !== 'scale') {
              return;
          }
  
          var screen = UI.screenSize();
  
-         if (!screen || !UI.connected || !UI.rfb.get_display()) {
+         if (!screen || !UI.connected) {
              return;
          }
  
-         var display = UI.rfb.get_display();
-         var downscaleOnly = resizeMode === 'downscale';
-         display.autoscale(screen.w, screen.h, downscaleOnly);
+         UI.rfb.autoscale(screen.w, screen.h);
          UI.fixScrollbars();
      },
  
      // Normally we only apply the current resize mode after a window resize
      // event. This means that when a new connection is opened, there is no
      // resize mode active.
-     // We have to wait until the first FBU because this is where the client
-     // will find the supported encodings of the server. Some calls later in
-     // the chain is dependant on knowing the server-capabilities.
-     initialResize: function(rfb, fbu) {
+     // We have to wait until we know the capabilities of the server as
+     // some calls later in the chain is dependant on knowing the
+     // server-capabilities.
+     initialResize: function() {
+         if (UI.doneInitialResize) return;
          UI.applyResizeMode();
-         // After doing this once, we remove the callback.
-         UI.rfb.set_onFBUComplete(function() { });
+         UI.doneInitialResize = true;
      },
  
  /* ------^-------
      updateViewClip: function() {
          if (!UI.rfb) return;
  
-         var display = UI.rfb.get_display();
-         var cur_clip = display.get_viewport();
+         var cur_clip = UI.rfb.clipViewport;
          var new_clip = UI.getSetting('view_clip');
  
          var resizeSetting = UI.getSetting('resize');
-         if (resizeSetting === 'downscale' || resizeSetting === 'scale') {
+         if (resizeSetting === 'scale') {
              // Disable viewport clipping if we are scaling
              new_clip = false;
          } else if (isTouchDevice) {
          }
  
          if (cur_clip !== new_clip) {
-             display.set_viewport(new_clip);
+             UI.rfb.clipViewport = new_clip;
          }
  
          var size = UI.screenSize();
          if (new_clip && size) {
              // When clipping is enabled, the screen is limited to
              // the size of the browser window.
-             display.viewportChangeSize(size.w, size.h);
+             UI.rfb.viewportChangeSize(size.w, size.h);
              UI.fixScrollbars();
          }
  
      enableDisableViewClip: function() {
          var resizeSetting = UI.getSetting('resize');
          // Disable clipping if we are scaling, connected or on touch
-         if (resizeSetting === 'downscale' || resizeSetting === 'scale' ||
+         if (resizeSetting === 'scale' ||
              isTouchDevice) {
              UI.disableSetting('view_clip');
          } else {
      toggleViewDrag: function() {
          if (!UI.rfb) return;
  
-         var drag = UI.rfb.get_viewportDrag();
+         var drag = UI.rfb.dragViewport;
          UI.setViewDrag(!drag);
       },
  
      setViewDrag: function(drag) {
          if (!UI.rfb) return;
  
-         UI.rfb.set_viewportDrag(drag);
+         UI.rfb.dragViewport = drag;
  
          UI.updateViewDrag();
      },
  
          // Check if viewport drag is possible. It is only possible
          // if the remote display is clipping the client display.
-         if (UI.rfb.get_display().get_viewport() &&
-             UI.rfb.get_display().clippingDisplay()) {
+         if (UI.rfb.clipViewport && UI.rfb.isClipped) {
              clipping = true;
          }
  
          var viewDragButton = document.getElementById('noVNC_view_drag_button');
  
          if (!clipping &&
-             UI.rfb.get_viewportDrag()) {
+             UI.rfb.dragViewport) {
              // The size of the remote display is the same or smaller
              // than the client display. Make sure viewport drag isn't
              // active when it can't be used.
-             UI.rfb.set_viewportDrag(false);
+             UI.rfb.dragViewport = false;
          }
  
-         if (UI.rfb.get_viewportDrag()) {
+         if (UI.rfb.dragViewport) {
              viewDragButton.classList.add("noVNC_selected");
          } else {
              viewDragButton.classList.remove("noVNC_selected");
      onfocusVirtualKeyboard: function(event) {
          document.getElementById('noVNC_keyboard_button')
              .classList.add("noVNC_selected");
 +        if (UI.rfb) {
 +            UI.rfb.set_focus_on_click(false);
 +        }
      },
  
      onblurVirtualKeyboard: function(event) {
          document.getElementById('noVNC_keyboard_button')
              .classList.remove("noVNC_selected");
 +        if (UI.rfb) {
 +            UI.rfb.set_focus_on_click(true);
 +        }
      },
  
      keepVirtualKeyboard: function(event) {
              }
          }
  
 -        // The default action of touchstart is to generate other
 -        // events, which other elements might depend on. So we can't
 -        // blindly prevent that. Instead restore focus right away.
 -        if (event.type === "touchstart") {
 -            setTimeout(input.focus.bind(input));
 -        } else {
 -            event.preventDefault();
 -        }
 +        event.preventDefault();
      },
  
      keyboardinputReset: function() {
   * ------v------*/
  
      setMouseButton: function(num) {
-         var view_only = UI.rfb.get_view_only();
+         var view_only = UI.rfb.viewOnly;
          if (UI.rfb && !view_only) {
-             UI.rfb.get_mouse().set_touchButton(num);
+             UI.rfb.touchButton = num;
          }
  
          var blist = [0, 1,2,4];
          }
      },
  
-     updateLocalCursor: function() {
-         if (!UI.rfb) return;
-         UI.rfb.set_local_cursor(UI.getSetting('cursor'));
-     },
      updateViewOnly: function() {
          if (!UI.rfb) return;
-         UI.rfb.set_view_only(UI.getSetting('view_only'));
+         UI.rfb.viewOnly = UI.getSetting('view_only');
      },
  
      updateLogging: function() {
          WebUtil.init_logging(UI.getSetting('logging'));
      },
  
-     updateSessionSize: function(rfb, width, height) {
+     updateSessionSize: function(e) {
          UI.updateViewClip();
          UI.updateScaling();
          UI.fixScrollbars();
          screen.style.overflow = "";
      },
  
-     updateDesktopName: function(rfb, name) {
-         UI.desktopName = name;
+     updateDesktopName: function(e) {
+         UI.desktopName = e.detail.name;
          // Display the desktop name in the document title
-         document.title = name + " - noVNC";
+         document.title = e.detail.name + " - noVNC";
      },
  
-     bell: function(rfb) {
+     bell: function(e) {
          if (WebUtil.getConfigVar('bell', 'on') === 'on') {
              var promise = document.getElementById('noVNC_bell').play();
              // The standards disagree on the return value here
diff --combined core/rfb.js
index a16093b6aaadbc080a12eec3117c5ef8675b4a22,a3546b0029c1fd80d656b9451f4d4b5632b158c7..3d12ae4ba94825159a993fb2b18caaa705d7a7d8
@@@ -13,7 -13,8 +13,8 @@@
  import * as Log from './util/logging.js';
  import _ from './util/localization.js';
  import { decodeUTF8 } from './util/strings.js';
- import { set_defaults, make_properties } from './util/properties.js';
+ import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
+ import EventTargetMixin from './util/eventtarget.js';
  import Display from "./display.js";
  import Keyboard from "./input/keyboard.js";
  import Mouse from "./input/mouse.js";
@@@ -24,50 -25,78 +25,78 @@@ import KeyTable from "./input/keysym.js
  import XtScancode from "./input/xtscancodes.js";
  import Inflator from "./inflator.js";
  import { encodings, encodingName } from "./encodings.js";
+ import "./util/polyfill.js";
  
  /*jslint white: false, browser: true */
  /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
  
- export default function RFB(defaults) {
-     "use strict";
-     if (!defaults) {
-         defaults = {};
+ // How many seconds to wait for a disconnect to finish
+ var DISCONNECT_TIMEOUT = 3;
+ export default function RFB(target, url, options) {
+     if (!target) {
+         throw Error("Must specify target");
+     }
+     if (!url) {
+         throw Error("Must specify URL");
      }
  
-     this._rfb_host = '';
-     this._rfb_port = 5900;
-     this._rfb_password = '';
-     this._rfb_path = '';
+     this._target = target;
+     this._url = url;
+     // Connection details
+     options = options || {}
+     this._rfb_credentials = options.credentials || {};
+     this._shared = 'shared' in options ? !!options.shared : true;
+     this._repeaterID = options.repeaterID || '';
  
+     // Internal state
      this._rfb_connection_state = '';
      this._rfb_init_state = '';
-     this._rfb_version = 0;
-     this._rfb_max_version = 3.8;
      this._rfb_auth_scheme = '';
      this._rfb_disconnect_reason = "";
  
+     // Server capabilities
+     this._rfb_version = 0;
+     this._rfb_max_version = 3.8;
      this._rfb_tightvnc = false;
      this._rfb_xvp_ver = 0;
  
-     this._encHandlers = {};
-     this._encStats = {};
+     this._fb_width = 0;
+     this._fb_height = 0;
+     this._fb_name = "";
  
+     this._capabilities = { power: false, resize: false };
+     this._supportsFence = false;
+     this._supportsContinuousUpdates = false;
+     this._enabledContinuousUpdates = false;
+     this._supportsSetDesktopSize = false;
+     this._screen_id = 0;
+     this._screen_flags = 0;
+     this._qemuExtKeyEventSupported = false;
+     // 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._disconnTimer = null;      // disconnection timer
  
-     this._supportsFence = false;
+     // Timers
+     this._disconnTimer = null;      // disconnection timer
  
-     this._supportsContinuousUpdates = false;
-     this._enabledContinuousUpdates = false;
+     // Decoder states and stats
+     this._encHandlers = {};
+     this._encStats = {};
  
-     // Frame buffer update state
      this._FBU = {
          rects: 0,
-         subrects: 0,            // RRE
+         subrects: 0,            // RRE and HEXTILE
          lines: 0,               // RAW
          tiles: 0,               // HEXTILE
          bytes: 0,
          encoding: 0,
          subencoding: -1,
          background: null,
-         zlib: []                // TIGHT zlib streams
+         zlibs: []               // TIGHT zlib streams
      };
-     this._fb_width = 0;
-     this._fb_height = 0;
-     this._fb_name = "";
+     for (var i = 0; i < 4; i++) {
+         this._FBU.zlibs[i] = new Inflator();
+     }
  
      this._destBuff = null;
      this._paletteBuff = new Uint8Array(1024);  // 256 * 4 (max palette size * max bytes-per-pixel)
          pixels: 0
      };
  
-     this._supportsSetDesktopSize = false;
-     this._screen_id = 0;
-     this._screen_flags = 0;
      // Mouse state
      this._mouse_buttonMask = 0;
      this._mouse_arr = [];
      this._viewportDragPos = {};
      this._viewportHasMoved = false;
  
-     // QEMU Extended Key Event support - default to false
-     this._qemuExtKeyEventSupported = false;
-     // set the default value on user-facing properties
-     set_defaults(this, defaults, {
-         'target': 'null',                       // VNC display rendering Canvas object
-         'encrypt': false,                       // Use TLS/SSL/wss encryption
-         'local_cursor': false,                  // Request locally rendered cursor
-         'shared': true,                         // Request shared mode
-         'view_only': false,                     // Disable client mouse/keyboard
-         'focus_on_click': true,                 // Grab focus on canvas on mouse click
-         'xvp_password_sep': '@',                // Separator for XVP password fields
-         'disconnectTimeout': 3,                 // Time (s) to wait for disconnection
-         'wsProtocols': ['binary'],              // Protocols to use in the WebSocket connection
-         'repeaterID': '',                       // [UltraVNC] RepeaterID to connect to
-         'viewportDrag': false,                  // Move the viewport on mouse drags
-         // Callback functions
-         'onUpdateState': function () { },       // onUpdateState(rfb, state, oldstate): connection state change
-         'onNotification': function () { },      // onNotification(rfb, msg, level, options): notification for UI
-         'onDisconnected': function () { },      // onDisconnected(rfb, reason): disconnection finished
-         'onPasswordRequired': function () { },  // onPasswordRequired(rfb, msg): VNC password is required
-         'onClipboard': function () { },         // onClipboard(rfb, text): RFB clipboard contents received
-         'onBell': function () { },              // onBell(rfb): RFB Bell message received
-         'onFBUReceive': function () { },        // onFBUReceive(rfb, rect): RFB FBU rect received but not yet processed
-         'onFBUComplete': function () { },       // onFBUComplete(rfb): RFB FBU received and processed
-         'onFBResize': function () { },          // onFBResize(rfb, width, height): frame buffer resized
-         'onDesktopName': function () { },       // onDesktopName(rfb, name): desktop name received
-         'onXvpInit': function () { }            // onXvpInit(version): XVP extensions active for this connection
-     });
 +    // Bound event handlers
 +    this._eventHandlers = {
 +        focusCanvas: this._focusCanvas.bind(this),
 +    };
 +
      // main setup
      Log.Debug(">> RFB.constructor");
  
      // NB: nothing that needs explicit teardown should be done
      // before this point, since this can throw an exception
      try {
-         this._display = new Display({target: this._target,
-                                      onFlush: this._onFlush.bind(this)});
+         this._display = new Display(this._target);
      } catch (exc) {
          Log.Error("Display exception: " + exc);
          throw exc;
      }
+     this._display.onflush = this._onFlush.bind(this);
+     this._display.clear();
  
-     this._keyboard = new Keyboard({target: this._target,
-                                    onKeyEvent: this._handleKeyEvent.bind(this)});
+     this._keyboard = new Keyboard(this._target);
+     this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
  
-     this._mouse = new Mouse({target: this._target,
-                              onMouseButton: this._handleMouseButton.bind(this),
-                              onMouseMove: this._handleMouseMove.bind(this)});
+     this._mouse = new Mouse(this._target);
+     this._mouse.onmousebutton = this._handleMouseButton.bind(this);
+     this._mouse.onmousemove = this._handleMouseMove.bind(this);
  
      this._sock = new Websock();
      this._sock.on('message', this._handle_message.bind(this));
          Log.Warn("WebSocket on-error event");
      });
  
-     this._init_vars();
-     this._cleanup();
-     var rmode = this._display.get_render_mode();
-     Log.Info("Using native WebSockets, render mode: " + rmode);
+     // Slight delay of the actual connection so that the caller has
+     // time to set up callbacks
+     setTimeout(this._updateConnectionState.bind(this, 'connecting'));
  
      Log.Debug("<< RFB.constructor");
  };
  
  RFB.prototype = {
-     // Public methods
-     connect: function (host, port, password, path) {
-         this._rfb_host = host;
-         this._rfb_port = port;
-         this._rfb_password = (password !== undefined) ? password : "";
-         this._rfb_path = (path !== undefined) ? path : "";
+     // ===== PROPERTIES =====
  
-         if (!this._rfb_host) {
-             return this._fail(
-                 _("Must set host"));
-         }
+     dragViewport: false,
++    focusOnClick: true,
  
-         this._rfb_init_state = '';
-         this._updateConnectionState('connecting');
-         return true;
+     _viewOnly: false,
+     get viewOnly() { return this._viewOnly; },
+     set viewOnly(viewOnly) {
+         this._viewOnly = viewOnly;
+         if (this._rfb_connection_state === "connecting" ||
+             this._rfb_connection_state === "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 viewportScale() { return this._display.scale; },
+     set viewportScale(scale) { this._display.scale = scale; },
+     get clipViewport() { return this._display.clipViewport; },
+     set clipViewport(viewport) { this._display.clipViewport = viewport; },
+     get isClipped() { return this._display.isClipped; },
+     // ===== PUBLIC METHODS =====
      disconnect: function () {
          this._updateConnectionState('disconnecting');
          this._sock.off('error');
          this._sock.off('open');
      },
  
-     sendPassword: function (passwd) {
-         this._rfb_password = passwd;
+     sendCredentials: function (creds) {
+         this._rfb_credentials = creds;
          setTimeout(this._init_msg.bind(this), 0);
      },
  
      sendCtrlAltDel: function () {
-         if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
+         if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
          Log.Info("Sending Ctrl-Alt-Del");
  
          this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
          this.sendKey(KeyTable.XK_Delete, "Delete", false);
          this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
          this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
-         return true;
-     },
-     xvpOp: function (ver, op) {
-         if (this._rfb_xvp_ver < ver) { return false; }
-         Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
-         this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
-         return true;
      },
  
-     xvpShutdown: function () {
-         return this.xvpOp(1, 2);
+     machineShutdown: function () {
+         this._xvpOp(1, 2);
      },
  
-     xvpReboot: function () {
-         return this.xvpOp(1, 3);
+     machineReboot: function () {
+         this._xvpOp(1, 3);
      },
  
-     xvpReset: function () {
-         return this.xvpOp(1, 4);
+     machineReset: function () {
+         this._xvpOp(1, 4);
      },
  
      // Send a key press. If 'down' is not specified then send a down key
      // followed by an up key.
      sendKey: function (keysym, code, down) {
-         if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
+         if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
  
          if (down === undefined) {
              this.sendKey(keysym, code, true);
              this.sendKey(keysym, code, false);
-             return true;
+             return;
          }
  
          var scancode = XtScancode[code];
              RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
          } else {
              if (!keysym) {
-                 return false;
+                 return;
              }
              Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
              RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
          }
-         return true;
      },
  
      clipboardPasteFrom: function (text) {
-         if (this._rfb_connection_state !== 'connected' || this._view_only) { return; }
+         if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
          RFB.messages.clientCutText(this._sock, text);
      },
  
+     autoscale: function (width, height) {
+         if (this._rfb_connection_state !== 'connected') { return; }
+         this._display.autoscale(width, height);
+     },
+     viewportChangeSize: function(width, height) {
+         if (this._rfb_connection_state !== 'connected') { return; }
+         this._display.viewportChangeSize(width, height);
+     },
      // Requests a change of remote desktop size. This message is an extension
      // and may only be sent if we have received an ExtendedDesktopSize message
      requestDesktopSize: function (width, height) {
          if (this._rfb_connection_state !== 'connected' ||
-             this._view_only) {
-             return false;
+             this._viewOnly) {
+             return;
          }
  
-         if (this._supportsSetDesktopSize) {
-             RFB.messages.setDesktopSize(this._sock, width, height,
-                                         this._screen_id, this._screen_flags);
-             this._sock.flush();
-             return true;
-         } else {
-             return false;
+         if (!this._supportsSetDesktopSize) {
+             return;
          }
+         RFB.messages.setDesktopSize(this._sock, width, height,
+                                     this._screen_id, this._screen_flags);
      },
  
  
-     // Private methods
+     // ===== PRIVATE METHODS =====
  
      _connect: function () {
          Log.Debug(">> RFB.connect");
-         this._init_vars();
  
-         var uri;
-         if (typeof UsingSocketIO !== 'undefined') {
-             uri = 'http';
-         } else {
-             uri = this._encrypt ? 'wss' : 'ws';
-         }
-         uri += '://' + this._rfb_host;
-         if(this._rfb_port) {
-             uri += ':' + this._rfb_port;
-         }
-         uri += '/' + this._rfb_path;
-         Log.Info("connecting to " + uri);
+         Log.Info("connecting to " + this._url);
  
          try {
              // WebSocket.onopen transitions to the RFB init states
-             this._sock.open(uri, this._wsProtocols);
+             this._sock.open(this._url, ['binary']);
          } catch (e) {
              if (e.name === 'SyntaxError') {
                  this._fail("Invalid host or port value given", e);
          }
  
          // Always grab focus on some kind of click event
 -        this._target.addEventListener("mousedown", this._focusCanvas);
 -        this._target.addEventListener("touchstart", this._focusCanvas);
 +        this._target.addEventListener("mousedown", this._eventHandlers.focusCanvas);
 +        this._target.addEventListener("touchstart", this._eventHandlers.focusCanvas);
  
          Log.Debug("<< RFB.connect");
      },
  
      _disconnect: function () {
          Log.Debug(">> RFB.disconnect");
 -        this._target.removeEventListener("mousedown", this._focusCanvas);
 -        this._target.removeEventListener("touchstart", this._focusCanvas);
 +        this._target.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
 +        this._target.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
          this._cleanup();
          this._sock.close();
          this._print_stats();
          Log.Debug("<< RFB.disconnect");
      },
  
-     _init_vars: function () {
-         // reset state
-         this._FBU.rects        = 0;
-         this._FBU.subrects     = 0;  // RRE and HEXTILE
-         this._FBU.lines        = 0;  // RAW
-         this._FBU.tiles        = 0;  // HEXTILE
-         this._FBU.zlibs        = []; // TIGHT zlib encoders
-         this._mouse_buttonMask = 0;
-         this._mouse_arr        = [];
-         this._rfb_tightvnc     = false;
-         // Clear the per connection encoding stats
-         var stats = this._encStats;
-         Object.keys(stats).forEach(function (key) {
-             stats[key][0] = 0;
-         });
-         var i;
-         for (i = 0; i < 4; i++) {
-             this._FBU.zlibs[i] = new Inflator();
-         }
-     },
      _print_stats: function () {
          var stats = this._encStats;
  
      },
  
      _cleanup: function () {
-         if (!this._view_only) { this._keyboard.ungrab(); }
-         if (!this._view_only) { this._mouse.ungrab(); }
+         if (!this._viewOnly) { this._keyboard.ungrab(); }
+         if (!this._viewOnly) { this._mouse.ungrab(); }
          this._display.defaultCursor();
          if (Log.get_logging() !== 'debug') {
-             // Show noVNC logo on load and when disconnected, unless in
+             // Show noVNC logo when disconnected, unless in
              // debug mode
              this._display.clear();
          }
      },
  
 -    // Event handler for canvas so this points to the canvas element
      _focusCanvas: function(event) {
          // Respect earlier handlers' request to not do side-effects
 -        if (!event.defaultPrevented)
 -            this.focus();
 +        if (event.defaultPrevented) {
 +            return;
 +        }
 +
 +        if (!this._focus_on_click) {
 +            return;
 +        }
 +
 +        this._target.focus();
      },
  
      /*
          // State change actions
  
          this._rfb_connection_state = state;
-         this._onUpdateState(this, state, oldstate);
+         var event = new CustomEvent("updatestate", { detail: { state: state } });
+         this.dispatchEvent(event);
  
          var smsg = "New state '" + state + "', was '" + oldstate + "'.";
          Log.Debug(smsg);
  
          switch (state) {
              case 'disconnected':
-                 // Call onDisconnected callback after onUpdateState since
+                 // Fire disconnected event after updatestate event since
                  // we don't know if the UI only displays the latest message
                  if (this._rfb_disconnect_reason !== "") {
-                     this._onDisconnected(this, this._rfb_disconnect_reason);
+                     event = new CustomEvent("disconnect",
+                                             { detail: { reason: this._rfb_disconnect_reason } });
                  } else {
                      // No reason means clean disconnect
-                     this._onDisconnected(this);
+                     event = new CustomEvent("disconnect", { detail: {} });
                  }
+                 this.dispatchEvent(event);
                  break;
  
              case 'connecting':
                  this._disconnTimer = setTimeout(function () {
                      this._rfb_disconnect_reason = _("Disconnect timeout");
                      this._updateConnectionState('disconnected');
-                 }.bind(this), this._disconnectTimeout * 1000);
+                 }.bind(this), DISCONNECT_TIMEOUT * 1000);
                  break;
          }
      },
       * Send a notification to the UI. Valid levels are:
       *   'normal'|'warn'|'error'
       *
-      *   NOTE: Options could be added in the future.
       *   NOTE: If this function is called multiple times, remember that the
       *         interface could be only showing the latest notification.
       */
-     _notification: function(msg, level, options) {
+     _notification: function(msg, level) {
          switch (level) {
              case 'normal':
              case 'warn':
                  return;
          }
  
-         if (options) {
-             this._onNotification(this, msg, level, options);
-         } else {
-             this._onNotification(this, msg, level);
-         }
+         var event = new CustomEvent("notification",
+                                     { detail: { message: msg, level: level } });
+         this.dispatchEvent(event);
+     },
+     _setCapability: function (cap, val) {
+         this._capabilities[cap] = val;
+         var event = new CustomEvent("capabilities",
+                                     { detail: { capabilities: this._capabilities } });
+         this.dispatchEvent(event);
      },
  
      _handle_message: function () {
              this._mouse_buttonMask &= ~bmask;
          }
  
-         if (this._viewportDrag) {
+         if (this.dragViewport) {
              if (down && !this._viewportDragging) {
                  this._viewportDragging = true;
                  this._viewportDragPos = {'x': x, 'y': y};
  
                  // If the viewport didn't actually move, then treat as a mouse click event
                  // Send the button down event here, as the button up event is sent at the end of this function
-                 if (!this._viewportHasMoved && !this._view_only) {
+                 if (!this._viewportHasMoved && !this._viewOnly) {
                      RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask);
                  }
                  this._viewportHasMoved = false;
              }
          }
  
-         if (this._view_only) { return; } // View only, skip mouse events
+         if (this._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);
              return;
          }
  
-         if (this._view_only) { return; } // View only, skip mouse events
+         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);
          }
  
          if (is_repeater) {
-             var repeaterID = this._repeaterID;
+             var repeaterID = "ID:" + this._repeaterID;
              while (repeaterID.length < 250) {
                  repeaterID += "\0";
              }
  
      // authentication
      _negotiate_xvp_auth: function () {
-         var xvp_sep = this._xvp_password_sep;
-         var xvp_auth = this._rfb_password.split(xvp_sep);
-         if (xvp_auth.length < 3) {
-             var msg = 'XVP credentials required (user' + xvp_sep +
-                 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
-             this._onPasswordRequired(this, msg);
+         if (!this._rfb_credentials.username ||
+             !this._rfb_credentials.password ||
+             !this._rfb_credentials.target) {
+             var event = new CustomEvent("credentialsrequired",
+                                         { detail: { types: ["username", "password", "target"] } });
+             this.dispatchEvent(event);
              return false;
          }
  
-         var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
-                            String.fromCharCode(xvp_auth[1].length) +
-                            xvp_auth[0] +
-                            xvp_auth[1];
+         var 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_password = xvp_auth.slice(2).join(xvp_sep);
          this._rfb_auth_scheme = 2;
          return this._negotiate_authentication();
      },
      _negotiate_std_vnc_auth: function () {
          if (this._sock.rQwait("auth challenge", 16)) { return false; }
  
-         if (this._rfb_password.length === 0) {
-             this._onPasswordRequired(this);
+         if (!this._rfb_credentials.password) {
+             var event = new CustomEvent("credentialsrequired",
+                                         { detail: { types: ["password"] } });
+             this.dispatchEvent(event);
              return false;
          }
  
          // TODO(directxman12): make genDES not require an Array
          var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
-         var response = RFB.genDES(this._rfb_password, challenge);
+         var response = RFB.genDES(this._rfb_credentials.password, challenge);
          this._sock.send(response);
          this._rfb_init_state = "SecurityResult";
          return true;
          }
  
          // we're past the point where we could backtrack, so it's safe to call this
-         this._onDesktopName(this, this._fb_name);
+         var event = new CustomEvent("desktopname",
+                                     { detail: { name: this._fb_name } });
+         this.dispatchEvent(event);
  
          this._resize(width, height);
  
-         if (!this._view_only) { this._keyboard.grab(); }
-         if (!this._view_only) { this._mouse.grab(); }
+         if (!this._viewOnly) { this._keyboard.grab(); }
+         if (!this._viewOnly) { this._mouse.grab(); }
  
          this._fb_depth = 24;
  
          encs.push(encodings.pseudoEncodingFence);
          encs.push(encodings.pseudoEncodingContinuousUpdates);
  
-         if (this._local_cursor && this._fb_depth == 24) {
+         if (browserSupportsCursorURIs() &&
+             !isTouchDevice && this._fb_depth == 24) {
              encs.push(encodings.pseudoEncodingCursor);
          }
  
  
          var text = this._sock.rQshiftStr(length);
  
-         if (this._view_only) { return true; }
+         if (this._viewOnly) { return true; }
  
-         this._onClipboard(this, text);
+         var event = new CustomEvent("clipboard",
+                                     { detail: { text: text } });
+         this.dispatchEvent(event);
  
          return true;
      },
              case 1:  // XVP_INIT
                  this._rfb_xvp_ver = xvp_ver;
                  Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
-                 this._onXvpInit(this._rfb_xvp_ver);
+                 this._setCapability("power", true);
                  break;
              default:
                  this._fail("Unexpected server message",
  
              case 2:  // Bell
                  Log.Debug("Bell");
-                 this._onBell(this);
+                 var event = new CustomEvent("bell", { detail: {} });
+                 this.dispatchEvent(event);
                  return true;
  
              case 3:  // ServerCutText
                  this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
                                                (hdr[10] << 8) + hdr[11], 10);
  
-                 this._onFBUReceive(this,
-                     {'x': this._FBU.x, 'y': this._FBU.y,
-                      'width': this._FBU.width, 'height': this._FBU.height,
-                      'encoding': this._FBU.encoding,
-                      'encodingName': encodingName(this._FBU.encoding)});
                  if (!this._encHandlers[this._FBU.encoding]) {
                      this._fail("Unexpected server message",
                                 "Unsupported encoding " +
  
          this._display.flip();
  
-         this._onFBUComplete(this);
          return true;  // We finished this FBU
      },
  
          this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
  
          this._display.resize(this._fb_width, this._fb_height);
-         this._onFBResize(this, this._fb_width, this._fb_height);
+         var event = new CustomEvent("fbresize",
+                                     { detail: { width: this._fb_width,
+                                                 height: this._fb_height } });
+         this.dispatchEvent(event);
  
          this._timing.fbu_rt_start = (new Date()).getTime();
          this._updateContinuousUpdates();
-     }
- };
- make_properties(RFB, [
-     ['target', 'wo', 'dom'],                // VNC display rendering Canvas object
-     ['encrypt', 'rw', 'bool'],              // Use TLS/SSL/wss encryption
-     ['local_cursor', 'rw', 'bool'],         // Request locally rendered cursor
-     ['shared', 'rw', 'bool'],               // Request shared mode
-     ['view_only', 'rw', 'bool'],            // Disable client mouse/keyboard
-     ['focus_on_click', 'rw', 'bool'],       // Grab focus on canvas on mouse click
-     ['xvp_password_sep', 'rw', 'str'],      // Separator for XVP password fields
-     ['disconnectTimeout', 'rw', 'int'],     // Time (s) to wait for disconnection
-     ['wsProtocols', 'rw', 'arr'],           // Protocols to use in the WebSocket connection
-     ['repeaterID', 'rw', 'str'],            // [UltraVNC] RepeaterID to connect to
-     ['viewportDrag', 'rw', 'bool'],         // Move the viewport on mouse drags
-     // Callback functions
-     ['onUpdateState', 'rw', 'func'],        // onUpdateState(rfb, state, oldstate): connection state change
-     ['onNotification', 'rw', 'func'],       // onNotification(rfb, msg, level, options): notification for the UI
-     ['onDisconnected', 'rw', 'func'],       // onDisconnected(rfb, reason): disconnection finished
-     ['onPasswordRequired', 'rw', 'func'],   // onPasswordRequired(rfb, msg): VNC password is required
-     ['onClipboard', 'rw', 'func'],          // onClipboard(rfb, text): RFB clipboard contents received
-     ['onBell', 'rw', 'func'],               // onBell(rfb): RFB Bell message received
-     ['onFBUReceive', 'rw', 'func'],         // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
-     ['onFBUComplete', 'rw', 'func'],        // onFBUComplete(rfb, fbu): RFB FBU received and processed
-     ['onFBResize', 'rw', 'func'],           // onFBResize(rfb, width, height): frame buffer resized
-     ['onDesktopName', 'rw', 'func'],        // onDesktopName(rfb, name): desktop name received
-     ['onXvpInit', 'rw', 'func']             // onXvpInit(version): XVP extensions active for this connection
- ]);
- RFB.prototype.set_local_cursor = function (cursor) {
-     if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
-         this._local_cursor = false;
-         this._display.disableLocalCursor(); //Only show server-side cursor
-     } else {
-         if (this._display.get_cursor_uri()) {
-             this._local_cursor = true;
-         } else {
-             Log.Warn("Browser does not support local cursor");
-             this._display.disableLocalCursor();
-         }
-     }
-     // Need to send an updated list of encodings if we are connected
-     if (this._rfb_connection_state === "connected") {
-         this._sendEncodings();
-     }
- };
- RFB.prototype.set_view_only = function (view_only) {
-     this._view_only = view_only;
+     },
  
-     if (this._rfb_connection_state === "connecting" ||
-         this._rfb_connection_state === "connected") {
-         if (view_only) {
-             this._keyboard.ungrab();
-             this._mouse.ungrab();
-         } else {
-             this._keyboard.grab();
-             this._mouse.grab();
-         }
-     }
+     _xvpOp: function (ver, op) {
+         if (this._rfb_xvp_ver < ver) { return; }
+         Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
+         RFB.messages.xvpOp(this._sock, ver, op);
+     },
  };
  
- RFB.prototype.get_display = function () { return this._display; };
- RFB.prototype.get_keyboard = function () { return this._keyboard; };
- RFB.prototype.get_mouse = function () { return this._mouse; };
+ Object.assign(RFB.prototype, EventTargetMixin);
  
  // Class Methods
  RFB.messages = {
  
          sock._sQlen += 10;
          sock.flush();
-     }
+     },
+     xvpOp: function (sock, ver, op) {
+         var buff = sock._sQ;
+         var offset = sock._sQlen;
+         buff[offset] = 250; // msg-type
+         buff[offset + 1] = 0; // padding
+         buff[offset + 2] = ver;
+         buff[offset + 3] = op;
+         sock._sQlen += 4;
+         sock.flush();
+     },
  };
  
  RFB.genDES = function (password, challenge) {
@@@ -2361,6 -2288,8 +2300,8 @@@ RFB.encodingHandlers = 
          if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
  
          this._supportsSetDesktopSize = true;
+         this._setCapability("resize", true);
          var number_of_screens = this._sock.rQpeek8();
  
          this._FBU.bytes = 4 + (number_of_screens * 16);
diff --combined docs/API.md
index 2a9e93e9236ba6b8a91e37cfee9bc26720171b2d,983d73571b0483ac23a844207e3d03a7c83c699e..8f565e93cf4138b6bd285eea764bf66f1dffb6b6
- # 1. Modules / API
+ # noVNC API
  
- The noVNC client is a composed of several modular components that handle
- rendering, input, networking, etc. Each of the modules is designed to
- be cross-browser and be useful as a standalone library in other
- projects (see LICENSE.txt).
+ The interface of the noVNC client consists of a single RFB object that
+ is instantiated once per connection.
  
+ ## RFB
  
- ## 1.1 Module List
+ The `RFB` object represents a single connection to a VNC server. It
+ communicates using a WebSocket that must provide a standard RFB
+ protocol stream.
  
- * __Mouse__ (core/input/mouse.js): Mouse input event handler with
- limited touch support.
+ ### Constructor
  
- * __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with
- non-US keyboard support. Translates keyDown and keyUp events to X11
- keysym values.
+ [`RFB()`](#rfb-1)
+   - Creates and returns a new `RFB` object.
  
- * __Display__ (core/display.js): Efficient 2D rendering abstraction
- layered on the HTML5 canvas element.
+ ### Properties
  
- * __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.
+ `viewOnly`
+   - Is a `boolean` indicating if any events (e.g. key presses or mouse
+     movement) should be prevented from being sent to the server.
+     Disabled by default.
  
- * __RFB__ (core/rfb.js): Main class that implements the RFB
- protocol and stitches the other classes together.
++`focusOnClick`
++  - Is a `boolean` indicating if keyboard focus should automatically be
++    moved to the canvas when a `mousedown` or `touchstart` event is
++    received.
 +
+ `touchButton`
+   - Is a `long` controlling the button mask that should be simulated
+     when a touch event is recieved. Uses the same values as
+     [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button).
+     Is set to `1` by default.
  
- ## 1.2 Configuration Attributes
+ `viewportScale`
+   - Is a `double` indicating how the framebuffer contents should be
+     scaled before being rendered on to the canvas. See also
+     [`RFB.autoscale()`](#rfbautoscale). Is set to `1.0` by default.
  
- The Mouse, Keyboard, Display and RFB classes have a similar API for
- configuration options. Each configuration option has a default value,
- which can be overridden by a a configuration object passed to the
- constructor. Configuration options can then be read and modified after
- initialization with "get_*" and "set_*" methods respectively. For
- example, the following initializes an RFB object with the 'encrypt'
- configuration option enabled, then confirms it was set, then disables
- it.
+ `clipViewport`
+   - Is a `boolean` indicating if the canvas should be clipped to its
+     container. When disabled the container must be able to handle the
+     resulting overflow. Disabled by default.
  
-     var rfb = new RFB({'encrypt': true});
-     if (rfb.get_encrypt()) {
-         alert("Encryption is set");
-     }
-     rfb.set_encrypt(false);
+ `dragViewport`
+   - Is a `boolean` indicating if mouse events should control the
+     relative position of a clipped canvas. Only relevant if
+     `clipViewport` is enabled. Disabled by default.
  
- Some attributes are read-only and cannot be changed. For example, the
- Display 'render_mode' option will throw an exception if an attempt is
- made to set it. The attribute mode is one of the following:
+ `isClipped` *Read only*
+   - Is a `boolean` indicating if the framebuffer is larger than the
+     current canvas, i.e. it is being clipped.
  
-     RO - read only
-     RW - read write
-     WO - write once
+ `capabilities` *Read only*
+   - Is an `Object` indicating which optional extensions are available
+     on the server. Some methods may only be called if the corresponding
+     capability is set. The following capabilities are defined:
  
+     | name     | type      | description
+     | -------- | --------- | -----------
+     | `power`  | `boolean` | Machine power control is available
+     | `resize` | `boolean` | The framebuffer can be resized
  
- ## 1.3 Methods
+ ### Events
  
- In addition to the getter and setter methods to modify configuration
- attributes, each of the modules has other methods that are available
- in the object instance. For example, the Display module has method
- named 'blitImage' which takes an array of pixel data and draws it to
- the 2D canvas.
+ [`updatestate`](#updatestate)
+   - The `updatestate` event is fired when the connection state of the
+     `RFB` object changes.
  
- ## 1.4 Callbacks
+ [`notification`](#notification)
+   - The `notification` event is fired when the `RFB` usage has a
+     message to display to the user.
  
- Each of the modules has certain events that can be hooked with
- callback functions. For the Mouse, Keyboard, Display and RFB classes
- the callback functions are assigned to configuration attributes. The
- WebSock module has a method named 'on' that takes two parameters: the
- callback event name, and the callback function.
+ [`disconnect`](#disconnected)
+   - The `disconnect` event is fired when the `RFB` object disconnects.
  
- ## 2. Modules
+ [`credentialsrequired`](#credentialsrequired)
+   - The `credentialsrequired` event is fired when more credentials must
+     be given to continue.
  
- ## 2.1 Mouse Module
+ [`clipboard`](#clipboard)
+   - The `clipboard` event is fired when clipboard data is received from
+     the server.
  
- ### 2.1.1 Configuration Attributes
+ [`bell`](#bell)
+   - The `bell` event is fired when a audible bell request is received
+     from the server.
  
- | name        | type | mode | default  | description
- | ----------- | ---- | ---- | -------- | ------------
- | target      | DOM  | WO   | document | DOM element that captures mouse input
- | touchButton | int  | RW   | 1        | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
+ [`fbresize`](#fbresize)
+   - The `fbresize` event is fired when the framebuffer size is changed.
  
- ### 2.1.2 Methods
+ [`desktopname`](#desktopname)
+   - The `desktopname` event is fired when the remote desktop name
+     changes.
  
- | name   | parameters | description
- | ------ | ---------- | ------------
- | grab   | ()         | Begin capturing mouse events
- | ungrab | ()         | Stop capturing mouse events
+ [`capabilities`](#capabilities)
+   - The `capabilities` event is fired when `RFB.capabilities` is
+     updated.
  
- ### 2.1.2 Callbacks
+ ### Methods
  
- | name          | parameters          | description
- | ------------- | ------------------- | ------------
- | onMouseButton | (x, y, down, bmask) | Handler for mouse button click/release
- | onMouseMove   | (x, y)              | Handler for mouse movement
+ [`RFB.disconnect()`](#rfbdisconnect)
+   - Disconnect from the server.
  
+ [`RFB.sendCredentials()`](#rfbsendcredentials)
+   - Send credentials to server. Should be called after the
+     [`credentialsrequired`](#credentialsrequired) event has fired.
  
- ## 2.2 Keyboard Module
+ [`RFB.sendKey()`](#rfbsendKey)
+   - Send a key event.
  
- ### 2.2.1 Configuration Attributes
- | name    | type | mode | default  | description
- | ------- | ---- | ---- | -------- | ------------
- | target  | DOM  | WO   | document | DOM element that captures keyboard input
- ### 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
- | ----------- | ----- | ---- | ------- | ------------
- | target      | DOM   | WO   |         | Canvas element for rendering
- | context     | raw   | RO   |         | Canvas 2D context for rendering
- | 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
- | viewport    | bool  | RW   | false   | Use viewport clipping
- | width       | int   | RO   |         | Display area width
- | height      | int   | RO   |         | Display area height
- | render_mode | str   | RO   | ''      | Canvas rendering mode
- | prefer_js   | str   | RW   |         | Prefer JavaScript over canvas methods
- | cursor_uri  | raw   | RW   |         | Can we render cursor using data URI
- ### 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
- | changeCursor       | (pixels, mask, hotx, hoty, w, h)                        | Change cursor appearance
- | defaultCursor      | ()                                                      | Restore default cursor appearance
- | disableLocalCursor | ()                                                      | Disable local (client-side) cursor
- | clippingDisplay    | ()                                                      | Check if the remote display is larger than the client display
- | autoscale          | (containerWidth, containerHeight, downscaleOnly)        | 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
- ## 2.4 RFB Module
- ### 2.4.1 Configuration Attributes
- | name              | type | mode | default    | description
- | ----------------- | ---- | ---- | ---------- | ------------
- | target            | DOM  | WO   | null       | Canvas element for rendering (passed to Display, Mouse and Keyboard)
- | encrypt           | bool | RW   | false      | Use TLS/SSL encryption
- | local_cursor      | bool | RW   | false      | Request locally rendered cursor
- | shared            | bool | RW   | true       | Request shared VNC mode
- | view_only         | bool | RW   | false      | Disable client mouse/keyboard
- | focus_on_click    | bool | RW   | true       | Grab focus on canvas on mouse click
- | xvp_password_sep  | str  | RW   | '@'        | Separator for XVP password fields
- | disconnectTimeout | int  | RW   | 3          | Time (in seconds) to wait for disconnection
- | wsProtocols       | arr  | RW   | ['binary'] | Protocols to use in the WebSocket connection
- | repeaterID        | str  | RW   | ''         | UltraVNC RepeaterID to connect to
- | viewportDrag      | bool | RW   | false      | Move the viewport on mouse drags
- ### 2.4.2 Methods
- | name               | parameters                   | description
- | ------------------ | ---------------------------- | ------------
- | connect            | (host, port, password, path) | Connect to the given host:port/path. Optional password and path.
- | disconnect         | ()                           | Disconnect
- | sendPassword       | (passwd)                     | Send password after onPasswordRequired callback
- | sendCtrlAltDel     | ()                           | Send Ctrl-Alt-Del key sequence
- | xvpOp              | (ver, op)                    | Send a XVP operation (2=shutdown, 3=reboot, 4=reset)
- | xvpShutdown        | ()                           | Send XVP shutdown.
- | xvpReboot          | ()                           | Send XVP reboot.
- | xvpReset           | ()                           | Send XVP reset.
- | sendKey            | (keysym, code, down)         | Send a key press event. If down not specified, send a down and up event.
- | clipboardPasteFrom | (text)                       | Send a clipboard paste event
- | requestDesktopSize | (width, height)              | Send a request to change the remote desktop size.
- ### 2.4.3 Callbacks
- | name               | parameters                 | description
- | ------------------ | -------------------------- | ------------
- | onUpdateState      | (rfb, state, oldstate)     | Connection state change (see details below)
- | onNotification     | (rfb, msg, level, options) | Notification for the UI (optional options)
- | onDisconnected     | (rfb, reason)              | Disconnection finished with an optional reason. No reason specified means normal disconnect.
- | onPasswordRequired | (rfb, msg)                 | VNC password is required (use sendPassword), optionally comes with a message.
- | onClipboard        | (rfb, text)                | RFB clipboard contents received
- | onBell             | (rfb)                      | RFB Bell message received
- | onFBUReceive       | (rfb, fbu)                 | RFB FBU received but not yet processed (see details below)
- | onFBUComplete      | (rfb, fbu)                 | RFB FBU received and processed (see details below)
- | onFBResize         | (rfb, width, height)       | Frame buffer (remote desktop) size changed
- | onDesktopName      | (rfb, name)                | VNC desktop name recieved
- | onXvpInit          | (version)                  | XVP extensions active for this connection.
- __RFB onUpdateState callback details__
- The RFB module has an 'onUpdateState' callback that is invoked after
- the noVNC connection state changes. Here is a list of the states that
- are reported. Note that the RFB module can not transition from the
- disconnected state in any way, a new instance of the object has to be
- created for new connections.
- | connection state | description
- | ---------------- | ------------
- | connecting       | starting to connect
- | connected        | connected normally
- | disconnecting    | starting to disconnect
- | disconnected     | disconnected - permanent end-state for this RFB object
- __RFB onFBUReceive and on FBUComplete callback details__
- The onFBUReceive callback is invoked when a frame buffer update
- message has been received from the server but before the RFB class has
- done any additional handling. The onFBUComplete callback is invoked
- with the same information but after the RFB class has handled the
- message.
- The 'fbu' parameter is an object with the following structure:
-     {
-         x:            FBU_x_position,
-         y:            FBU_y_position,
-         width:        FBU_width,
-         height:       FBU_height,
-         encoding:     FBU_encoding_number,
-         encodingName: FBU_encoding_string
-     }
+ [`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel)
+   - Send Ctrl-Alt-Del key sequence.
+ [`RFB.machineShutdown()`](#rfbmachineshutdown)
+   - Request a shutdown of the remote machine.
+ [`RFB.machineReboot()`](#rfbmachinereboot)
+   - Request a reboot of the remote machine.
+ [`RFB.machineReset()`](#rfbmachinereset)
+   - Request a reset of the remote machine.
+ [`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom)
+   - Send clipboard contents to server.
+ [`RFB.autoscale()`](#rfbautoscale)
+   - Set `RFB.viewportScale` so that the framebuffer fits a specified
+     container.
+ [`RFB.requestDesktopSize()`](#rfbrequestDesktopSize)
+   - Send a request to change the remote desktop size.
+ [`RFB.viewportChangeSize()`](#rfbviewportChangeSize)
+   - Change size of the viewport.
+ ### Details
+ #### RFB()
+ The `RFB()` constructor returns a new `RFB` object and initiates a new
+ connection to a specified VNC server.
+ ##### Syntax
+     var rfb = new RFB( target, url [, options] );
+ ###### Parameters
+ **`target`**
+   - A [`HTMLCanvasElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement)
+     that specifies where graphics should be rendered and input events
+     should be monitored.
+ **`url`**
+   - A `DOMString` specifying the VNC server to connect to. This must be
+     a valid WebSocket URL.
+ **`options`** *Optional*
+   - An `Object` specifying extra details about how the connection
+     should be made.
+     Possible options:
+     `shared`
+       - A `boolean` indicating if the remote server should be shared or
+         if any other connected clients should be disconnected. Enabled
+         by default.
+     `credentials`
+       - An `Object` specifying the credentials to provide to the server
+         when authenticating. The following credentials are possible:
+         | name         | type        | description
+         | ------------ | ----------- | -----------
+         | `"username"` | `DOMString` | The user that authenticates
+         | `"password"` | `DOMString` | Password for the user
+         | `"target"`   | `DOMString` | Target machine or session
+     `repeaterID`
+       - A `DOMString` specifying the ID to provide to any VNC repeater
+         encountered.
+ #### updatestate
+ The `updatestate` event is fired after the noVNC connection state
+ changes. The `detail` property is an `Object` containg the property
+ `state` with the new connection state.
+ Here is a list of the states that are reported:
+ | connection state  | description
+ | ----------------- | ------------
+ | `"connecting"`    | starting to connect
+ | `"connected"`     | connected normally
+ | `"disconnecting"` | starting to disconnect
+ | `"disconnected"`  | disconnected
+ Note that a `RFB` objects can not transition from the disconnected
+ state in any way, a new instance of the object has to be created for
+ new connections.
+ #### notification
+ The `notification` event is fired when the `RFB` object wants a message
+ displayed to the user. The `detail` property is an `Object` containing
+ the following properties:
+ | Property  | Type        | Description
+ | --------- | ----------- | -----------
+ | `message` | `DOMString` | The message to display
+ | `level`   | `DOMString` | The severity of the message
+ The following levels are currently defined:
+   - `"normal"`
+   - `"warn"`
+   - `"error"`
+ #### disconnect
+ The `disconnect` event is fired when the connection has been
+ terminated. The `detail` property is an `Object` the optionally
+ contains the property `reason`. `reason` is a `DOMString` specifying
+ the reason in the event of an unexpected termination. `reason` will be
+ omitted for a clean termination.
+ #### credentialsrequired
+ The `credentialsrequired` event is fired when the server requests more
+ credentials than were specified to [`RFB()`](#rfb-1). The `detail`
+ property is an `Object` containing the property `types` which is an
+ `Array` of `DOMString` listing the credentials that are required.
+ #### clipboard
+ The `clipboard` event is fired when the server has sent clipboard data.
+ The `detail` property is an `Object` containing the property `text`
+ which is a `DOMString` with the clipboard data.
+ #### bell
+ The `bell` event is fired when the server has requested an audible
+ bell.
+ #### fbresize
+ The `fbresize` event is fired when the framebuffer has changed
+ dimensions. The `detail` property is an `Object` with the properties
+ `width` and `height` specifying the new dimensions.
+ #### desktopname
+ The `desktopname` event is fired when the name of the remote desktop
+ changes. The `detail` property is an `Object` with the property `name`
+ which is a `DOMString` specifying the new name.
+ #### capabilities
+ The `capabilities` event is fired whenever an entry is added or removed
+ from `RFB.capabilities`. The `detail` property is an `Object` with the
+ property `capabilities` containing the new value of `RFB.capabilities`.
+ #### RFB.disconnect()
+ The `RFB.disconnect()` method is used to disconnect from the currently
+ connected server.
+ ##### Syntax
+     RFB.disconnect( );
+ #### RFB.sendCredentials()
+ The `RFB.sendCredentials()` method is used to provide the missing
+ credentials after a `credentialsrequired` event has been fired.
+ ##### Syntax
+     RFB.sendCredentials( credentials );
+ ###### Parameters
+ **`credentials`**
+   - An `Object` specifying the credentials to provide to the server
+     when authenticating. See [`RFB()`](#rfb-1) for details.
+ #### RFB.sendKey()
+ The `RFB.sendKey()` method is used to send a key event to the server.
+ ##### Syntax
+     RFB.sendKey( keysym, code [, down] );
+ ###### Parameters
+ **`keysym`**
+   - A `long` specifying the RFB keysym to send. Can be `0` if a valid
+     **`code`** is specified.
+ **`code`**
+   - A `DOMString` specifying the physical key to send. Valid values are
+     those that can be specified to
+     [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code).
+     If the physical key cannot be determined then `null` shall be
+     specified.
+ **`down`** *Optional*
+   - A `boolean` specifying if a press or a release event should be
+     sent. If omitted then both a press and release event are sent.
+ #### RFB.sendCtrlAltDel()
+ The `RFB.sendCtrlAltDel()` method is used to send the key sequence
+ *left Control*, *left Alt*, *Delete*. This is a convenience wrapper
+ around [`RFB.sendKey()`](#rfbsendkey).
+ ##### Syntax
+     RFB.sendCtrlAltDel( );
+ #### RFB.machineShutdown()
+ The `RFB.machineShutdown()` method is used to request to shut down the
+ remote machine. The capability `power` must be set for this method to
+ have any effect.
+ ##### Syntax
+     RFB.machineShutdown( );
+ #### RFB.machineReboot()
+ The `RFB.machineReboot()` method is used to request a clean reboot of
+ the remote machine. The capability `power` must be set for this method
+ to have any effect.
+ ##### Syntax
+     RFB.machineReboot( );
+ #### RFB.machineReset()
+ The `RFB.machineReset()` method is used to request a forced reset of
+ the remote machine. The capability `power` must be set for this method
+ to have any effect.
+ ##### Syntax
+     RFB.machineReset( );
+ #### RFB.clipboardPasteFrom()
+ The `RFB.clipboardPasteFrom()` method is used to send clipboard data
+ to the remote server.
+ ##### Syntax
+     RFB.clipboardPasteFrom( text );
+ ###### Parameters
+ **`text`**
+   - A `DOMString` specifying the clipboard data to send. Currently only
+   characters from ISO 8859-1 are supported.
+ #### RFB.autoscale()
+ The `RFB.autoscale()` method is used to automatically adjust
+ `RFB.viewportScale` to fit given dimensions.
+ ##### Syntax
+     RFB.autoscale( width, height );
+ ###### Parameters
+ **`width`**
+   - A `long` specifying the maximum width of the canvas in CSS pixels.
+ **`height`**
+   - A `long` specifying the maximum height of the canvas in CSS pixels.
+ #### RFB.requestDesktopSize()
+ The `RFB.requestDesktopSize()` method is used to request a change of
+ the framebuffer. The capability `resize` must be set for this method to
+ have any effect.
+ Note that this is merely a request and the server may deny it.
+ The [`fbresize`](#fbresize) event will be fired when the framebuffer
+ actually changes dimensions.
+ ##### Syntax
+     RFB.requestDesktopSize( width, height );
+ ###### Parameters
+ **`width`**
+   - A `long` specifying the new requested width in CSS pixels.
+ **`height`**
+   - A `long` specifying the new requested height in CSS pixels.
+ #### RFB.viewportChangeSize()
+ The `RFB.viewportChangeSize()` method is used to change the size of the
+ canvas rather than the underlying framebuffer.
+ This method has no effect if `RFB.clipViewport` is set to `false`.
+ ##### Syntax
+     RFB.viewportChangeSize( width, height );
+ ###### Parameters
+ **`width`**
+   - A `long` specifying the new width in CSS pixels.
+ **`height`**
+   - A `long` specifying the new height in CSS pixels.
diff --combined vnc_lite.html
index 671d78e67d778275ddfd47439cdb8920d46f8796,6575b4631b34d2dfa168d73d7cb4e8bb92c71acb..d817885d30fb6e4383b47b8190d7156995fdc9c6
@@@ -5,7 -5,7 +5,7 @@@
      <!--
      noVNC example: lightweight example using minimal UI and features
      Copyright (C) 2012 Joel Martin
 -    Copyright (C) 2013 Samuel Mannehed for Cendio AB
 +    Copyright (C) 2017 Samuel Mannehed for Cendio AB
      noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
      This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
  
@@@ -80,6 -80,7 +80,7 @@@
          import RFB from './core/rfb.js';
  
          var rfb;
+         var doneInitialResize;
          var resizeTimeout;
          var desktopName;
  
                      rfb.requestDesktopSize(innerW, innerH - controlbarH);
              }
          }
-         function FBUComplete(rfb, fbu) {
+         function initialResize() {
+             if (doneInitialResize) return;
              UIresize();
-             rfb.set_onFBUComplete(function() { });
+             doneInitialResize = true;
          }
-         function updateDesktopName(rfb, name) {
-             desktopName = name;
+         function updateDesktopName(e) {
+             desktopName = e.detail.name;
          }
-         function passwordRequired(rfb, msg) {
-             if (typeof msg === 'undefined') {
-                 msg = 'Password Required: ';
-             }
+         function credentials(e) {
              var html;
  
              var form = document.createElement('form');
 -            form.style = 'margin-bottom: 0px';
 -            form.innerHTML = '<label></label>'
 -            form.innerHTML += '<input type=password size=10 id="password_input" class="noVNC_status">';
 +            form.innerHTML = '<label></label>';
 +            form.innerHTML += '<input type=password size=10 id="password_input">';
              form.onsubmit = setPassword;
  
              // bypass status() because it sets text content
              document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
              document.getElementById('noVNC_status').innerHTML = '';
              document.getElementById('noVNC_status').appendChild(form);
-             document.getElementById('noVNC_status').querySelector('label').textContent = msg;
+             document.getElementById('noVNC_status').querySelector('label').textContent = 'Password Required: ';
          }
          function setPassword() {
-             rfb.sendPassword(document.getElementById('password_input').value);
+             rfb.sendCredentials({ password: document.getElementById('password_input').value });
              return false;
          }
          function sendCtrlAltDel() {
              rfb.sendCtrlAltDel();
              return false;
          }
-         function xvpShutdown() {
-             rfb.xvpShutdown();
+         function machineShutdown() {
+             rfb.machineShutdown();
              return false;
          }
-         function xvpReboot() {
-             rfb.xvpReboot();
+         function machineReboot() {
+             rfb.machineReboot();
              return false;
          }
-         function xvpReset() {
-             rfb.xvpReset();
+         function machineReset() {
+             rfb.machineReset();
              return false;
          }
          function status(text, level) {
                  default:
                      level = "warn";
              }
 -            document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_" + level);
 +            document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level;
              document.getElementById('noVNC_status').textContent = text;
          }
-         function updateState(rfb, state, oldstate) {
+         function updateState(e) {
              var cad = document.getElementById('sendCtrlAltDelButton');
-             switch (state) {
+             switch (e.detail.state) {
                  case 'connecting':
                      status("Connecting", "normal");
                      break;
                  case 'connected':
-                     if (rfb && rfb.get_encrypt()) {
+                     doneInitialResize = false;
+                     if (WebUtil.getConfigVar('encrypt',
+                                              (window.location.protocol === "https:"))) {
                          status("Connected (encrypted) to " +
                                 desktopName, "normal");
                      } else {
                      status("Disconnected", "normal");
                      break;
                  default:
-                     status(state, "warn");
+                     status(e.detail.state, "warn");
                      break;
              }
  
-             if (state === 'connected') {
+             if (e.detail.state === 'connected') {
                  cad.disabled = false;
              } else {
                  cad.disabled = true;
-                 xvpInit(0);
+                 updatePowerButtons();
              }
  
          }
-         function disconnected(rfb, reason) {
-             if (typeof(reason) !== 'undefined') {
-                 status(reason, "error");
+         function disconnect(e) {
+             if (typeof(e.detail.reason) !== 'undefined') {
+                 status(e.detail.reason, "error");
              }
          }
-         function notification(rfb, msg, level, options) {
-             status(msg, level);
+         function notification(e) {
+             status(e.detail.message, e.detail.level);
          }
  
          window.onresize = function () {
              }, 500);
          };
  
-         function xvpInit(ver) {
-             var xvpbuttons;
-             xvpbuttons = document.getElementById('noVNC_xvp_buttons');
-             if (ver >= 1) {
+         function updatePowerButtons() {
+             var powerbuttons;
+             powerbuttons = document.getElementById('noVNC_power_buttons');
+             if (rfb.capabilities.power) {
 -                powerbuttons.style.display = 'inline';
 +                xvpbuttons.className= "noVNC_shown";
              } else {
 -                powerbuttons.style.display = 'none';
 +                xvpbuttons.className = "noVNC_hidden";
              }
          }
  
 -        document.getElementById('sendCtrlAltDelButton').style.display = "inline";
          document.getElementById('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
-         document.getElementById('xvpShutdownButton').onclick = xvpShutdown;
-         document.getElementById('xvpRebootButton').onclick = xvpReboot;
-         document.getElementById('xvpResetButton').onclick = xvpReset;
+         document.getElementById('machineShutdownButton').onclick = machineShutdown;
+         document.getElementById('machineRebootButton').onclick = machineReboot;
+         document.getElementById('machineResetButton').onclick = machineReset;
  
          WebUtil.init_logging(WebUtil.getConfigVar('logging', 'warn'));
          document.title = WebUtil.getConfigVar('title', 'noVNC');
                  status('Must specify host and port in URL', 'error');
              }
  
-             try {
-                 rfb = new RFB({'target':       document.getElementById('noVNC_canvas'),
-                                'encrypt':      WebUtil.getConfigVar('encrypt',
-                                         (window.location.protocol === "https:")),
-                                'repeaterID':   WebUtil.getConfigVar('repeaterID', ''),
-                                'local_cursor': WebUtil.getConfigVar('cursor', true),
-                                'shared':       WebUtil.getConfigVar('shared', true),
-                                'view_only':    WebUtil.getConfigVar('view_only', false),
-                                'onNotification':  notification,
-                                'onUpdateState':  updateState,
-                                'onDisconnected': disconnected,
-                                'onXvpInit':    xvpInit,
-                                'onPasswordRequired':  passwordRequired,
-                                'onFBUComplete': FBUComplete,
-                                'onDesktopName': updateDesktopName});
-             } catch (exc) {
-                 status('Unable to create RFB client -- ' + exc, 'error');
-                 return; // don't continue trying to connect
+             var url;
+             if (WebUtil.getConfigVar('encrypt',
+                                      (window.location.protocol === "https:"))) {
+                 url = 'wss';
+             } else {
+                 url = 'ws';
+             }
+             url += '://' + host;
+             if(port) {
+                 url += ':' + port;
              }
+             url += '/' + path;
  
-             rfb.connect(host, port, password, path);
+             rfb = new RFB(document.getElementById('noVNC_canvas'), url,
+                           { repeaterID: WebUtil.getConfigVar('repeaterID', ''),
+                             shared: WebUtil.getConfigVar('shared', true),
+                             credentials: { password: password } });
+             rfb.viewOnly =       WebUtil.getConfigVar('view_only', false);
+             rfb.addEventListener("notification", notification);
+             rfb.addEventListener("updatestate",  updateState);
+             rfb.addEventListener("disconnect", disconnect);
+             rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
+             rfb.addEventListener("credentialsrequired", credentials);
+             rfb.addEventListener("desktopname", updateDesktopName);
          })();
      </script>
  </head>
  
 -<body style="margin: 0px;">
 -    <div id="noVNC_container">
 -            <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
 -                <table border=0 width="100%"><tr>
 -                    <td><div id="noVNC_status" style="position: relative; height: auto;">
 -                        Loading
 -                    </div></td>
 -                    <td width="1%"><div id="noVNC_buttons">
 -                        <input type=button value="Send CtrlAltDel"
 -                            id="sendCtrlAltDelButton">
 -                        <span id="noVNC_power_buttons">
 -                        <input type=button value="Shutdown"
 -                            id="machineShutdownButton">
 -                        <input type=button value="Reboot"
 -                            id="machineRebootButton">
 -                        <input type=button value="Reset"
 -                            id="machineResetButton">
 -                        </span>
 -                            </div></td>
 -                </tr></table>
 -            </div>
 -            <canvas id="noVNC_canvas" width="640px" height="20px">
 -                Canvas not supported.
 -            </canvas>
 -        </div>
 -
 -    </body>
 +<body>
 +  <div id="noVNC_status_bar">
 +    <div id="noVNC_left_dummy_elem"></div>
 +    <div id="noVNC_status">Loading</div>
 +    <div id="noVNC_buttons">
 +      <input type=button value="Send CtrlAltDel"
 +             id="sendCtrlAltDelButton" class="noVNC_shown">
-       <span id="noVNC_xvp_buttons" class="noVNC_hidden">
++      <span id="noVNC_power_buttons" class="noVNC_hidden">
 +        <input type=button value="Shutdown"
-                id="xvpShutdownButton">
++               id="machineShutdownButton">
 +        <input type=button value="Reboot"
-                id="xvpRebootButton">
++               id="machineRebootButton">
 +        <input type=button value="Reset"
-                id="xvpResetButton">
++               id="machineResetButton">
 +      </span>
 +    </div>
 +  </div>
 +  <canvas id="noVNC_canvas" width="0" height="0">
 +    Canvas not supported.
 +  </canvas>
 +
 +</body>
  </html>