From 6929a0a183dec9ab20e33d34d5f241406ce12eec Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 12:18:45 +0200 Subject: [PATCH 01/34] Split out internal API We only commit to maintaining the API of the RFB object, not the other modules. --- docs/API-internal.md | 141 +++++++++++++++++++++++++++++++++ docs/API.md | 185 ++++++------------------------------------- 2 files changed, 165 insertions(+), 161 deletions(-) create mode 100644 docs/API-internal.md diff --git a/docs/API-internal.md b/docs/API-internal.md new file mode 100644 index 00000000..8d11d91d --- /dev/null +++ b/docs/API-internal.md @@ -0,0 +1,141 @@ +# 1. Internal Modules + +The noVNC client is composed of several internal modules that handle +rendering, input, networking, etc. Each of the modules is designed to +be cross-browser and independent from each other. + +Note however that the API of these modules is not guaranteed to be +stable, and this documentation is not maintained as well as the +official external API. + + +## 1.1 Module List + +* __Mouse__ (core/input/mouse.js): Mouse input event handler with +limited touch support. + +* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with +non-US keyboard support. Translates keyDown and keyUp events to X11 +keysym values. + +* __Display__ (core/display.js): Efficient 2D rendering abstraction +layered on the HTML5 canvas element. + +* __Websock__ (core/websock.js): Websock client from websockify +with transparent binary data support. +[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page. + + +## 1.2 Configuration Attributes + +The Mouse, Keyboard and Display objects have a similar API for +configuration options as the RFB object. See the official API +documentation for details. + + +## 1.3 Callbacks + +For the Mouse, Keyboard and Display objects the callback functions are +assigned to configuration attributes, just as for the RFB object. The +WebSock module has a method named 'on' that takes two parameters: the +callback event name, and the callback function. + +## 2. Modules + +## 2.1 Mouse Module + +### 2.1.1 Configuration Attributes + +| name | type | mode | default | description +| ----------- | ---- | ---- | -------- | ------------ +| 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. + +### 2.1.2 Methods + +| name | parameters | description +| ------ | ---------- | ------------ +| grab | () | Begin capturing mouse events +| ungrab | () | Stop capturing mouse events + +### 2.1.2 Callbacks + +| name | parameters | description +| ------------- | ------------------- | ------------ +| onMouseButton | (x, y, down, bmask) | Handler for mouse button click/release +| onMouseMove | (x, y) | Handler for mouse movement + + +## 2.2 Keyboard Module + +### 2.2.1 Configuration Attributes + +| 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 diff --git a/docs/API.md b/docs/API.md index a658cade..46eade14 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,41 +1,17 @@ -# 1. Modules / API +# 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. -## 1.1 Module List +## 1 Configuration Attributes -* __Mouse__ (core/input/mouse.js): Mouse input event handler with -limited touch support. - -* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with -non-US keyboard support. Translates keyDown and keyUp events to X11 -keysym values. - -* __Display__ (core/display.js): Efficient 2D rendering abstraction -layered on the HTML5 canvas element. - -* __Websock__ (core/websock.js): Websock client from websockify -with transparent binary data support. -[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page. - -* __RFB__ (core/rfb.js): Main class that implements the RFB -protocol and stitches the other classes together. - - -## 1.2 Configuration Attributes - -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. +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: var rfb = new RFB({'encrypt': true}); if (rfb.get_encrypt()) { @@ -43,136 +19,14 @@ it. } rfb.set_encrypt(false); -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: +Some attributes are read-only and cannot be changed. An exception will +be thrown if an attempt is made to set one of these attributs. The +attribute mode is one of the following: RO - read only RW - read write WO - write once - -## 1.3 Methods - -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. - -## 1.4 Callbacks - -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. - -## 2. Modules - -## 2.1 Mouse Module - -### 2.1.1 Configuration Attributes - -| 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. - -### 2.1.2 Methods - -| name | parameters | description -| ------ | ---------- | ------------ -| grab | () | Begin capturing mouse events -| ungrab | () | Stop capturing mouse events - -### 2.1.2 Callbacks - -| name | parameters | description -| ------------- | ------------------- | ------------ -| onMouseButton | (x, y, down, bmask) | Handler for mouse button click/release -| onMouseMove | (x, y) | Handler for mouse movement - - -## 2.2 Keyboard Module - -### 2.2.1 Configuration Attributes - -| 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) @@ -186,7 +40,12 @@ callback event name, and the callback function. | repeaterID | str | RW | '' | UltraVNC RepeaterID to connect to | viewportDrag | bool | RW | false | Move the viewport on mouse drags -### 2.4.2 Methods + +## 2 Methods + +In addition to the getter and setter methods to modify configuration +attributes, the RFB object has other methods that are available in the +object instance. | name | parameters | description | ------------------ | ---------------------------- | ------------ @@ -202,7 +61,11 @@ callback event name, and the callback function. | clipboardPasteFrom | (text) | Send a clipboard paste event | requestDesktopSize | (width, height) | Send a request to change the remote desktop size. -### 2.4.3 Callbacks + +## 3 Callbacks + +The RFB object has certain events that can be hooked with callback +functions. | name | parameters | description | ------------------ | -------------------------- | ------------ From b9854a5ca5d7d967c96c5b260ea9fbdfbef7f55a Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 12:46:47 +0200 Subject: [PATCH 02/34] Hide display object from RFB callers --- app/ui.js | 21 +++++++++----------- core/rfb.js | 34 ++++++++++++++++++++++++++++++++- docs/API.md | 55 +++++++++++++++++++++++++++++------------------------ 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/app/ui.js b/app/ui.js index dc7dbbd8..b72088d6 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1216,11 +1216,10 @@ var UI = { 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.set_scale(1); // Make sure the viewport is adjusted first UI.updateViewClip(); @@ -1258,13 +1257,12 @@ var UI = { 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, downscaleOnly); UI.fixScrollbars(); }, @@ -1302,8 +1300,7 @@ var UI = { updateViewClip: function() { if (!UI.rfb) return; - var display = UI.rfb.get_display(); - var cur_clip = display.get_viewport(); + var cur_clip = UI.rfb.get_viewport(); var new_clip = UI.getSetting('view_clip'); var resizeSetting = UI.getSetting('resize'); @@ -1316,7 +1313,7 @@ var UI = { } if (cur_clip !== new_clip) { - display.set_viewport(new_clip); + UI.rfb.set_viewport(new_clip); } var size = UI.screenSize(); @@ -1324,7 +1321,7 @@ var UI = { 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(); } @@ -1374,8 +1371,8 @@ var UI = { // 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.get_viewport() && + UI.rfb.clippingDisplay()) { clipping = true; } diff --git a/core/rfb.js b/core/rfb.js index 284a01bb..c41e9d8b 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -338,6 +338,21 @@ RFB.prototype = { RFB.messages.clientCutText(this._sock, text); }, + autoscale: function (width, height, downscaleOnly) { + if (this._rfb_connection_state !== 'connected') { return; } + this._display.autoscale(width, height, downscaleOnly); + }, + + viewportChangeSize: function(width, height) { + if (this._rfb_connection_state !== 'connected') { return; } + this._display.viewportChangeSize(width, height); + }, + + clippingDisplay: function () { + if (this._rfb_connection_state !== 'connected') { return false; } + return this._display.clippingDisplay(); + }, + // 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) { @@ -1478,6 +1493,8 @@ make_properties(RFB, [ ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor ['shared', 'rw', 'bool'], // Request shared mode ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard + ['scale', 'rw', 'float'], // Display area scale factor + ['viewport', 'rw', 'bool'], // Use viewport clipping ['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 @@ -1532,7 +1549,22 @@ RFB.prototype.set_view_only = function (view_only) { } }; -RFB.prototype.get_display = function () { return this._display; }; +RFB.prototype.set_scale = function (scale) { + this._display.set_scale(scale); +}; + +RFB.prototype.get_scale = function () { + return this._display.get_scale(); +}; + +RFB.prototype.set_viewport = function (viewport) { + this._display.set_viewport(viewport); +}; + +RFB.prototype.get_viewport = function () { + return this._display.get_viewport(); +}; + RFB.prototype.get_keyboard = function () { return this._keyboard; }; RFB.prototype.get_mouse = function () { return this._mouse; }; diff --git a/docs/API.md b/docs/API.md index 46eade14..dd451999 100644 --- a/docs/API.md +++ b/docs/API.md @@ -27,18 +27,20 @@ attribute mode is one of the following: RW - read write WO - write once -| 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 -| 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 +| 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 +| scale | float | RW | 1.0 | Display area scale factor +| viewport | bool | RW | false | Use viewport clipping +| 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 Methods @@ -47,19 +49,22 @@ In addition to the getter and setter methods to modify configuration attributes, the RFB object has other methods that are available in the object instance. -| 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. +| 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 +| autoscale | (width, height, downscaleOnly) | Scale the display +| clippingDisplay | () | Check if the remote display is larger than the client display +| requestDesktopSize | (width, height) | Send a request to change the remote desktop size. +| viewportChangeSize | (width, height) | Change size of the viewport ## 3 Callbacks From f976b5500e6dceb19c39965b22cb1e4934a75a13 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 13:07:17 +0200 Subject: [PATCH 03/34] Hide mouse object from RFB callers --- app/ui.js | 2 +- core/rfb.js | 10 +++++++++- docs/API.md | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/ui.js b/app/ui.js index b72088d6..c5cc7cbc 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1657,7 +1657,7 @@ var UI = { setMouseButton: function(num) { var view_only = UI.rfb.get_view_only(); if (UI.rfb && !view_only) { - UI.rfb.get_mouse().set_touchButton(num); + UI.rfb.set_touchButton(num); } var blist = [0, 1,2,4]; diff --git a/core/rfb.js b/core/rfb.js index c41e9d8b..a329fcc6 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1493,6 +1493,7 @@ make_properties(RFB, [ ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor ['shared', 'rw', 'bool'], // Request shared mode ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard + ['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) ['scale', 'rw', 'float'], // Display area scale factor ['viewport', 'rw', 'bool'], // Use viewport clipping ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields @@ -1549,6 +1550,14 @@ RFB.prototype.set_view_only = function (view_only) { } }; +RFB.prototype.set_touchButton = function (button) { + this._mouse.set_touchButton(button); +}; + +RFB.prototype.get_touchButton = function () { + return this._mouse.get_touchButton(); +}; + RFB.prototype.set_scale = function (scale) { this._display.set_scale(scale); }; @@ -1566,7 +1575,6 @@ RFB.prototype.get_viewport = function () { }; RFB.prototype.get_keyboard = function () { return this._keyboard; }; -RFB.prototype.get_mouse = function () { return this._mouse; }; // Class Methods RFB.messages = { diff --git a/docs/API.md b/docs/API.md index dd451999..959d137f 100644 --- a/docs/API.md +++ b/docs/API.md @@ -34,6 +34,7 @@ attribute mode is one of the following: | 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 +| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. | scale | float | RW | 1.0 | Display area scale factor | viewport | bool | RW | false | Use viewport clipping | xvp_password_sep | str | RW | '@' | Separator for XVP password fields From 233c8b6a5378ca82c44c52277c50a20d07d79bbf Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 13:07:39 +0200 Subject: [PATCH 04/34] Hide keyboard object from RFB callers --- core/rfb.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index a329fcc6..d4f3f2d7 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -1574,8 +1574,6 @@ RFB.prototype.get_viewport = function () { return this._display.get_viewport(); }; -RFB.prototype.get_keyboard = function () { return this._keyboard; }; - // Class Methods RFB.messages = { keyEvent: function (sock, keysym, down) { From 430f00d6fefbaedf72a609f5d747eed648cf843c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 13:50:49 +0200 Subject: [PATCH 05/34] Allow other credentials than just password Makes the XVP authentication mechanism more general. --- app/ui.js | 14 ++++----- core/rfb.js | 41 ++++++++++++-------------- docs/API.md | 73 ++++++++++++++++++++++++++++------------------- tests/playback.js | 4 +-- tests/test.rfb.js | 58 +++++++++++++++++++------------------ vnc_lite.html | 13 ++++----- 6 files changed, 105 insertions(+), 98 deletions(-) diff --git a/app/ui.js b/app/ui.js index c5cc7cbc..6e08172a 100644 --- a/app/ui.js +++ b/app/ui.js @@ -206,7 +206,7 @@ var UI = { 'onNotification': UI.notification, 'onUpdateState': UI.updateState, 'onDisconnected': UI.disconnectFinished, - 'onPasswordRequired': UI.passwordRequired, + 'onCredentialsRequired': UI.credentials, 'onXvpInit': UI.updateXvpButton, 'onClipboard': UI.clipboardReceive, 'onBell': UI.bell, @@ -1067,7 +1067,7 @@ var UI = { UI.updateLocalCursor(); UI.updateViewOnly(); - UI.rfb.connect(host, port, password, path); + UI.rfb.connect(host, port, { password: password }, path); }, disconnect: function() { @@ -1127,8 +1127,8 @@ var UI = { * PASSWORD * ------v------*/ - passwordRequired: function(rfb, msg) { - + credentials: function(rfb, types) { + // FIXME: handle more types document.getElementById('noVNC_password_dlg') .classList.add('noVNC_open'); @@ -1136,9 +1136,7 @@ var UI = { 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"); }, @@ -1148,7 +1146,7 @@ var UI = { 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'); diff --git a/core/rfb.js b/core/rfb.js index d4f3f2d7..3565f7cd 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -36,7 +36,7 @@ export default function RFB(defaults) { this._rfb_host = ''; this._rfb_port = 5900; - this._rfb_password = ''; + this._rfb_credentials = {}; this._rfb_path = ''; this._rfb_connection_state = ''; @@ -124,7 +124,6 @@ export default function RFB(defaults) { 'local_cursor': false, // Request locally rendered cursor 'shared': true, // Request shared mode 'view_only': false, // Disable client mouse/keyboard - 'xvp_password_sep': '@', // Separator for XVP password fields 'disconnectTimeout': 3, // Time (s) to wait for disconnection 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection 'repeaterID': '', // [UltraVNC] RepeaterID to connect to @@ -134,7 +133,7 @@ export default function RFB(defaults) { '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 + 'onCredentialsRequired': function () { }, // onCredentialsRequired(rfb, types): VNC credentials are 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 @@ -241,10 +240,10 @@ export default function RFB(defaults) { RFB.prototype = { // Public methods - connect: function (host, port, password, path) { + connect: function (host, port, creds, path) { this._rfb_host = host; this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_credentials = (creds !== undefined) ? creds : {}; this._rfb_path = (path !== undefined) ? path : ""; if (!this._rfb_host) { @@ -264,8 +263,8 @@ RFB.prototype = { 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); }, @@ -848,21 +847,18 @@ RFB.prototype = { // 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) { + this._onCredentialsRequired(this, ["username", "password", "target"]); 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(); }, @@ -870,14 +866,14 @@ RFB.prototype = { _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) { + this._onCredentialsRequired(this, ["password"]); 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; @@ -1496,7 +1492,6 @@ make_properties(RFB, [ ['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) ['scale', 'rw', 'float'], // Display area scale factor ['viewport', 'rw', 'bool'], // Use viewport clipping - ['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 @@ -1506,7 +1501,7 @@ make_properties(RFB, [ ['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 + ['onCredentialsRequired', 'rw', 'func'], // onCredentialsRequired(rfb, types): VNC credentials are 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 diff --git a/docs/API.md b/docs/API.md index 959d137f..9052daaf 100644 --- a/docs/API.md +++ b/docs/API.md @@ -37,7 +37,6 @@ attribute mode is one of the following: | touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. | scale | float | RW | 1.0 | Display area scale factor | viewport | bool | RW | false | Use viewport clipping -| 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 @@ -50,22 +49,22 @@ In addition to the getter and setter methods to modify configuration attributes, the RFB object has other methods that are available in the object instance. -| 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 -| autoscale | (width, height, downscaleOnly) | Scale the display -| clippingDisplay | () | Check if the remote display is larger than the client display -| requestDesktopSize | (width, height) | Send a request to change the remote desktop size. -| viewportChangeSize | (width, height) | Change size of the viewport +| name | parameters | description +| ------------------ | ------------------------------- | ------------ +| connect | (host, port, credentials, path) | Connect to the given host:port/path. Optional credentials and path. +| disconnect | () | Disconnect +| sendCredentials | (credentials) | Send credentials after onCredentialsRequired 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 +| autoscale | (width, height, downscaleOnly) | Scale the display +| clippingDisplay | () | Check if the remote display is larger than the client display +| requestDesktopSize | (width, height) | Send a request to change the remote desktop size. +| viewportChangeSize | (width, height) | Change size of the viewport ## 3 Callbacks @@ -73,19 +72,19 @@ object instance. The RFB object has certain events that can be hooked with callback functions. -| 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. +| 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. +| onCredentialsRequired | (rfb, types) | VNC credentials are required (use sendCredentials) +| 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__ @@ -103,6 +102,20 @@ created for new connections. | disconnecting | starting to disconnect | disconnected | disconnected - permanent end-state for this RFB object +__RFB onCredentialsRequired callback details__ + +The onCredentialsRequired callback is called when the server requests more +credentials than was specified to connect(). The types argument is a list +of all the credentials that are required. Currently the following are +defined: + +| name | description +| -------- | ------------ +| username | User that authenticates +| password | Password for user +| target | String specifying target machine or session + + __RFB onFBUReceive and on FBUComplete callback details__ The onFBUReceive callback is invoked when a frame buffer update diff --git a/tests/playback.js b/tests/playback.js index 745e1f55..ac36ee42 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -104,10 +104,10 @@ RecordingPlayer.prototype = { this._rfb._sock.close = function () {}; this._rfb._sock.flush = function () {}; this._rfb._checkEvents = function () {}; - this._rfb.connect = function (host, port, password, path) { + this._rfb.connect = function (host, port, credentials, path) { this._rfb_host = host; this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_credentials = {}; this._rfb_path = (path !== undefined) ? path : ""; this._sock.init('binary', 'ws'); this._rfb_connection_state = 'connecting'; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index fa6ba561..e4336f6f 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -116,18 +116,18 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); - describe('#sendPassword', function () { + describe('#sendCredentials', function () { beforeEach(function () { this.clock = sinon.useFakeTimers(); }); afterEach(function () { this.clock.restore(); }); - it('should set the rfb password properly"', function () { - client.sendPassword('pass'); - expect(client._rfb_password).to.equal('pass'); + it('should set the rfb credentials properly"', function () { + client.sendCredentials({ password: 'pass' }); + expect(client._rfb_credentials).to.deep.equal({ password: 'pass' }); }); it('should call init_msg "soon"', function () { client._init_msg = sinon.spy(); - client.sendPassword('pass'); + client.sendCredentials({ password: 'pass' }); this.clock.tick(5); expect(client._init_msg).to.have.been.calledOnce; }); @@ -836,21 +836,22 @@ describe('Remote Frame Buffer Protocol Client', function() { client._rfb_version = 3.8; }); - it('should call the passwordRequired callback if missing a password', function () { - client.set_onPasswordRequired(sinon.spy()); + it('should call the onCredentialsRequired callback if missing a password', function () { + client.set_onCredentialsRequired(sinon.spy()); send_security(2, client); var challenge = []; for (var i = 0; i < 16; i++) { challenge[i] = i; } client._sock._websocket._receive_data(new Uint8Array(challenge)); - var spy = client.get_onPasswordRequired(); - expect(client._rfb_password.length).to.equal(0); + var spy = client.get_onCredentialsRequired(); + expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; + expect(spy.args[0][1]).to.have.members(["password"]); }); it('should encrypt the password with DES and then send it back', function () { - client._rfb_password = 'passwd'; + client._rfb_credentials = { password: 'passwd' }; send_security(2, client); client._sock._websocket._get_sent_data(); // skip the choice of auth reply @@ -863,7 +864,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should transition to SecurityResult immediately after sending the password', function () { - client._rfb_password = 'passwd'; + client._rfb_credentials = { password: 'passwd' }; send_security(2, client); var challenge = []; @@ -886,41 +887,44 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should fall through to standard VNC authentication upon completion', function () { - client.set_xvp_password_sep('#'); - client._rfb_password = 'user#target#password'; + client._rfb_credentials = { username: 'user', + target: 'target', + password: 'password' }; client._negotiate_std_vnc_auth = sinon.spy(); send_security(22, client); expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; }); - it('should call the passwordRequired callback if the password is missing', function() { - client.set_onPasswordRequired(sinon.spy()); - client._rfb_password = ''; + it('should call the onCredentialsRequired callback if all credentials are missing', function() { + client.set_onCredentialsRequired(sinon.spy()); + client._rfb_credentials = {}; send_security(22, client); - var spy = client.get_onPasswordRequired(); - expect(client._rfb_password.length).to.equal(0); + var spy = client.get_onCredentialsRequired(); + expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; + expect(spy.args[0][1]).to.have.members(["username", "password", "target"]); }); - it('should call the passwordRequired callback if the password is improperly formatted', function() { - client.set_onPasswordRequired(sinon.spy()); - client._rfb_password = 'user@target'; + it('should call the onCredentialsRequired callback if some credentials are missing', function() { + client.set_onCredentialsRequired(sinon.spy()); + client._rfb_credentials = { username: 'user', + target: 'target' }; send_security(22, client); - var spy = client.get_onPasswordRequired(); + var spy = client.get_onCredentialsRequired(); expect(spy).to.have.been.calledOnce; + expect(spy.args[0][1]).to.have.members(["username", "password", "target"]); }); - it('should split the password, send the first two parts, and pass on the last part', function () { - client.set_xvp_password_sep('#'); - client._rfb_password = 'user#target#password'; + it('should send user and target separately', function () { + client._rfb_credentials = { username: 'user', + target: 'target', + password: 'password' }; client._negotiate_std_vnc_auth = sinon.spy(); send_security(22, client); - expect(client._rfb_password).to.equal('password'); - var expected = [22, 4, 6]; // auth selection, len user, len target for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } diff --git a/vnc_lite.html b/vnc_lite.html index f54cb13a..73aa426f 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -99,10 +99,7 @@ function updateDesktopName(rfb, name) { desktopName = name; } - function passwordRequired(rfb, msg) { - if (typeof msg === 'undefined') { - msg = 'Password Required: '; - } + function credentials(rfb, types) { var html; var form = document.createElement('form'); @@ -115,10 +112,10 @@ 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() { @@ -266,7 +263,7 @@ 'onUpdateState': updateState, 'onDisconnected': disconnected, 'onXvpInit': xvpInit, - 'onPasswordRequired': passwordRequired, + 'onCredentialsRequired': credentials, 'onFBUComplete': FBUComplete, 'onDesktopName': updateDesktopName}); } catch (exc) { @@ -274,7 +271,7 @@ return; // don't continue trying to connect } - rfb.connect(host, port, password, path); + rfb.connect(host, port, { password: password }, path); })(); From 85b35fc0cc30b41617eea6d0317da40fc3ea6c97 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 13:57:19 +0200 Subject: [PATCH 06/34] Add proper protocol encoder for XVP messages --- core/rfb.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 3565f7cd..18380e7e 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -285,7 +285,7 @@ RFB.prototype = { 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)); + RFB.messages.xvpOp(this._sock, ver, op); return true; }, @@ -1850,7 +1850,21 @@ 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) { From cd523e8f28dc027c23c1ef5d2cb65db0d8f80458 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 14:25:26 +0200 Subject: [PATCH 07/34] Make power API generic Decouple it from XVP and make it a generic API. --- app/styles/base.css | 10 +++---- app/ui.js | 66 +++++++++++++++++++++++---------------------- core/rfb.js | 37 +++++++++++++------------ docs/API.md | 10 +++---- tests/test.rfb.js | 25 +++++++---------- vnc.html | 12 ++++----- vnc_lite.html | 42 ++++++++++++++--------------- 7 files changed, 101 insertions(+), 101 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index 790837a0..b8ce81bd 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -591,17 +591,17 @@ select:active { padding: 0 10px; } -/* XVP Shutdown/Reboot */ -:root:not(.noVNC_connected) #noVNC_xvp_button { +/* Shutdown/Reboot */ +:root:not(.noVNC_connected) #noVNC_power_button { display: none; } -#noVNC_xvp { +#noVNC_power { } -#noVNC_xvp_buttons { +#noVNC_power_buttons { display: none; } -#noVNC_xvp input[type=button] { +#noVNC_power input[type=button] { width: 100%; } diff --git a/app/ui.js b/app/ui.js index 6e08172a..6f1690ec 100644 --- a/app/ui.js +++ b/app/ui.js @@ -91,7 +91,7 @@ var UI = { UI.addControlbarHandlers(); UI.addTouchSpecificHandlers(); UI.addExtraKeysHandlers(); - UI.addXvpHandlers(); + UI.addMachineHandlers(); UI.addConnectionControlHandlers(); UI.addClipboardHandlers(); UI.addSettingsHandlers(); @@ -207,7 +207,7 @@ var UI = { 'onUpdateState': UI.updateState, 'onDisconnected': UI.disconnectFinished, 'onCredentialsRequired': UI.credentials, - 'onXvpInit': UI.updateXvpButton, + 'onCapabilities': UI.updatePowerButton, 'onClipboard': UI.clipboardReceive, 'onBell': UI.bell, 'onFBUComplete': UI.initialResize, @@ -332,15 +332,15 @@ var UI = { .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() { @@ -489,7 +489,7 @@ var UI = { UI.enableSetting('port'); UI.enableSetting('path'); UI.enableSetting('repeaterID'); - UI.updateXvpButton(0); + UI.updatePowerButton(); UI.keepControlbar(); } @@ -868,7 +868,7 @@ var UI = { closeAllPanels: function() { UI.closeSettingsPanel(); - UI.closeXvpPanel(); + UI.closePowerPanel(); UI.closeClipboardPanel(); UI.closeExtraKeys(); }, @@ -926,50 +926,52 @@ var UI = { /* ------^------- * /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.get_capabilities().power && + !UI.rfb.get_view_only()) { + 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------*/ diff --git a/core/rfb.js b/core/rfb.js index 18380e7e..5b66abb4 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -49,6 +49,8 @@ export default function RFB(defaults) { this._rfb_tightvnc = false; this._rfb_xvp_ver = 0; + this._capabilities = { power: false }; + this._encHandlers = {}; this._encStats = {}; @@ -140,7 +142,7 @@ export default function RFB(defaults) { '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 + 'onCapabilities': function () { } // onCapabilities(rfb, caps): the supported capabilities has changed }); // main setup @@ -282,23 +284,16 @@ RFB.prototype = { return true; }, - xvpOp: function (ver, op) { - if (this._rfb_xvp_ver < ver) { return false; } - Log.Info("Sending XVP operation " + op + " (version " + ver + ")"); - RFB.messages.xvpOp(this._sock, ver, op); - return true; + machineShutdown: function () { + this._xvpOp(1, 2); }, - xvpShutdown: function () { - return this.xvpOp(1, 2); + machineReboot: function () { + this._xvpOp(1, 3); }, - xvpReboot: function () { - return 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 @@ -1282,7 +1277,8 @@ RFB.prototype = { 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._capabilities.power = true; + this._onCapabilities(this, this._capabilities) break; default: this._fail("Unexpected server message", @@ -1480,7 +1476,13 @@ RFB.prototype = { this._timing.fbu_rt_start = (new Date()).getTime(); this._updateContinuousUpdates(); - } + }, + + _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); + }, }; make_properties(RFB, [ @@ -1496,6 +1498,7 @@ make_properties(RFB, [ ['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 + ['capabilities', 'ro', 'arr'], // Supported capabilities // Callback functions ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change @@ -1508,7 +1511,7 @@ make_properties(RFB, [ ['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 + ['onCapabilities', 'rw', 'func'] // onCapabilities(rfb, caps): the supported capabilities has changed ]); RFB.prototype.set_local_cursor = function (cursor) { diff --git a/docs/API.md b/docs/API.md index 9052daaf..92222e67 100644 --- a/docs/API.md +++ b/docs/API.md @@ -41,6 +41,7 @@ attribute mode is one of the following: | 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 +| capabilities | arr | RO | [] | Supported capabilities (can include: 'power') ## 2 Methods @@ -55,10 +56,9 @@ object instance. | disconnect | () | Disconnect | sendCredentials | (credentials) | Send credentials after onCredentialsRequired 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. +| machineShutdown | () | Request a shutdown of the remote machine. +| machineReboot | () | Request a reboot of the remote machine. +| machineReset | () | Request a reset of the remote machine. | 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 | autoscale | (width, height, downscaleOnly) | Scale the display @@ -84,7 +84,7 @@ functions. | 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. +| onCapabilities | (rfb, capabilities) | The supported capabilities has changed __RFB onUpdateState callback details__ diff --git a/tests/test.rfb.js b/tests/test.rfb.js index e4336f6f..f59a2fe3 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -300,28 +300,23 @@ describe('Remote Frame Buffer Protocol Client', function() { client._rfb_xvp_ver = 1; }); - it('should send the shutdown signal on #xvpShutdown', function () { - client.xvpShutdown(); + it('should send the shutdown signal on #machineShutdown', function () { + client.machineShutdown(); expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02])); }); - it('should send the reboot signal on #xvpReboot', function () { - client.xvpReboot(); + it('should send the reboot signal on #machineReboot', function () { + client.machineReboot(); expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03])); }); - it('should send the reset signal on #xvpReset', function () { - client.xvpReset(); + it('should send the reset signal on #machineReset', function () { + client.machineReset(); expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04])); }); - it('should support sending arbitrary XVP operations via #xvpOp', function () { - client.xvpOp(1, 7); - expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07])); - }); - it('should not send XVP operations with higher versions than we support', function () { - expect(client.xvpOp(2, 7)).to.be.false; + client._xvpOp(2, 7); expect(client._sock.flush).to.not.have.been.called; }); }); @@ -1800,11 +1795,11 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should set the XVP version and fire the callback with the version on XVP_INIT', function () { - client.set_onXvpInit(sinon.spy()); + client.set_onCapabilities(sinon.spy()); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1])); expect(client._rfb_xvp_ver).to.equal(10); - expect(client.get_onXvpInit()).to.have.been.calledOnce; - expect(client.get_onXvpInit()).to.have.been.calledWith(10); + expect(client.get_onCapabilities()).to.have.been.calledOnce; + expect(client.get_onCapabilities()).to.have.been.calledWith(client, { power: true }); }); it('should fail on unknown XVP message types', function () { diff --git a/vnc.html b/vnc.html index 40d89c7f..e2799d6e 100644 --- a/vnc.html +++ b/vnc.html @@ -152,18 +152,18 @@ - +
-
+
Power
- - - + + +
diff --git a/vnc_lite.html b/vnc_lite.html index 73aa426f..e252aa5c 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -122,16 +122,16 @@ 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) { @@ -176,7 +176,7 @@ cad.disabled = false; } else { cad.disabled = true; - xvpInit(0); + updatePowerButtons(); } } @@ -199,21 +199,21 @@ }, 500); }; - function xvpInit(ver) { - var xvpbuttons; - xvpbuttons = document.getElementById('noVNC_xvp_buttons'); - if (ver >= 1) { - xvpbuttons.style.display = 'inline'; + function updatePowerButtons() { + var powerbuttons; + powerbuttons = document.getElementById('noVNC_power_buttons'); + if (rfb.get_capabilities().power) { + powerbuttons.style.display = 'inline'; } else { - xvpbuttons.style.display = 'none'; + powerbuttons.style.display = 'none'; } } 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'); @@ -262,7 +262,7 @@ 'onNotification': notification, 'onUpdateState': updateState, 'onDisconnected': disconnected, - 'onXvpInit': xvpInit, + 'onCapabilities': updatePowerButtons, 'onCredentialsRequired': credentials, 'onFBUComplete': FBUComplete, 'onDesktopName': updateDesktopName}); @@ -286,13 +286,13 @@
- + + id="machineShutdownButton"> + id="machineRebootButton"> + id="machineResetButton">
From 832be2625bf0f46a58d972716450429805e47d9c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 14:40:25 +0200 Subject: [PATCH 08/34] Add resize as a capability Makes the API more transparent than piggybacking on completion of the first framebuffer update. --- app/ui.js | 20 +++++++++----------- core/rfb.js | 12 +++++++++--- docs/API.md | 2 +- tests/test.rfb.js | 25 +++++++++++++++++++++---- vnc_lite.html | 10 ++++++---- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/app/ui.js b/app/ui.js index 6f1690ec..1aa5f8cc 100644 --- a/app/ui.js +++ b/app/ui.js @@ -207,10 +207,9 @@ var UI = { 'onUpdateState': UI.updateState, 'onDisconnected': UI.disconnectFinished, 'onCredentialsRequired': UI.credentials, - 'onCapabilities': UI.updatePowerButton, + 'onCapabilities': function () { UI.updatePowerButton(); UI.initialResize(); }, 'onClipboard': UI.clipboardReceive, 'onBell': UI.bell, - 'onFBUComplete': UI.initialResize, 'onFBResize': UI.updateSessionSize, 'onDesktopName': UI.updateDesktopName}); return true; @@ -431,6 +430,7 @@ var UI = { case 'connected': UI.connected = true; UI.inhibit_reconnect = false; + UI.doneInitialResize = false; document.documentElement.classList.add("noVNC_connected"); if (rfb && rfb.get_encrypt()) { msg = _("Connected (encrypted) to ") + UI.desktopName; @@ -1079,9 +1079,6 @@ var UI = { // 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 }, @@ -1275,13 +1272,14 @@ var UI = { // 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; }, /* ------^------- diff --git a/core/rfb.js b/core/rfb.js index 5b66abb4..fb2f4302 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -49,7 +49,7 @@ export default function RFB(defaults) { this._rfb_tightvnc = false; this._rfb_xvp_ver = 0; - this._capabilities = { power: false }; + this._capabilities = { power: false, resize: false }; this._encHandlers = {}; this._encStats = {}; @@ -638,6 +638,11 @@ RFB.prototype = { } }, + _setCapability: function (cap, val) { + this._capabilities[cap] = val; + this._onCapabilities(this, this._capabilities); + }, + _handle_message: function () { if (this._sock.rQlen() === 0) { Log.Warn("handle_message called on an empty receive queue"); @@ -1277,8 +1282,7 @@ RFB.prototype = { case 1: // XVP_INIT this._rfb_xvp_ver = xvp_ver; Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); - this._capabilities.power = true; - this._onCapabilities(this, this._capabilities) + this._setCapability("power", true); break; default: this._fail("Unexpected server message", @@ -2398,6 +2402,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 --git a/docs/API.md b/docs/API.md index 92222e67..18d92397 100644 --- a/docs/API.md +++ b/docs/API.md @@ -41,7 +41,7 @@ attribute mode is one of the following: | 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 -| capabilities | arr | RO | [] | Supported capabilities (can include: 'power') +| capabilities | arr | RO | [] | Supported capabilities (can include: 'power', 'resize') ## 2 Methods diff --git a/tests/test.rfb.js b/tests/test.rfb.js index f59a2fe3..4c4b12f6 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1684,6 +1684,25 @@ describe('Remote Frame Buffer Protocol Client', function() { return data; } + it('should call callback when resize is supported', function () { + client.set_onCapabilities(sinon.spy()); + + expect(client._supportsSetDesktopSize).to.be.false; + expect(client.get_capabilities().resize).to.be.false; + + var reason_for_change = 0; // server initiated + var status_code = 0; // No error + + send_fbu_msg([{ x: reason_for_change, y: status_code, + width: 4, height: 4, encoding: -308 }], + make_screen_data(1), client); + + expect(client._supportsSetDesktopSize).to.be.true; + expect(client.get_onCapabilities()).to.have.been.calledOnce; + expect(client.get_onCapabilities().args[0][1].resize).to.be.true; + expect(client.get_capabilities().resize).to.be.true; + }), + it('should handle a resize requested by this client', function () { var reason_for_change = 1; // requested by this client var status_code = 0; // No error @@ -1692,7 +1711,6 @@ describe('Remote Frame Buffer Protocol Client', function() { width: 20, height: 50, encoding: -308 }], make_screen_data(1), client); - expect(client._supportsSetDesktopSize).to.be.true; expect(client._fb_width).to.equal(20); expect(client._fb_height).to.equal(50); @@ -1712,7 +1730,6 @@ describe('Remote Frame Buffer Protocol Client', function() { width: 20, height: 50, encoding: -308 }], make_screen_data(1), client); - expect(client._supportsSetDesktopSize).to.be.true; expect(client._fb_width).to.equal(20); expect(client._fb_height).to.equal(50); @@ -1732,7 +1749,6 @@ describe('Remote Frame Buffer Protocol Client', function() { width: 60, height: 50, encoding: -308 }], make_screen_data(3), client); - expect(client._supportsSetDesktopSize).to.be.true; expect(client._fb_width).to.equal(60); expect(client._fb_height).to.equal(50); @@ -1799,7 +1815,8 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1])); expect(client._rfb_xvp_ver).to.equal(10); expect(client.get_onCapabilities()).to.have.been.calledOnce; - expect(client.get_onCapabilities()).to.have.been.calledWith(client, { power: true }); + expect(client.get_onCapabilities().args[0][1].power).to.be.true; + expect(client.get_capabilities().power).to.be.true; }); it('should fail on unknown XVP message types', function () { diff --git a/vnc_lite.html b/vnc_lite.html index e252aa5c..f1bf6f37 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -80,6 +80,7 @@ import RFB from './core/rfb.js'; var rfb; + var doneInitialResize; var resizeTimeout; var desktopName; @@ -92,9 +93,10 @@ 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; @@ -153,6 +155,7 @@ status("Connecting", "normal"); break; case 'connected': + doneInitialResize = false; if (rfb && rfb.get_encrypt()) { status("Connected (encrypted) to " + desktopName, "normal"); @@ -262,9 +265,8 @@ 'onNotification': notification, 'onUpdateState': updateState, 'onDisconnected': disconnected, - 'onCapabilities': updatePowerButtons, + 'onCapabilities': function () { updatePowerButtons(); initialResize(); }, 'onCredentialsRequired': credentials, - 'onFBUComplete': FBUComplete, 'onDesktopName': updateDesktopName}); } catch (exc) { status('Unable to create RFB client -- ' + exc, 'error'); From 30691b668eb43c4182b47a45d8245c7a6c7f8ec0 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 13 Oct 2017 14:45:44 +0200 Subject: [PATCH 09/34] Remove onFBU* callbacks They are internal mechanisms that callers should be isolated from. --- core/rfb.js | 12 ------------ docs/API.md | 22 ---------------------- tests/test.rfb.js | 28 ---------------------------- 3 files changed, 62 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index fb2f4302..7eb1c417 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -138,8 +138,6 @@ export default function RFB(defaults) { 'onCredentialsRequired': function () { }, // onCredentialsRequired(rfb, types): VNC credentials are 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 'onCapabilities': function () { } // onCapabilities(rfb, caps): the supported capabilities has changed @@ -1397,12 +1395,6 @@ RFB.prototype = { 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 " + @@ -1457,8 +1449,6 @@ RFB.prototype = { this._display.flip(); - this._onFBUComplete(this); - return true; // We finished this FBU }, @@ -1511,8 +1501,6 @@ make_properties(RFB, [ ['onCredentialsRequired', 'rw', 'func'], // onCredentialsRequired(rfb, types): VNC credentials are 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 ['onCapabilities', 'rw', 'func'] // onCapabilities(rfb, caps): the supported capabilities has changed diff --git a/docs/API.md b/docs/API.md index 18d92397..bec9b173 100644 --- a/docs/API.md +++ b/docs/API.md @@ -80,8 +80,6 @@ functions. | onCredentialsRequired | (rfb, types) | VNC credentials are required (use sendCredentials) | 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 | onCapabilities | (rfb, capabilities) | The supported capabilities has changed @@ -114,23 +112,3 @@ defined: | username | User that authenticates | password | Password for user | target | String specifying target machine or session - - -__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 - } diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 4c4b12f6..dae9e720 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1343,32 +1343,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._sock._websocket._get_sent_data()).to.have.length(0); }); - it('should parse out information from a header before any actual data comes in', function () { - client.set_onFBUReceive(sinon.spy()); - var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' }; - send_fbu_msg([rect_info], [[]], client); - - var spy = client.get_onFBUReceive(); - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, rect_info); - }); - - it('should fire onFBUComplete when the update is complete', function () { - client.set_onFBUComplete(sinon.spy()); - var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' }; - send_fbu_msg([rect_info], [[]], client); // last_rect - - var spy = client.get_onFBUComplete(); - expect(spy).to.have.been.calledOnce; - }); - - it('should not fire onFBUComplete if we have not finished processing the update', function () { - client.set_onFBUComplete(sinon.spy()); - var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' }; - send_fbu_msg([rect_info], [[]], client); - expect(client.get_onFBUComplete()).to.not.have.been.called; - }); - it('should fail on an unsupported encoding', function () { sinon.spy(client, "_fail"); var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 }; @@ -1783,10 +1757,8 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle the last_rect pseudo-encoding', function () { - client.set_onFBUReceive(sinon.spy()); send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100); expect(client._FBU.rects).to.equal(0); - expect(client.get_onFBUReceive()).to.have.been.calledOnce; }); }); }); From c7d08d721f62ed256bcffeced87627d04a1353b5 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 11:24:16 +0200 Subject: [PATCH 10/34] Tidy up variable initialisation Sort things by category, and organise everything in the same place. We don't support reuse of RFB objects so we can safely init everything in the constructor. --- core/rfb.js | 91 +++++++++++++++++++---------------------------- tests/test.rfb.js | 6 ---- 2 files changed, 37 insertions(+), 60 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 7eb1c417..4037bf9f 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -34,42 +34,59 @@ export default function RFB(defaults) { defaults = {}; } + // Connection details this._rfb_host = ''; this._rfb_port = 5900; this._rfb_credentials = {}; this._rfb_path = ''; + // 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._fb_width = 0; + this._fb_height = 0; + + this._fb_name = ""; + this._capabilities = { power: false, resize: false }; - this._encHandlers = {}; - this._encStats = {}; - - this._sock = null; // Websock object - this._display = null; // Display object - this._flushing = false; // Display flushing state - this._keyboard = null; // Keyboard input handler object - this._mouse = null; // Mouse input handler object - this._disconnTimer = null; // disconnection timer - this._supportsFence = false; this._supportsContinuousUpdates = false; this._enabledContinuousUpdates = false; - // Frame buffer update state + this._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 + + // Timers + this._disconnTimer = null; // disconnection timer + + // Decoder states and stats + this._encHandlers = {}; + this._encStats = {}; + this._FBU = { rects: 0, - subrects: 0, // RRE + subrects: 0, // RRE and HEXTILE lines: 0, // RAW tiles: 0, // HEXTILE bytes: 0, @@ -80,12 +97,11 @@ export default function RFB(defaults) { 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) @@ -105,10 +121,6 @@ export default function RFB(defaults) { pixels: 0 }; - this._supportsSetDesktopSize = false; - this._screen_id = 0; - this._screen_flags = 0; - // Mouse state this._mouse_buttonMask = 0; this._mouse_arr = []; @@ -116,9 +128,6 @@ export default function RFB(defaults) { 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 @@ -173,6 +182,7 @@ export default function RFB(defaults) { Log.Error("Display exception: " + exc); throw exc; } + this._display.clear(); this._keyboard = new Keyboard({target: this._target, onKeyEvent: this._handleKeyEvent.bind(this)}); @@ -229,9 +239,6 @@ export default function RFB(defaults) { 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); @@ -368,7 +375,6 @@ RFB.prototype = { _connect: function () { Log.Debug(">> RFB.connect"); - this._init_vars(); var uri; if (typeof UsingSocketIO !== 'undefined') { @@ -413,29 +419,6 @@ RFB.prototype = { 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; @@ -459,7 +442,7 @@ RFB.prototype = { if (!this._view_only) { this._mouse.ungrab(); } this._display.defaultCursor(); if (Log.get_logging() !== 'debug') { - // Show noVNC logo on load and when disconnected, unless in + // Show noVNC logo when disconnected, unless in // debug mode this._display.clear(); } diff --git a/tests/test.rfb.js b/tests/test.rfb.js index dae9e720..4cef186b 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -459,12 +459,6 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); }); - it('should reset the variable states', function () { - sinon.spy(client, '_init_vars'); - client._updateConnectionState('connecting'); - expect(client._init_vars).to.have.been.calledOnce; - }); - it('should actually connect to the websocket', function () { sinon.spy(client._sock, 'open'); client._updateConnectionState('connecting'); From 3a0010a3d038d041d1f517fa437a7d3521e9ed4c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 12:00:19 +0200 Subject: [PATCH 11/34] Avoid code duplication in RFB test preparation --- tests/test.rfb.js | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 4cef186b..f6112b93 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1242,18 +1242,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Framebuffer Update Handling', function () { - var client; - - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; - client._fb_width = 640; - client._fb_height = 20; - }); - var target_data_arr = [ 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, @@ -1619,14 +1607,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('the ExtendedDesktopSize pseudo-encoding handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; client._supportsSetDesktopSize = false; // a really small frame client._fb_width = 4; @@ -1758,16 +1739,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('XVP Message Handling', function () { - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; - client._fb_width = 27; - client._fb_height = 32; - }); - it('should send a notification on XVP_FAIL', function () { client.set_onNotification(sinon.spy()); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0])); @@ -2026,6 +1997,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); + client.connect('host', 8675); this.clock = sinon.useFakeTimers(); }); @@ -2033,7 +2005,6 @@ describe('Remote Frame Buffer Protocol Client', function() { // message events it ('should do nothing if we receive an empty message and have nothing in the queue', function () { - client.connect('host', 8675); client._rfb_connection_state = 'connected'; client._normal_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([])); @@ -2041,7 +2012,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle a message in the connected state as a normal message', function () { - client.connect('host', 8675); client._rfb_connection_state = 'connected'; client._normal_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); @@ -2049,7 +2019,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle a message in any non-disconnected/failed state like an init message', function () { - client.connect('host', 8675); client._rfb_init_state = 'ProtocolVersion'; client._init_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); @@ -2057,7 +2026,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should process all normal messages directly', function () { - client.connect('host', 8675); client._sock._websocket._open(); client._rfb_connection_state = 'connected'; client.set_onBell(sinon.spy()); @@ -2067,14 +2035,12 @@ describe('Remote Frame Buffer Protocol Client', function() { // open events it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () { - client.connect('host', 8675); client._sock._websocket._open(); expect(client._rfb_init_state).to.equal('ProtocolVersion'); }); it('should fail if we are not currently ready to connect and we get an "open" event', function () { sinon.spy(client, "_fail"); - client.connect('host', 8675); client._rfb_connection_state = 'some_other_state'; client._sock._websocket._open(); expect(client._fail).to.have.been.calledOnce; @@ -2082,7 +2048,6 @@ describe('Remote Frame Buffer Protocol Client', function() { // close events it('should transition to "disconnected" from "disconnecting" on a close event', function () { - client.connect('host', 8675); client._rfb_connection_state = 'disconnecting'; client._sock._websocket.close(); expect(client._rfb_connection_state).to.equal('disconnected'); @@ -2090,7 +2055,6 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should fail if we get a close event while connecting', function () { sinon.spy(client, "_fail"); - client.connect('host', 8675); client._rfb_connection_state = 'connecting'; client._sock._websocket.close(); expect(client._fail).to.have.been.calledOnce; @@ -2098,7 +2062,6 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should fail if we get a close event while disconnected', function () { sinon.spy(client, "_fail"); - client.connect('host', 8675); client._rfb_connection_state = 'disconnected'; client._sock._websocket.close(); expect(client._fail).to.have.been.calledOnce; @@ -2106,7 +2069,6 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should unregister close event handler', function () { sinon.spy(client._sock, 'off'); - client.connect('host', 8675); client._rfb_connection_state = 'disconnecting'; client._sock._websocket.close(); expect(client._sock.off).to.have.been.calledWith('close'); From 5b4e5d016e59f5678881d418f1513354e4ea6586 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 12:02:02 +0200 Subject: [PATCH 12/34] Switch to URL for connect() This is more in line with how other JavaScript APIs work. --- app/ui.js | 15 ++++++++--- core/rfb.js | 35 ++++++------------------- docs/API.md | 13 +++++---- tests/playback.js | 8 +++--- tests/test.rfb.js | 67 +++++++++++++++-------------------------------- vnc_lite.html | 22 +++++++++++++--- 6 files changed, 68 insertions(+), 92 deletions(-) diff --git a/app/ui.js b/app/ui.js index 1aa5f8cc..3ec3648a 100644 --- a/app/ui.js +++ b/app/ui.js @@ -432,7 +432,7 @@ var UI = { 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; @@ -1062,14 +1062,23 @@ var UI = { 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: password }, path); + var url; + + url = UI.getSetting('encrypt') ? 'wss' : 'ws'; + + url += '://' + host; + if(port) { + url += ':' + port; + } + url += '/' + path; + + UI.rfb.connect(url, { password: password }); }, disconnect: function() { diff --git a/core/rfb.js b/core/rfb.js index 4037bf9f..ec413a84 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -35,10 +35,8 @@ export default function RFB(defaults) { } // Connection details - this._rfb_host = ''; - this._rfb_port = 5900; + this._url = ''; this._rfb_credentials = {}; - this._rfb_path = ''; // Internal state this._rfb_connection_state = ''; @@ -131,7 +129,6 @@ export default function RFB(defaults) { // 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 @@ -247,15 +244,13 @@ export default function RFB(defaults) { RFB.prototype = { // Public methods - connect: function (host, port, creds, path) { - this._rfb_host = host; - this._rfb_port = port; + connect: function (url, creds) { + this._url = url; this._rfb_credentials = (creds !== undefined) ? creds : {}; - this._rfb_path = (path !== undefined) ? path : ""; - if (!this._rfb_host) { - return this._fail( - _("Must set host")); + if (!url) { + this._fail(_("Must specify URL")); + return; } this._rfb_init_state = ''; @@ -376,24 +371,11 @@ RFB.prototype = { _connect: function () { Log.Debug(">> RFB.connect"); - 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, this._wsProtocols); } catch (e) { if (e.name === 'SyntaxError') { this._fail("Invalid host or port value given", e); @@ -1464,7 +1446,6 @@ RFB.prototype = { 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 diff --git a/docs/API.md b/docs/API.md index bec9b173..8617be49 100644 --- a/docs/API.md +++ b/docs/API.md @@ -10,14 +10,14 @@ 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 +initializes an RFB object with the 'view_only' configuration option enabled, then confirms it was set, then disables it: - var rfb = new RFB({'encrypt': true}); - if (rfb.get_encrypt()) { - alert("Encryption is set"); + var rfb = new RFB({'view_only': true}); + if (rfb.get_view_only()) { + alert("View Only is set"); } - rfb.set_encrypt(false); + rfb.set_view_only(false); Some attributes are read-only and cannot be changed. An exception will be thrown if an attempt is made to set one of these attributs. The @@ -30,7 +30,6 @@ attribute mode is one of the following: | 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 @@ -52,7 +51,7 @@ object instance. | name | parameters | description | ------------------ | ------------------------------- | ------------ -| connect | (host, port, credentials, path) | Connect to the given host:port/path. Optional credentials and path. +| connect | (url, credentials) | Connect to the given URL. Optional credentials. | disconnect | () | Disconnect | sendCredentials | (credentials) | Send credentials after onCredentialsRequired callback | sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence diff --git a/tests/playback.js b/tests/playback.js index ac36ee42..ffb4859b 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -93,7 +93,7 @@ RecordingPlayer.prototype = { this._running = true; // launch the tests - this._rfb.connect('test', 0, 'bogus'); + this._rfb.connect('wss://test'); this._queueNextPacket(); }, @@ -104,11 +104,9 @@ RecordingPlayer.prototype = { this._rfb._sock.close = function () {}; this._rfb._sock.flush = function () {}; this._rfb._checkEvents = function () {}; - this._rfb.connect = function (host, port, credentials, path) { - this._rfb_host = host; - this._rfb_port = port; + this._rfb.connect = function (url) { + this._url = url; this._rfb_credentials = {}; - this._rfb_path = (path !== undefined) ? path : ""; this._sock.init('binary', 'ws'); this._rfb_connection_state = 'connecting'; this._rfb_init_state = 'ProtocolVersion'; diff --git a/tests/test.rfb.js b/tests/test.rfb.js index f6112b93..51720663 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -73,15 +73,15 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client._updateConnectionState = sinon.spy(); }); it('should set the current state to "connecting"', function () { - client.connect('host', 8675); + client.connect('wss://host:8675'); expect(client._updateConnectionState).to.have.been.calledOnce; expect(client._updateConnectionState).to.have.been.calledWith('connecting'); }); - it('should not try to connect if we are missing a host', function () { + it('should not try to connect if we are missing a URL', function () { client._fail = sinon.spy(); client._rfb_connection_state = ''; - client.connect(undefined, 8675); + client.connect(undefined); expect(client._fail).to.have.been.calledOnce; expect(client._updateConnectionState).to.not.have.been.called; expect(client._rfb_connection_state).to.equal(''); @@ -347,7 +347,6 @@ describe('Remote Frame Buffer Protocol Client', function() { client.set_onUpdateState(sinon.spy()); client._updateConnectionState('connecting'); var spy = client.get_onUpdateState(); - expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.equal('connecting'); }); @@ -386,7 +385,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { this.clock = sinon.useFakeTimers(); client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); }); afterEach(function () { @@ -465,36 +464,12 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._sock.open).to.have.been.calledOnce; }); - it('should use wss:// to connect if encryption is enabled', function () { + it('should use a url specified to connect', function () { sinon.spy(client._sock, 'open'); - client.set_encrypt(true); - client._updateConnectionState('connecting'); - expect(client._sock.open.args[0][0]).to.contain('wss://'); - }); - - it('should use ws:// to connect if encryption is not enabled', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(true); - client._updateConnectionState('connecting'); - expect(client._sock.open.args[0][0]).to.contain('wss://'); - }); - - it('should use a uri with the host, port, and path specified to connect', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(false); - client._rfb_host = 'HOST'; - client._rfb_port = 8675; - client._rfb_path = 'PATH'; + client._url = 'ws://HOST:8675/PATH'; client._updateConnectionState('connecting'); expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); }); - - it('should not include a port in the uri if not specified in connect', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(true); - client.connect('HOST', undefined) - expect(client._sock.open).to.have.been.calledWith('wss://HOST/'); - }); }); describe('disconnecting', function () { @@ -502,7 +477,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { this.clock = sinon.useFakeTimers(); client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); }); afterEach(function () { @@ -603,7 +578,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); }); @@ -666,7 +641,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); }); @@ -706,7 +681,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'Security'; }); @@ -770,7 +745,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'Security'; }); @@ -819,7 +794,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; @@ -869,7 +844,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; @@ -926,7 +901,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; @@ -1016,7 +991,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'SecurityResult'; }); @@ -1049,7 +1024,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'SecurityResult'; }); @@ -1077,7 +1052,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_init_state = 'ServerInitialisation'; }); @@ -1233,7 +1208,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_connection_state = 'connected'; client._fb_name = 'some device'; @@ -1353,7 +1328,7 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_connection_state = 'connected'; client._fb_name = 'some device'; @@ -1439,7 +1414,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); client._sock._websocket._open(); client._rfb_connection_state = 'connected'; client._fb_name = 'some device'; @@ -1997,7 +1972,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); + client.connect('wss://host:8675'); this.clock = sinon.useFakeTimers(); }); diff --git a/vnc_lite.html b/vnc_lite.html index f1bf6f37..c91f564f 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -156,7 +156,8 @@ break; case 'connected': doneInitialResize = false; - if (rfb && rfb.get_encrypt()) { + if (WebUtil.getConfigVar('encrypt', + (window.location.protocol === "https:"))) { status("Connected (encrypted) to " + desktopName, "normal"); } else { @@ -256,8 +257,6 @@ 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), @@ -273,7 +272,22 @@ return; // don't continue trying to connect } - rfb.connect(host, port, { password: password }, path); + 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(url, { password: password }); })(); From f8318361b1b62c4d76b091132d4a8ccfdd2957e4 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 12:45:56 +0200 Subject: [PATCH 13/34] Remove wsProtocols setting It isn't in use anymore since we deprecated support for Base64 mode. --- core/rfb.js | 4 +--- docs/API.md | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index ec413a84..2fe251c2 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -133,7 +133,6 @@ export default function RFB(defaults) { 'shared': true, // Request shared mode 'view_only': false, // Disable client mouse/keyboard '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 @@ -375,7 +374,7 @@ RFB.prototype = { try { // WebSocket.onopen transitions to the RFB init states - this._sock.open(this._url, this._wsProtocols); + this._sock.open(this._url, ['binary']); } catch (e) { if (e.name === 'SyntaxError') { this._fail("Invalid host or port value given", e); @@ -1453,7 +1452,6 @@ make_properties(RFB, [ ['scale', 'rw', 'float'], // Display area scale factor ['viewport', 'rw', 'bool'], // Use viewport clipping ['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 ['capabilities', 'ro', 'arr'], // Supported capabilities diff --git a/docs/API.md b/docs/API.md index 8617be49..f129a288 100644 --- a/docs/API.md +++ b/docs/API.md @@ -37,7 +37,6 @@ attribute mode is one of the following: | scale | float | RW | 1.0 | Display area scale factor | viewport | bool | RW | false | Use viewport clipping | 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 | capabilities | arr | RO | [] | Supported capabilities (can include: 'power', 'resize') From facf0b7027c8b709facff3e1d4e7fea87daedf87 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 12:58:25 +0200 Subject: [PATCH 14/34] Fix duplicate beforeEach() hook --- tests/test.rfb.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 51720663..9e606122 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -556,12 +556,11 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('Protocol Initialization States', function () { describe('ProtocolVersion', function () { + var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function () { - this.clock.restore(); + client = make_rfb(); + client.connect('wss://host:8675'); + client._sock._websocket._open(); }); function send_ver (ver, client) { @@ -638,13 +637,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - }); - it('should handle two step repeater negotiation', function () { client._repeaterID = '\x01\x02\x03\x04\x05'; From ebf1c0f9912aacaba511563de56413477bba6d68 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 13:02:40 +0200 Subject: [PATCH 15/34] Always include ID marker in repeater protocol This makes sure we're always following the protocol, and the caller doesn't have to care about the details. --- core/rfb.js | 2 +- tests/test.rfb.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 2fe251c2..5b79d3c8 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -732,7 +732,7 @@ RFB.prototype = { } if (is_repeater) { - var repeaterID = this._repeaterID; + var repeaterID = "ID:" + this._repeaterID; while (repeaterID.length < 250) { repeaterID += "\0"; } diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 9e606122..9ccc2fad 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -582,12 +582,12 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should interpret version 000.000 as a repeater', function () { - client._repeaterID = '\x01\x02\x03\x04\x05'; + client._repeaterID = '12345'; send_ver('000.000', client); expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); + expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); }); it('should interpret version 003.003 as version 3.3', function () { @@ -638,12 +638,12 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle two step repeater negotiation', function () { - client._repeaterID = '\x01\x02\x03\x04\x05'; + client._repeaterID = '12345'; send_ver('000.000', client); expect(client._rfb_version).to.equal(0); var sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); + expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); expect(sent_data).to.have.length(250); send_ver('003.008', client); From dd0db4d46035198c8c1bf687849a6e090326b8bc Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 13:25:10 +0200 Subject: [PATCH 16/34] Allow omitting configuration for properties --- core/util/properties.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/util/properties.js b/core/util/properties.js index e1d607e0..7b08a644 100644 --- a/core/util/properties.js +++ b/core/util/properties.js @@ -114,6 +114,7 @@ export function make_properties (constructor, arr) { export function set_defaults (obj, conf, defaults) { var defaults_keys = Object.keys(defaults); + conf = conf || {}; var conf_keys = Object.keys(conf); var keys_obj = {}; var i; From 3d7bb0203619a2787b16b5cc4b68b16e18a4ee68 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 13:06:03 +0200 Subject: [PATCH 17/34] Change some attributes to arguments Some attributes are better suited as arguments, primarily because they are associated with a specific method and cannot be changed later. --- app/ui.js | 15 +++++----- core/display.js | 5 ++-- core/input/keyboard.js | 10 +++---- core/input/mouse.js | 6 ++-- core/rfb.js | 34 ++++++++++----------- docs/API-internal.md | 6 +--- docs/API.md | 17 +++++++---- tests/playback.js | 4 +-- tests/test.display.js | 22 +++++++------- tests/test.keyboard.js | 52 ++++++++++++++++---------------- tests/test.mouse.js | 48 +++++++++++++----------------- tests/test.rfb.js | 67 +++++++++++++++++++++--------------------- vnc_lite.html | 10 +++---- 13 files changed, 144 insertions(+), 152 deletions(-) diff --git a/app/ui.js b/app/ui.js index 3ec3648a..9b4fac19 100644 --- a/app/ui.js +++ b/app/ui.js @@ -202,8 +202,8 @@ var UI = { initRFB: function() { try { - UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'), - 'onNotification': UI.notification, + UI.rfb = new RFB(document.getElementById('noVNC_canvas'), + {'onNotification': UI.notification, 'onUpdateState': UI.updateState, 'onDisconnected': UI.disconnectFinished, 'onCredentialsRequired': UI.credentials, @@ -277,8 +277,8 @@ var UI = { 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'), + {onKeyEvent: UI.keyEvent}); UI.touchKeyboard.grab(); document.getElementById("noVNC_keyboardinput") .addEventListener('input', UI.keyInput); @@ -1062,9 +1062,6 @@ var UI = { UI.closeAllPanels(); UI.closeConnectPanel(); - UI.rfb.set_shared(UI.getSetting('shared')); - UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); - UI.updateLocalCursor(); UI.updateViewOnly(); @@ -1078,7 +1075,9 @@ var UI = { } url += '/' + path; - UI.rfb.connect(url, { password: password }); + UI.rfb.connect(url, { shared: UI.getSetting('shared'), + repeaterID: UI.getSetting('repeaterID'), + credentials: { password: password } }); }, disconnect: function() { diff --git a/core/display.js b/core/display.js index e3f4293a..e7d8bd3a 100644 --- a/core/display.js +++ b/core/display.js @@ -15,7 +15,7 @@ import { set_defaults, make_properties } from './util/properties.js'; import * as Log from './util/logging.js'; import Base64 from "./base64.js"; -export default function Display(defaults) { +export default function Display(target, defaults) { this._drawCtx = null; this._c_forceCanvas = false; @@ -42,6 +42,8 @@ export default function Display(defaults) { Log.Debug(">> Display.constructor"); // The visible canvas + this._target = target; + if (!this._target) { throw new Error("Target must be set"); } @@ -691,7 +693,6 @@ Display.prototype = { }; make_properties(Display, [ - ['target', 'wo', 'dom'], // Canvas element for rendering ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only) ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data} ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 diff --git a/core/input/keyboard.js b/core/input/keyboard.js index fa4a5ae4..6dee460f 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -18,14 +18,14 @@ import KeyTable from "./keysym.js"; // Keyboard event handler // -export default function Keyboard(defaults) { +export default function Keyboard(target, defaults) { + this._target = target || null; + this._keyDownList = {}; // List of depressed keys // (even if they are happy) this._pendingKey = null; // Key waiting for keypress - set_defaults(this, defaults, { - 'target': null, - }); + set_defaults(this, defaults, {}); // keep these here so we can refer to them later this._eventHandlers = { @@ -338,7 +338,5 @@ Keyboard.prototype = { }; make_properties(Keyboard, [ - ['target', 'wo', 'dom'], // DOM element that captures keyboard input - ['onKeyEvent', 'rw', 'func'] // Handler for key press/release ]); diff --git a/core/input/mouse.js b/core/input/mouse.js index 49b5c395..ae3ca106 100644 --- a/core/input/mouse.js +++ b/core/input/mouse.js @@ -17,7 +17,8 @@ var WHEEL_STEP = 10; // Delta threshold for a mouse wheel step var WHEEL_STEP_TIMEOUT = 50; // ms var WHEEL_LINE_HEIGHT = 19; -export default function Mouse(defaults) { +export default function Mouse(target, defaults) { + this._target = target || document; this._doubleClickTimer = null; this._lastTouchPos = null; @@ -30,7 +31,6 @@ export default function Mouse(defaults) { // Configuration attributes set_defaults(this, defaults, { - 'target': document, 'touchButton': 1 }); @@ -284,8 +284,6 @@ Mouse.prototype = { }; make_properties(Mouse, [ - ['target', 'ro', 'dom'], // DOM element that captures mouse input - ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release ['onMouseMove', 'rw', 'func'], // Handler for mouse movement ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) diff --git a/core/rfb.js b/core/rfb.js index 5b79d3c8..ec638a9c 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -28,11 +28,12 @@ import { encodings, encodingName } from "./encodings.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) { +export default function RFB(target, defaults) { "use strict"; if (!defaults) { defaults = {}; } + this._target = target; // Connection details this._url = ''; @@ -128,12 +129,9 @@ export default function RFB(defaults) { // set the default value on user-facing properties set_defaults(this, defaults, { - 'target': 'null', // VNC display rendering Canvas object 'local_cursor': false, // Request locally rendered cursor - 'shared': true, // Request shared mode 'view_only': false, // Disable client mouse/keyboard 'disconnectTimeout': 3, // Time (s) to wait for disconnection - 'repeaterID': '', // [UltraVNC] RepeaterID to connect to 'viewportDrag': false, // Move the viewport on mouse drags // Callback functions @@ -172,19 +170,19 @@ export default function RFB(defaults) { // 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, + {onFlush: this._onFlush.bind(this)}); } catch (exc) { Log.Error("Display exception: " + exc); throw exc; } this._display.clear(); - this._keyboard = new Keyboard({target: this._target, - onKeyEvent: this._handleKeyEvent.bind(this)}); + this._keyboard = new Keyboard(this._target, + {onKeyEvent: this._handleKeyEvent.bind(this)}); - this._mouse = new Mouse({target: this._target, - onMouseButton: this._handleMouseButton.bind(this), + this._mouse = new Mouse(this._target, + {onMouseButton: this._handleMouseButton.bind(this), onMouseMove: this._handleMouseMove.bind(this)}); this._sock = new Websock(); @@ -243,15 +241,20 @@ export default function RFB(defaults) { RFB.prototype = { // Public methods - connect: function (url, creds) { - this._url = url; - this._rfb_credentials = (creds !== undefined) ? creds : {}; - + connect: function (url, options) { if (!url) { this._fail(_("Must specify URL")); return; } + this._url = url; + + options = options || {} + + this._rfb_credentials = options.credentials || {}; + this._shared = 'shared' in options ? !!options.shared : true; + this._repeaterID = options.repeaterID || ''; + this._rfb_init_state = ''; this._updateConnectionState('connecting'); return true; @@ -1444,15 +1447,12 @@ RFB.prototype = { }; make_properties(RFB, [ - ['target', 'wo', 'dom'], // VNC display rendering Canvas object ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor - ['shared', 'rw', 'bool'], // Request shared mode ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard ['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) ['scale', 'rw', 'float'], // Display area scale factor ['viewport', 'rw', 'bool'], // Use viewport clipping ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection - ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags ['capabilities', 'ro', 'arr'], // Supported capabilities diff --git a/docs/API-internal.md b/docs/API-internal.md index 8d11d91d..ec6d7ab7 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -48,7 +48,6 @@ callback event name, and the callback function. | 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. ### 2.1.2 Methods @@ -70,9 +69,7 @@ callback event name, and the callback function. ### 2.2.1 Configuration Attributes -| name | type | mode | default | description -| ------- | ---- | ---- | -------- | ------------ -| target | DOM | WO | document | DOM element that captures keyboard input +None ### 2.2.2 Methods @@ -94,7 +91,6 @@ callback event name, and the callback function. | 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 diff --git a/docs/API.md b/docs/API.md index f129a288..63293c0b 100644 --- a/docs/API.md +++ b/docs/API.md @@ -13,7 +13,7 @@ and "set_*" methods respectively. For example, the following initializes an RFB object with the 'view_only' configuration option enabled, then confirms it was set, then disables it: - var rfb = new RFB({'view_only': true}); + var rfb = new RFB(target, {'view_only': true}); if (rfb.get_view_only()) { alert("View Only is set"); } @@ -29,15 +29,12 @@ attribute mode is one of the following: | name | type | mode | default | description | ----------------- | ----- | ---- | ---------- | ------------ -| target | DOM | WO | null | Canvas element for rendering (passed to Display, Mouse and Keyboard) | 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 | touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. | scale | float | RW | 1.0 | Display area scale factor | viewport | bool | RW | false | Use viewport clipping | disconnectTimeout | int | RW | 3 | Time (in seconds) to wait for disconnection -| repeaterID | str | RW | '' | UltraVNC RepeaterID to connect to | viewportDrag | bool | RW | false | Move the viewport on mouse drags | capabilities | arr | RO | [] | Supported capabilities (can include: 'power', 'resize') @@ -50,7 +47,7 @@ object instance. | name | parameters | description | ------------------ | ------------------------------- | ------------ -| connect | (url, credentials) | Connect to the given URL. Optional credentials. +| connect | (url, options) | Connect to the given URL | disconnect | () | Disconnect | sendCredentials | (credentials) | Send credentials after onCredentialsRequired callback | sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence @@ -64,6 +61,16 @@ object instance. | requestDesktopSize | (width, height) | Send a request to change the remote desktop size. | viewportChangeSize | (width, height) | Change size of the viewport +__connect() details__ + +The connect() call supports the following options: + +| name | type | default | description +| ----------------- | ----- | ---------- | ------------ +| shared | bool | true | Request shared VNC mode +| credentials | obj | {} | Credentials to use when authenticating +| repeaterID | str | '' | UltraVNC RepeaterID to connect to + ## 3 Callbacks diff --git a/tests/playback.js b/tests/playback.js index ffb4859b..c2567a95 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -77,8 +77,8 @@ export default function RecordingPlayer (frames, encoding, disconnected, notific RecordingPlayer.prototype = { run: function (realtime, trafficManagement) { // initialize a new RFB - this._rfb = new RFB({'target': document.getElementById('VNC_canvas'), - 'view_only': true, + this._rfb = new RFB(document.getElementById('VNC_canvas'), + {'view_only': true, 'onDisconnected': this._handleDisconnect.bind(this), 'onNotification': this._notification}); this._enablePlaybackMode(); diff --git a/tests/test.display.js b/tests/test.display.js index fac0febc..77065872 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -40,19 +40,19 @@ describe('Display/Canvas Helper', function () { describe('checking for cursor uri support', function () { it('should disable cursor URIs if there is no support', function () { _forceCursorURIs(false); - var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); + var display = new Display(document.createElement('canvas'), { prefer_js: true, viewport: false }); expect(display._cursor_uri).to.be.false; }); it('should enable cursor URIs if there is support', function () { _forceCursorURIs(true); - var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); + var display = new Display(document.createElement('canvas'), { prefer_js: true, viewport: false }); expect(display._cursor_uri).to.be.true; }); it('respect the cursor_uri option if there is support', function () { _forceCursorURIs(false); - var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false }); + var display = new Display(document.createElement('canvas'), { prefer_js: true, viewport: false, cursor_uri: false }); expect(display._cursor_uri).to.be.false; }); }); @@ -60,7 +60,7 @@ describe('Display/Canvas Helper', function () { describe('viewport handling', function () { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + display = new Display(document.createElement('canvas'), { prefer_js: false, viewport: true }); display.resize(5, 5); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -153,7 +153,7 @@ describe('Display/Canvas Helper', function () { describe('resizing', function () { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false }); + display = new Display(document.createElement('canvas'), { prefer_js: false, viewport: false }); display.resize(4, 4); }); @@ -214,11 +214,11 @@ describe('Display/Canvas Helper', function () { var canvas; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + canvas = document.createElement('canvas'); + display = new Display(canvas, { prefer_js: false, viewport: true }); display.resize(4, 4); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); - canvas = display.get_target(); document.body.appendChild(canvas); }); @@ -254,9 +254,9 @@ describe('Display/Canvas Helper', function () { var canvas; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + canvas = document.createElement('canvas'); + display = new Display(canvas, { prefer_js: false, viewport: true }); display.resize(4, 3); - canvas = display.get_target(); document.body.appendChild(canvas); }); @@ -314,7 +314,7 @@ describe('Display/Canvas Helper', function () { function drawing_tests (pref_js) { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js }); + display = new Display(document.createElement('canvas'), { prefer_js: pref_js }); display.resize(4, 4); }); @@ -428,7 +428,7 @@ describe('Display/Canvas Helper', function () { describe('the render queue processor', function () { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false }); + display = new Display(document.createElement('canvas'), { prefer_js: false }); display.resize(4, 4); sinon.spy(display, '_scan_renderQ'); }); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 7fdeb8f9..52f21f0e 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -31,7 +31,7 @@ describe('Key Event Handling', function() { describe('Decode Keyboard Events', function() { it('should decode keydown events', function(done) { if (isIE() || isEdge()) this.skip(); - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -43,7 +43,7 @@ describe('Key Event Handling', function() { it('should decode keyup events', function(done) { if (isIE() || isEdge()) this.skip(); var calls = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -59,12 +59,12 @@ describe('Key Event Handling', function() { describe('Legacy keypress Events', function() { it('should wait for keypress when needed', function() { var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); + var kbd = new Keyboard(document, {onKeyEvent: callback}); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); expect(callback).to.not.have.been.called; }); it('should decode keypress events', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -76,13 +76,13 @@ describe('Key Event Handling', function() { }); it('should ignore keypress with different code', function() { var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); + var kbd = new Keyboard(document, {onKeyEvent: callback}); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61})); expect(callback).to.not.have.been.called; }); it('should handle keypress with missing code', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -93,7 +93,7 @@ describe('Key Event Handling', function() { kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61})); }); it('should guess key if no keypress and numeric key', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x32); expect(code).to.be.equal('Digit2'); @@ -103,7 +103,7 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32})); }); it('should guess key if no keypress and alpha key', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -113,7 +113,7 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false})); }); it('should guess key if no keypress and alpha key (with shift)', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x41); expect(code).to.be.equal('KeyA'); @@ -123,7 +123,7 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true})); }); it('should not guess key if no keypress and unknown key', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0); expect(code).to.be.equal('KeyA'); @@ -139,7 +139,7 @@ describe('Key Event Handling', function() { if (isIE() || isEdge()) this.skip(); }); it('should suppress anything with a valid key', function() { - var kbd = new Keyboard({}); + var kbd = new Keyboard(document, {}); var evt = keyevent('keydown', {code: 'KeyA', key: 'a'}); kbd._handleKeyDown(evt); expect(evt.preventDefault).to.have.been.called; @@ -148,13 +148,13 @@ describe('Key Event Handling', function() { expect(evt.preventDefault).to.have.been.called; }); it('should not suppress keys without key', function() { - var kbd = new Keyboard({}); + var kbd = new Keyboard(document, {}); var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); kbd._handleKeyDown(evt); expect(evt.preventDefault).to.not.have.been.called; }); it('should suppress the following keypress event', function() { - var kbd = new Keyboard({}); + var kbd = new Keyboard(document, {}); var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); kbd._handleKeyDown(evt); var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41}); @@ -168,7 +168,7 @@ describe('Key Event Handling', function() { it('should fake keyup events for virtual keyboards', function(done) { if (isIE() || isEdge()) this.skip(); var count = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { switch (count++) { case 0: @@ -215,7 +215,7 @@ describe('Key Event Handling', function() { it('should fake keyup events on iOS', function(done) { if (isIE() || isEdge()) this.skip(); var count = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { switch (count++) { case 0: @@ -240,7 +240,7 @@ describe('Key Event Handling', function() { if (isIE() || isEdge()) this.skip(); }); it('should send release using the same keysym as the press', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -253,7 +253,7 @@ describe('Key Event Handling', function() { }); it('should send the same keysym for multiple presses', function() { var count = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); @@ -266,14 +266,14 @@ describe('Key Event Handling', function() { }); it('should do nothing on keyup events if no keys are down', function() { var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); + var kbd = new Keyboard(document, {onKeyEvent: callback}); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); expect(callback).to.not.have.been.called; }); describe('Legacy Events', function() { it('should track keys using keyCode if no code', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Platform65'); @@ -293,7 +293,7 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'})); }); it('should track keys using keyIdentifier if no code', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Platform65'); @@ -335,7 +335,7 @@ describe('Key Event Handling', function() { it('should change Alt to AltGraph', function() { var count = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { switch (count++) { case 0: @@ -353,7 +353,7 @@ describe('Key Event Handling', function() { expect(count).to.be.equal(2); }); it('should change left Super to Alt', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0xFFE9); expect(code).to.be.equal('MetaLeft'); @@ -362,7 +362,7 @@ describe('Key Event Handling', function() { kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); }); it('should change right Super to left Super', function(done) { - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { expect(keysym).to.be.equal(0xFFEB); expect(code).to.be.equal('MetaRight'); @@ -400,7 +400,7 @@ describe('Key Event Handling', function() { it('should generate fake undo/redo events on press when AltGraph is down', function() { var times_called = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { switch(times_called++) { case 0: @@ -449,7 +449,7 @@ describe('Key Event Handling', function() { }); it('should no do anything on key release', function() { var times_called = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { switch(times_called++) { case 7: @@ -469,7 +469,7 @@ describe('Key Event Handling', function() { }); it('should not consider a char modifier to be down on the modifier key itself', function() { var times_called = 0; - var kbd = new Keyboard({ + var kbd = new Keyboard(document, { onKeyEvent: function(keysym, code, down) { switch(times_called++) { case 0: diff --git a/tests/test.mouse.js b/tests/test.mouse.js index a67833c7..905b524c 100644 --- a/tests/test.mouse.js +++ b/tests/test.mouse.js @@ -33,47 +33,44 @@ describe('Mouse Event Handling', function() { describe('Decode Mouse Events', function() { it('should decode mousedown events', function(done) { - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { expect(bmask).to.be.equal(0x01); expect(down).to.be.equal(1); done(); - }, - target: target + } }); mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); }); it('should decode mouseup events', function(done) { var calls = 0; - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { expect(bmask).to.be.equal(0x01); if (calls++ === 1) { expect(down).to.not.be.equal(1); done(); } - }, - target: target + } }); mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' })); }); it('should decode mousemove events', function(done) { - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseMove: function(x, y) { // Note that target relative coordinates are sent expect(x).to.be.equal(40); expect(y).to.be.equal(10); done(); - }, - target: target + } }); mouse._handleMouseMove(mouseevent('mousemove', { clientX: 50, clientY: 20 })); }); it('should decode mousewheel events', function(done) { var calls = 0; - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { calls++; expect(bmask).to.be.equal(1<<6); @@ -83,8 +80,7 @@ describe('Mouse Event Handling', function() { expect(down).to.not.be.equal(1); done(); } - }, - target: target + } }); mouse._handleMouseWheel(mouseevent('mousewheel', { deltaX: 50, deltaY: 0, @@ -99,7 +95,7 @@ describe('Mouse Event Handling', function() { it('should use same pos for 2nd tap if close enough', function(done) { var calls = 0; - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { calls++; if (calls === 1) { @@ -112,8 +108,7 @@ describe('Mouse Event Handling', function() { expect(y).to.be.equal(36); done(); } - }, - target: target + } }); // touch events are sent in an array of events // with one item for each touch point @@ -132,7 +127,7 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if far apart', function(done) { var calls = 0; - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { calls++; if (calls === 1) { @@ -145,8 +140,7 @@ describe('Mouse Event Handling', function() { expect(y).to.not.be.equal(36); done(); } - }, - target: target + } }); mouse._handleMouseDown(touchevent( 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); @@ -163,7 +157,7 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if not soon enough', function(done) { var calls = 0; - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { calls++; if (calls === 1) { @@ -176,8 +170,7 @@ describe('Mouse Event Handling', function() { expect(y).to.not.be.equal(36); done(); } - }, - target: target + } }); mouse._handleMouseDown(touchevent( 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); @@ -194,7 +187,7 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if not touch', function(done) { var calls = 0; - var mouse = new Mouse({ + var mouse = new Mouse(target, { onMouseButton: function(x, y, down, bmask) { calls++; if (calls === 1) { @@ -207,8 +200,7 @@ describe('Mouse Event Handling', function() { expect(y).to.not.be.equal(36); done(); } - }, - target: target + } }); mouse._handleMouseDown(mouseevent( 'mousedown', { button: '0x01', clientX: 78, clientY: 46 })); @@ -232,7 +224,7 @@ describe('Mouse Event Handling', function() { it('should accumulate wheel events if small enough', function () { var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target, { onMouseButton: callback }); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -265,7 +257,7 @@ describe('Mouse Event Handling', function() { it('should not accumulate large wheel events', function () { var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target, { onMouseButton: callback }); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -284,7 +276,7 @@ describe('Mouse Event Handling', function() { it('should send even small wheel events after a timeout', function () { var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target, { onMouseButton: callback }); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -296,7 +288,7 @@ describe('Mouse Event Handling', function() { it('should account for non-zero deltaMode', function () { var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target, { onMouseButton: callback }); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 9ccc2fad..a1d2f9e9 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -10,12 +10,8 @@ import FakeWebSocket from './fake.websocket.js'; import sinon from '../vendor/sinon.js'; function make_rfb (extra_opts) { - if (!extra_opts) { - extra_opts = {}; - } - - extra_opts.target = extra_opts.target || document.createElement('canvas'); - return new RFB(extra_opts); + extra_opts = extra_opts || {}; + return new RFB(document.createElement('canvas'), extra_opts); } var push8 = function (arr, num) { @@ -581,15 +577,6 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); }); - it('should interpret version 000.000 as a repeater', function () { - client._repeaterID = '12345'; - send_ver('000.000', client); - expect(client._rfb_version).to.equal(0); - - var sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); - }); - it('should interpret version 003.003 as version 3.3', function () { send_ver('003.003', client); expect(client._rfb_version).to.equal(3.3); @@ -637,19 +624,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); - it('should handle two step repeater negotiation', function () { - client._repeaterID = '12345'; - - send_ver('000.000', client); - expect(client._rfb_version).to.equal(0); - var sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); - expect(sent_data).to.have.length(250); - - send_ver('003.008', client); - expect(client._rfb_version).to.equal(3.8); - }); - it('should send back the interpreted version', function () { send_ver('004.000', client); @@ -666,6 +640,29 @@ describe('Remote Frame Buffer Protocol Client', function() { send_ver('003.008', client); expect(client._rfb_init_state).to.equal('Security'); }); + + describe('Repeater', function () { + it('should interpret version 000.000 as a repeater', function () { + client = make_rfb(); + client.connect('wss://host:8675', { repeaterID: "12345" }); + client._sock._websocket._open(); + send_ver('000.000', client); + expect(client._rfb_version).to.equal(0); + + var sent_data = client._sock._websocket._get_sent_data(); + expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); + expect(sent_data).to.have.length(250); + }); + + it('should handle two step repeater negotiation', function () { + client = make_rfb(); + client.connect('wss://host:8675', { repeaterID: "12345" }); + client._sock._websocket._open(); + send_ver('000.000', client); + send_ver('003.008', client); + expect(client._rfb_version).to.equal(3.8); + }); + }); }); describe('Security', function () { @@ -1016,24 +1013,28 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - client._rfb_init_state = 'SecurityResult'; }); it('should transition to the ServerInitialisation state', function () { + client.connect('wss://host:8675'); + client._sock._websocket._open(); + client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._rfb_init_state).to.equal('ServerInitialisation'); }); it('should send 1 if we are in shared mode', function () { - client.set_shared(true); + client.connect('wss://host:8675', { shared: true }); + client._sock._websocket._open(); + client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._sock).to.have.sent(new Uint8Array([1])); }); it('should send 0 if we are not in shared mode', function () { - client.set_shared(false); + client.connect('wss://host:8675', { shared: false }); + client._sock._websocket._open(); + client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._sock).to.have.sent(new Uint8Array([0])); }); diff --git a/vnc_lite.html b/vnc_lite.html index c91f564f..95cf2865 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -256,10 +256,8 @@ } try { - rfb = new RFB({'target': document.getElementById('noVNC_canvas'), - 'repeaterID': WebUtil.getConfigVar('repeaterID', ''), - 'local_cursor': WebUtil.getConfigVar('cursor', true), - 'shared': WebUtil.getConfigVar('shared', true), + rfb = new RFB(document.getElementById('noVNC_canvas'), + {'local_cursor': WebUtil.getConfigVar('cursor', true), 'view_only': WebUtil.getConfigVar('view_only', false), 'onNotification': notification, 'onUpdateState': updateState, @@ -287,7 +285,9 @@ } url += '/' + path; - rfb.connect(url, { password: password }); + rfb.connect(url, { repeaterID: WebUtil.getConfigVar('repeaterID', ''), + shared: WebUtil.getConfigVar('shared', true), + credentials: { password: password } }); })(); From 656858a6d6ceffe0f754700872ced9e3593228b2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 16:12:05 +0200 Subject: [PATCH 18/34] Remove render_mode property It can only have a single value these days, so it is no longer useful. --- core/display.js | 7 +------ core/rfb.js | 3 --- docs/API-internal.md | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/core/display.js b/core/display.js index e7d8bd3a..dbeee244 100644 --- a/core/display.js +++ b/core/display.js @@ -35,7 +35,6 @@ export default function Display(target, defaults) { set_defaults(this, defaults, { 'scale': 1.0, 'viewport': false, - 'render_mode': '', "onFlush": function () {}, }); @@ -74,9 +73,7 @@ export default function Display(target, defaults) { this.clear(); // Check canvas features - if ('createImageData' in this._drawCtx) { - this._render_mode = 'canvas rendering'; - } else { + if (!('createImageData' in this._drawCtx)) { throw new Error("Canvas does not support createImageData"); } @@ -700,8 +697,6 @@ make_properties(Display, [ ['width', 'ro', 'int'], // Display area width ['height', 'ro', 'int'], // Display area height - ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only) - ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI diff --git a/core/rfb.js b/core/rfb.js index ec638a9c..59f0c4cd 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -233,9 +233,6 @@ export default function RFB(target, defaults) { Log.Warn("WebSocket on-error event"); }); - var rmode = this._display.get_render_mode(); - Log.Info("Using native WebSockets, render mode: " + rmode); - Log.Debug("<< RFB.constructor"); }; diff --git a/docs/API-internal.md b/docs/API-internal.md index ec6d7ab7..a5324be5 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -97,7 +97,6 @@ None | 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 From 134ec26ee095ca1bc64a09a11d3ad516ac76d5ff Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 16:44:40 +0200 Subject: [PATCH 19/34] Remove non-JavaScript render code It wasn't used anyway so simplify things. --- core/display.js | 69 ++++++-------- docs/API-internal.md | 1 - tests/test.display.js | 213 +++++++++++++++++++++--------------------- 3 files changed, 130 insertions(+), 153 deletions(-) diff --git a/core/display.js b/core/display.js index dbeee244..6b036ff5 100644 --- a/core/display.js +++ b/core/display.js @@ -77,11 +77,6 @@ export default function Display(target, defaults) { throw new Error("Canvas does not support createImageData"); } - if (this._prefer_js === null) { - Log.Info("Prefering javascript operations"); - this._prefer_js = true; - } - // Determine browser support for setting the cursor via data URI scheme if (this._cursor_uri || this._cursor_uri === null || this._cursor_uri === undefined) { @@ -381,56 +376,45 @@ Display.prototype = { this._tile = this._drawCtx.createImageData(width, height); } - if (this._prefer_js) { - var red = color[2]; - var green = color[1]; - var blue = color[0]; + var red = color[2]; + var green = color[1]; + var blue = color[0]; - var data = this._tile.data; - for (var i = 0; i < width * height * 4; i += 4) { - data[i] = red; - data[i + 1] = green; - data[i + 2] = blue; - data[i + 3] = 255; - } - } else { - this.fillRect(x, y, width, height, color, true); + var data = this._tile.data; + for (var i = 0; i < width * height * 4; i += 4) { + data[i] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = 255; } }, // update sub-rectangle of the current tile subTile: function (x, y, w, h, color) { - if (this._prefer_js) { - var red = color[2]; - var green = color[1]; - var blue = color[0]; - var xend = x + w; - var yend = y + h; + var red = color[2]; + var green = color[1]; + var blue = color[0]; + var xend = x + w; + var yend = y + h; - var data = this._tile.data; - var width = this._tile.width; - for (var j = y; j < yend; j++) { - for (var i = x; i < xend; i++) { - var p = (i + (j * width)) * 4; - data[p] = red; - data[p + 1] = green; - data[p + 2] = blue; - data[p + 3] = 255; - } + var data = this._tile.data; + var width = this._tile.width; + for (var j = y; j < yend; j++) { + for (var i = x; i < xend; i++) { + var p = (i + (j * width)) * 4; + data[p] = red; + data[p + 1] = green; + data[p + 2] = blue; + data[p + 3] = 255; } - } else { - this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true); } }, // draw the current tile to the screen finishTile: function () { - if (this._prefer_js) { - this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); - this._damage(this._tile_x, this._tile_y, - this._tile.width, this._tile.height); - } - // else: No-op -- already done by setSubTile + this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); + this._damage(this._tile_x, this._tile_y, + this._tile.width, this._tile.height); }, blitImage: function (x, y, width, height, arr, offset, from_queue) { @@ -697,7 +681,6 @@ make_properties(Display, [ ['width', 'ro', 'int'], // Display area width ['height', 'ro', 'int'], // Display area height - ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished diff --git a/docs/API-internal.md b/docs/API-internal.md index a5324be5..e33f8069 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -97,7 +97,6 @@ None | viewport | bool | RW | false | Use viewport clipping | width | int | RO | | Display area width | height | int | RO | | Display area height -| prefer_js | str | RW | | Prefer JavaScript over canvas methods | cursor_uri | raw | RW | | Can we render cursor using data URI ### 2.3.2 Methods diff --git a/tests/test.display.js b/tests/test.display.js index 77065872..3a8c13d1 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -40,19 +40,19 @@ describe('Display/Canvas Helper', function () { describe('checking for cursor uri support', function () { it('should disable cursor URIs if there is no support', function () { _forceCursorURIs(false); - var display = new Display(document.createElement('canvas'), { prefer_js: true, viewport: false }); + var display = new Display(document.createElement('canvas'), { viewport: false }); expect(display._cursor_uri).to.be.false; }); it('should enable cursor URIs if there is support', function () { _forceCursorURIs(true); - var display = new Display(document.createElement('canvas'), { prefer_js: true, viewport: false }); + var display = new Display(document.createElement('canvas'), { viewport: false }); expect(display._cursor_uri).to.be.true; }); it('respect the cursor_uri option if there is support', function () { _forceCursorURIs(false); - var display = new Display(document.createElement('canvas'), { prefer_js: true, viewport: false, cursor_uri: false }); + var display = new Display(document.createElement('canvas'), { viewport: false, cursor_uri: false }); expect(display._cursor_uri).to.be.false; }); }); @@ -60,7 +60,7 @@ describe('Display/Canvas Helper', function () { describe('viewport handling', function () { var display; beforeEach(function () { - display = new Display(document.createElement('canvas'), { prefer_js: false, viewport: true }); + display = new Display(document.createElement('canvas'), { viewport: true }); display.resize(5, 5); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -153,7 +153,7 @@ describe('Display/Canvas Helper', function () { describe('resizing', function () { var display; beforeEach(function () { - display = new Display(document.createElement('canvas'), { prefer_js: false, viewport: false }); + display = new Display(document.createElement('canvas'), { viewport: false }); display.resize(4, 4); }); @@ -215,7 +215,7 @@ describe('Display/Canvas Helper', function () { beforeEach(function () { canvas = document.createElement('canvas'); - display = new Display(canvas, { prefer_js: false, viewport: true }); + display = new Display(canvas, { viewport: true }); display.resize(4, 4); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -255,7 +255,7 @@ describe('Display/Canvas Helper', function () { beforeEach(function () { canvas = document.createElement('canvas'); - display = new Display(canvas, { prefer_js: false, viewport: true }); + display = new Display(canvas, { viewport: true }); display.resize(4, 3); document.body.appendChild(canvas); }); @@ -311,124 +311,119 @@ describe('Display/Canvas Helper', function () { // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the // basic cases - function drawing_tests (pref_js) { - var display; - beforeEach(function () { - display = new Display(document.createElement('canvas'), { prefer_js: pref_js }); - display.resize(4, 4); - }); + var display; + beforeEach(function () { + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); - it('should clear the screen on #clear without a logo set', function () { - display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); - display._logo = null; - display.clear(); - display.resize(4, 4); - var empty = []; - for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; } - expect(display).to.have.displayed(new Uint8Array(empty)); - }); + it('should clear the screen on #clear without a logo set', function () { + display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); + display._logo = null; + display.clear(); + display.resize(4, 4); + var empty = []; + for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; } + expect(display).to.have.displayed(new Uint8Array(empty)); + }); - it('should draw the logo on #clear with a logo set', function (done) { - display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) }; - display.clear(); - display.set_onFlush(function () { - expect(display).to.have.displayed(checked_data); - expect(display._fb_width).to.equal(4); - expect(display._fb_height).to.equal(4); - done(); - }); - display.flush(); - }); - - it('should not draw directly on the target canvas', function () { - display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); - display.flip(); - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - var expected = []; - for (var i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) { - expected[i] = 0xff; - expected[i+1] = expected[i+2] = 0; - expected[i+3] = 0xff; - } - expect(display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should support filling a rectangle with particular color via #fillRect', function () { - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); - display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); - display.flip(); + it('should draw the logo on #clear with a logo set', function (done) { + display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) }; + display.clear(); + display.set_onFlush(function () { expect(display).to.have.displayed(checked_data); + expect(display._fb_width).to.equal(4); + expect(display._fb_height).to.equal(4); + done(); }); + display.flush(); + }); - it('should support copying an portion of the canvas via #copyImage', function () { - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); - display.copyImage(0, 0, 2, 2, 2, 2); - display.flip(); + it('should not draw directly on the target canvas', function () { + display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); + display.flip(); + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + var expected = []; + for (var i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) { + expected[i] = 0xff; + expected[i+1] = expected[i+2] = 0; + expected[i+3] = 0xff; + } + expect(display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should support filling a rectangle with particular color via #fillRect', function () { + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); + display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); + + it('should support copying an portion of the canvas via #copyImage', function () { + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); + display.copyImage(0, 0, 2, 2, 2, 2); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing images via #imageRect', function (done) { + display.imageRect(0, 0, "image/png", make_image_png(checked_data)); + display.flip(); + display.set_onFlush(function () { expect(display).to.have.displayed(checked_data); + done(); }); + display.flush(); + }); - it('should support drawing images via #imageRect', function (done) { - display.imageRect(0, 0, "image/png", make_image_png(checked_data)); - display.flip(); - display.set_onFlush(function () { - expect(display).to.have.displayed(checked_data); - done(); - }); - display.flush(); - }); + it('should support drawing tile data with a background color and sub tiles', function () { + display.startTile(0, 0, 4, 4, [0, 0xff, 0]); + display.subTile(0, 0, 2, 2, [0xff, 0, 0]); + display.subTile(2, 2, 2, 2, [0xff, 0, 0]); + display.finishTile(); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); - it('should support drawing tile data with a background color and sub tiles', function () { - display.startTile(0, 0, 4, 4, [0, 0xff, 0]); - display.subTile(0, 0, 2, 2, [0xff, 0, 0]); - display.subTile(2, 2, 2, 2, [0xff, 0, 0]); - display.finishTile(); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); + it('should support drawing BGRX blit images with true color via #blitImage', function () { + var data = []; + for (var i = 0; i < 16; i++) { + data[i * 4] = checked_data[i * 4 + 2]; + data[i * 4 + 1] = checked_data[i * 4 + 1]; + data[i * 4 + 2] = checked_data[i * 4]; + data[i * 4 + 3] = checked_data[i * 4 + 3]; + } + display.blitImage(0, 0, 4, 4, data, 0); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); - it('should support drawing BGRX blit images with true color via #blitImage', function () { - var data = []; - for (var i = 0; i < 16; i++) { - data[i * 4] = checked_data[i * 4 + 2]; - data[i * 4 + 1] = checked_data[i * 4 + 1]; - data[i * 4 + 2] = checked_data[i * 4]; - data[i * 4 + 3] = checked_data[i * 4 + 3]; - } - display.blitImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); + it('should support drawing RGB blit images with true color via #blitRgbImage', function () { + var data = []; + for (var i = 0; i < 16; i++) { + data[i * 3] = checked_data[i * 4]; + data[i * 3 + 1] = checked_data[i * 4 + 1]; + data[i * 3 + 2] = checked_data[i * 4 + 2]; + } + display.blitRgbImage(0, 0, 4, 4, data, 0); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); - it('should support drawing RGB blit images with true color via #blitRgbImage', function () { - var data = []; - for (var i = 0; i < 16; i++) { - data[i * 3] = checked_data[i * 4]; - data[i * 3 + 1] = checked_data[i * 4 + 1]; - data[i * 3 + 2] = checked_data[i * 4 + 2]; - } - display.blitRgbImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - it('should support drawing an image object via #drawImage', function () { - var img = make_image_canvas(checked_data); - display.drawImage(img, 0, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - } - - describe('(prefering native methods)', function () { drawing_tests.call(this, false); }); - describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); }); + it('should support drawing an image object via #drawImage', function () { + var img = make_image_canvas(checked_data); + display.drawImage(img, 0, 0); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); }); describe('the render queue processor', function () { var display; beforeEach(function () { - display = new Display(document.createElement('canvas'), { prefer_js: false }); + display = new Display(document.createElement('canvas')); display.resize(4, 4); sinon.spy(display, '_scan_renderQ'); }); From fdff59eeb4a063d57fb6b8a88a09a64430974583 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 16:49:07 +0200 Subject: [PATCH 20/34] Move cursor URI check to RFB object Keeps the Display object simpler, and avoids having to abuse a property to transfer the information. --- core/display.js | 14 -------------- core/rfb.js | 3 ++- core/util/browsers.js | 8 -------- docs/API-internal.md | 1 - tests/test.display.js | 21 --------------------- 5 files changed, 2 insertions(+), 45 deletions(-) diff --git a/core/display.js b/core/display.js index 6b036ff5..07997cba 100644 --- a/core/display.js +++ b/core/display.js @@ -10,7 +10,6 @@ /*jslint browser: true, white: false */ /*global Util, Base64, changeCursor */ -import { browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js'; import { set_defaults, make_properties } from './util/properties.js'; import * as Log from './util/logging.js'; import Base64 from "./base64.js"; @@ -77,12 +76,6 @@ export default function Display(target, defaults) { throw new Error("Canvas does not support createImageData"); } - // Determine browser support for setting the cursor via data URI scheme - if (this._cursor_uri || this._cursor_uri === null || - this._cursor_uri === undefined) { - this._cursor_uri = cursorURIsSupported(); - } - Log.Debug("<< Display.constructor"); }; @@ -483,11 +476,6 @@ Display.prototype = { }, changeCursor: function (pixels, mask, hotx, hoty, w, h) { - if (this._cursor_uri === false) { - Log.Warn("changeCursor called but no cursor data URI support"); - return; - } - Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h); }, @@ -681,8 +669,6 @@ make_properties(Display, [ ['width', 'ro', 'int'], // Display area width ['height', 'ro', 'int'], // Display area height - ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI - ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished ]); diff --git a/core/rfb.js b/core/rfb.js index 59f0c4cd..55a9650f 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -14,6 +14,7 @@ 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 } from './util/browsers.js'; import Display from "./display.js"; import Keyboard from "./input/keyboard.js"; import Mouse from "./input/mouse.js"; @@ -1470,7 +1471,7 @@ RFB.prototype.set_local_cursor = function (cursor) { this._local_cursor = false; this._display.disableLocalCursor(); //Only show server-side cursor } else { - if (this._display.get_cursor_uri()) { + if (browserSupportsCursorURIs()) { this._local_cursor = true; } else { Log.Warn("Browser does not support local cursor"); diff --git a/core/util/browsers.js b/core/util/browsers.js index 77fca12f..50f59863 100644 --- a/core/util/browsers.js +++ b/core/util/browsers.js @@ -43,11 +43,3 @@ export function browserSupportsCursorURIs () { return _cursor_uris_supported; }; - -export function _forceCursorURIs(enabled) { - if (enabled === undefined || enabled) { - _cursor_uris_supported = true; - } else { - _cursor_uris_supported = false; - } -} diff --git a/docs/API-internal.md b/docs/API-internal.md index e33f8069..b9f3be8c 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -97,7 +97,6 @@ None | viewport | bool | RW | false | Use viewport clipping | width | int | RO | | Display area width | height | int | RO | | Display area height -| cursor_uri | raw | RW | | Can we render cursor using data URI ### 2.3.2 Methods diff --git a/tests/test.display.js b/tests/test.display.js index 3a8c13d1..32d054e3 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -3,7 +3,6 @@ var expect = chai.expect; import Base64 from '../core/base64.js'; import Display from '../core/display.js'; -import { _forceCursorURIs, browserSupportsCursorURIs } from '../core/util/browsers.js'; import sinon from '../vendor/sinon.js'; @@ -37,26 +36,6 @@ describe('Display/Canvas Helper', function () { return Base64.decode(data); } - describe('checking for cursor uri support', function () { - it('should disable cursor URIs if there is no support', function () { - _forceCursorURIs(false); - var display = new Display(document.createElement('canvas'), { viewport: false }); - expect(display._cursor_uri).to.be.false; - }); - - it('should enable cursor URIs if there is support', function () { - _forceCursorURIs(true); - var display = new Display(document.createElement('canvas'), { viewport: false }); - expect(display._cursor_uri).to.be.true; - }); - - it('respect the cursor_uri option if there is support', function () { - _forceCursorURIs(false); - var display = new Display(document.createElement('canvas'), { viewport: false, cursor_uri: false }); - expect(display._cursor_uri).to.be.false; - }); - }); - describe('viewport handling', function () { var display; beforeEach(function () { From 747b462337ae4db11208e42fb951c59e53c44be3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 14 Oct 2017 15:39:56 +0200 Subject: [PATCH 21/34] Use standard JavaScript properties Use normal properties with JavaScript setters and getters instead of our homegrown stuff. This also changes the properties to follow normal naming conventions. --- app/ui.js | 56 ++++----- core/display.js | 81 +++++++------- core/input/keyboard.js | 33 +++--- core/input/mouse.js | 61 +++++----- core/rfb.js | 243 +++++++++++++++++----------------------- core/util/properties.js | 139 ----------------------- docs/API-internal.md | 18 +-- docs/API.md | 47 ++------ tests/playback.js | 20 ++-- tests/test.display.js | 44 ++++---- tests/test.keyboard.js | 144 ++++++++++++------------ tests/test.mouse.js | 198 ++++++++++++++++---------------- tests/test.rfb.js | 175 ++++++++++++++--------------- vnc_lite.html | 20 ++-- 14 files changed, 523 insertions(+), 756 deletions(-) delete mode 100644 core/util/properties.js diff --git a/app/ui.js b/app/ui.js index 9b4fac19..626b5f46 100644 --- a/app/ui.js +++ b/app/ui.js @@ -202,16 +202,16 @@ var UI = { initRFB: function() { try { - UI.rfb = new RFB(document.getElementById('noVNC_canvas'), - {'onNotification': UI.notification, - 'onUpdateState': UI.updateState, - 'onDisconnected': UI.disconnectFinished, - 'onCredentialsRequired': UI.credentials, - 'onCapabilities': function () { UI.updatePowerButton(); UI.initialResize(); }, - 'onClipboard': UI.clipboardReceive, - 'onBell': UI.bell, - 'onFBResize': UI.updateSessionSize, - 'onDesktopName': UI.updateDesktopName}); + UI.rfb = new RFB(document.getElementById('noVNC_canvas')); + UI.rfb.onnotification = UI.notification; + UI.rfb.onupdatestate = UI.updateState; + UI.rfb.ondisconnected = UI.disconnectFinished; + UI.rfb.oncredentialsrequired = UI.credentials; + UI.rfb.oncapabilities = function () { UI.updatePowerButton(); UI.initialResize(); }; + UI.rfb.onclipboard = UI.clipboardReceive; + UI.rfb.onbell = UI.bell; + UI.rfb.onfbresize = UI.updateSessionSize; + UI.rfb.ondesktopname = UI.updateDesktopName; return true; } catch (exc) { var msg = "Unable to create RFB client -- " + exc; @@ -277,8 +277,8 @@ var UI = { document.getElementById("noVNC_keyboard_button") .addEventListener('click', UI.toggleVirtualKeyboard); - UI.touchKeyboard = new Keyboard(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); @@ -494,7 +494,7 @@ var UI = { } // 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') @@ -958,8 +958,8 @@ var UI = { // Disable/enable power button updatePowerButton: function() { if (UI.connected && - UI.rfb.get_capabilities().power && - !UI.rfb.get_view_only()) { + UI.rfb.capabilities.power && + !UI.rfb.viewOnly) { document.getElementById('noVNC_power_button') .classList.remove("noVNC_hidden"); } else { @@ -1224,7 +1224,7 @@ var UI = { if (screen && UI.connected) { var resizeMode = UI.getSetting('resize'); - UI.rfb.set_scale(1); + UI.rfb.scale = 1.0; // Make sure the viewport is adjusted first UI.updateViewClip(); @@ -1306,7 +1306,7 @@ var UI = { updateViewClip: function() { if (!UI.rfb) return; - var cur_clip = UI.rfb.get_viewport(); + var cur_clip = UI.rfb.viewport; var new_clip = UI.getSetting('view_clip'); var resizeSetting = UI.getSetting('resize'); @@ -1319,7 +1319,7 @@ var UI = { } if (cur_clip !== new_clip) { - UI.rfb.set_viewport(new_clip); + UI.rfb.viewport = new_clip; } var size = UI.screenSize(); @@ -1357,7 +1357,7 @@ var UI = { toggleViewDrag: function() { if (!UI.rfb) return; - var drag = UI.rfb.get_viewportDrag(); + var drag = UI.rfb.viewportDrag; UI.setViewDrag(!drag); }, @@ -1365,7 +1365,7 @@ var UI = { setViewDrag: function(drag) { if (!UI.rfb) return; - UI.rfb.set_viewportDrag(drag); + UI.rfb.viewportDrag = drag; UI.updateViewDrag(); }, @@ -1377,7 +1377,7 @@ var UI = { // Check if viewport drag is possible. It is only possible // if the remote display is clipping the client display. - if (UI.rfb.get_viewport() && + if (UI.rfb.viewport && UI.rfb.clippingDisplay()) { clipping = true; } @@ -1385,14 +1385,14 @@ var UI = { var viewDragButton = document.getElementById('noVNC_view_drag_button'); if (!clipping && - UI.rfb.get_viewportDrag()) { + UI.rfb.viewportDrag) { // The size of the remote display is the same or smaller // than the client display. Make sure viewport drag isn't // active when it can't be used. - UI.rfb.set_viewportDrag(false); + UI.rfb.viewportDrag = false; } - if (UI.rfb.get_viewportDrag()) { + if (UI.rfb.viewportDrag) { viewDragButton.classList.add("noVNC_selected"); } else { viewDragButton.classList.remove("noVNC_selected"); @@ -1661,9 +1661,9 @@ var UI = { * ------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.set_touchButton(num); + UI.rfb.touchButton = num; } var blist = [0, 1,2,4]; @@ -1680,12 +1680,12 @@ var UI = { updateLocalCursor: function() { if (!UI.rfb) return; - UI.rfb.set_local_cursor(UI.getSetting('cursor')); + UI.rfb.localCursor = 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() { diff --git a/core/display.js b/core/display.js index 07997cba..07d4ef5b 100644 --- a/core/display.js +++ b/core/display.js @@ -10,11 +10,10 @@ /*jslint browser: true, white: false */ /*global Util, Base64, changeCursor */ -import { set_defaults, make_properties } from './util/properties.js'; import * as Log from './util/logging.js'; import Base64 from "./base64.js"; -export default function Display(target, defaults) { +export default function Display(target) { this._drawCtx = null; this._c_forceCanvas = false; @@ -31,12 +30,6 @@ export default function Display(target, defaults) { this._tile_x = 0; this._tile_y = 0; - set_defaults(this, defaults, { - 'scale': 1.0, - 'viewport': false, - "onFlush": function () {}, - }); - Log.Debug(">> Display.constructor"); // The visible canvas @@ -88,7 +81,39 @@ try { } Display.prototype = { - // Public methods + // ===== PROPERTIES ===== + + _scale: 1.0, + get scale() { return this._scale; }, + set scale(scale) { + this._rescale(scale); + }, + + _viewport: false, + get viewport() { return this._viewport; }, + set viewport(viewport) { + this._viewport = viewport; + // May need to readjust the viewport dimensions + var vp = this._viewportLoc; + this.viewportChangeSize(vp.w, vp.h); + this.viewportChangePos(0, 0); + }, + + get width() { + return this._fb_width; + }, + get height() { + return this._fb_height; + }, + + logo: null, + + // ===== EVENT HANDLERS ===== + + onflush: function () {}, // A flush request has finished + + // ===== PUBLIC METHODS ===== + viewportChangePos: function (deltaX, deltaY) { var vp = this._viewportLoc; deltaX = Math.floor(deltaX); @@ -294,7 +319,7 @@ Display.prototype = { flush: function() { if (this._renderQ.length === 0) { - this._onFlush(); + this.onflush(); } else { this._flushing = true; } @@ -492,26 +517,6 @@ Display.prototype = { return this._fb_width > vp.w || this._fb_height > vp.h; }, - // Overridden getters/setters - set_scale: function (scale) { - this._rescale(scale); - }, - - set_viewport: function (viewport) { - this._viewport = viewport; - // May need to readjust the viewport dimensions - var vp = this._viewportLoc; - this.viewportChangeSize(vp.w, vp.h); - this.viewportChangePos(0, 0); - }, - - get_width: function () { - return this._fb_width; - }, - get_height: function () { - return this._fb_height; - }, - autoscale: function (containerWidth, containerHeight, downscaleOnly) { var vp = this._viewportLoc; var targetAspectRatio = containerWidth / containerHeight; @@ -531,7 +536,8 @@ Display.prototype = { this._rescale(scaleRatio); }, - // Private Methods + // ===== PRIVATE METHODS ===== + _rescale: function (factor) { this._scale = factor; var vp = this._viewportLoc; @@ -656,22 +662,11 @@ Display.prototype = { if (this._renderQ.length === 0 && this._flushing) { this._flushing = false; - this._onFlush(); + this.onflush(); } }, }; -make_properties(Display, [ - ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only) - ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data} - ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 - ['viewport', 'rw', 'bool'], // Use viewport clipping - ['width', 'ro', 'int'], // Display area width - ['height', 'ro', 'int'], // Display area height - - ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished -]); - // Class Methods Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) { if ((w === 0) || (h === 0)) { diff --git a/core/input/keyboard.js b/core/input/keyboard.js index 6dee460f..464d1ce8 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -10,7 +10,6 @@ import * as Log from '../util/logging.js'; import { stopEvent } from '../util/events.js'; -import { set_defaults, make_properties } from '../util/properties.js'; import * as KeyboardUtil from "./util.js"; import KeyTable from "./keysym.js"; @@ -18,15 +17,13 @@ import KeyTable from "./keysym.js"; // Keyboard event handler // -export default function Keyboard(target, defaults) { +export default function Keyboard(target) { this._target = target || null; this._keyDownList = {}; // List of depressed keys // (even if they are happy) this._pendingKey = null; // Key waiting for keypress - set_defaults(this, defaults, {}); - // keep these here so we can refer to them later this._eventHandlers = { 'keyup': this._handleKeyUp.bind(this), @@ -56,14 +53,14 @@ function isEdge() { } Keyboard.prototype = { - // private methods + // ===== EVENT HANDLERS ===== + + onkeyevent: function () {}, // Handler for key press/release + + // ===== PRIVATE METHODS ===== _sendKeyEvent: function (keysym, code, down) { - if (!this._onKeyEvent) { - return; - } - - Log.Debug("onKeyEvent " + (down ? "down" : "up") + + Log.Debug("onkeyevent " + (down ? "down" : "up") + ", keysym: " + keysym, ", code: " + code); // Windows sends CtrlLeft+AltRight when you press @@ -77,19 +74,19 @@ Keyboard.prototype = { ('ControlLeft' in this._keyDownList) && ('AltRight' in this._keyDownList)) { fakeAltGraph = true; - this._onKeyEvent(this._keyDownList['AltRight'], + this.onkeyevent(this._keyDownList['AltRight'], 'AltRight', false); - this._onKeyEvent(this._keyDownList['ControlLeft'], + this.onkeyevent(this._keyDownList['ControlLeft'], 'ControlLeft', false); } } - this._onKeyEvent(keysym, code, down); + this.onkeyevent(keysym, code, down); if (fakeAltGraph) { - this._onKeyEvent(this._keyDownList['ControlLeft'], + this.onkeyevent(this._keyDownList['ControlLeft'], 'ControlLeft', true); - this._onKeyEvent(this._keyDownList['AltRight'], + this.onkeyevent(this._keyDownList['AltRight'], 'AltRight', true); } }, @@ -305,7 +302,7 @@ Keyboard.prototype = { Log.Debug("<< Keyboard.allKeysUp"); }, - // Public methods + // ===== PUBLIC METHODS ===== grab: function () { //Log.Debug(">> Keyboard.grab"); @@ -336,7 +333,3 @@ Keyboard.prototype = { //Log.Debug(">> Keyboard.ungrab"); }, }; - -make_properties(Keyboard, [ - ['onKeyEvent', 'rw', 'func'] // Handler for key press/release -]); diff --git a/core/input/mouse.js b/core/input/mouse.js index ae3ca106..eaf908ad 100644 --- a/core/input/mouse.js +++ b/core/input/mouse.js @@ -11,13 +11,12 @@ import * as Log from '../util/logging.js'; import { isTouchDevice } from '../util/browsers.js'; import { setCapture, stopEvent, getPointerEvent } from '../util/events.js'; -import { set_defaults, make_properties } from '../util/properties.js'; var WHEEL_STEP = 10; // Delta threshold for a mouse wheel step var WHEEL_STEP_TIMEOUT = 50; // ms var WHEEL_LINE_HEIGHT = 19; -export default function Mouse(target, defaults) { +export default function Mouse(target) { this._target = target || document; this._doubleClickTimer = null; @@ -29,11 +28,6 @@ export default function Mouse(target, defaults) { this._accumulatedWheelDeltaX = 0; this._accumulatedWheelDeltaY = 0; - // Configuration attributes - set_defaults(this, defaults, { - 'touchButton': 1 - }); - this._eventHandlers = { 'mousedown': this._handleMouseDown.bind(this), 'mouseup': this._handleMouseUp.bind(this), @@ -44,7 +38,16 @@ export default function Mouse(target, defaults) { }; Mouse.prototype = { - // private methods + // ===== PROPERTIES ===== + + touchButton: 1, // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) + + // ===== EVENT HANDLERS ===== + + onmousebutton: function () {}, // Handler for mouse button click/release + onmousemove: function () {}, // Handler for mouse movement + + // ===== PRIVATE METHODS ===== _resetDoubleClickTimer: function () { this._doubleClickTimer = null; @@ -83,7 +86,7 @@ Mouse.prototype = { } this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); } - bmask = this._touchButton; + bmask = this.touchButton; // If bmask is set } else if (e.which) { /* everything except IE */ @@ -95,11 +98,10 @@ Mouse.prototype = { (e.button & 0x4) / 2; // Middle } - if (this._onMouseButton) { - Log.Debug("onMouseButton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - this._onMouseButton(pos.x, pos.y, down, bmask); - } + Log.Debug("onmousebutton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + this.onmousebutton(pos.x, pos.y, down, bmask); + stopEvent(e); }, @@ -122,11 +124,11 @@ Mouse.prototype = { _generateWheelStepX: function () { if (this._accumulatedWheelDeltaX < 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 5); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 5); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5); } else if (this._accumulatedWheelDeltaX > 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 6); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 6); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6); } this._accumulatedWheelDeltaX = 0; @@ -135,11 +137,11 @@ Mouse.prototype = { _generateWheelStepY: function () { if (this._accumulatedWheelDeltaY < 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 3); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 3); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3); } else if (this._accumulatedWheelDeltaY > 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 4); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 4); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4); } this._accumulatedWheelDeltaY = 0; @@ -153,8 +155,6 @@ Mouse.prototype = { }, _handleMouseWheel: function (e) { - if (!this._onMouseButton) { return; } - this._resetWheelStepTimers(); this._updateMousePosition(e); @@ -199,9 +199,7 @@ Mouse.prototype = { _handleMouseMove: function (e) { this._updateMousePosition(e); - if (this._onMouseMove) { - this._onMouseMove(this._pos.x, this._pos.y); - } + this.onmousemove(this._pos.x, this._pos.y); stopEvent(e); }, @@ -240,7 +238,8 @@ Mouse.prototype = { this._pos = {x:x, y:y}; }, - // Public methods + // ===== PUBLIC METHODS ===== + grab: function () { var c = this._target; @@ -282,9 +281,3 @@ Mouse.prototype = { c.removeEventListener('contextmenu', this._eventHandlers.mousedisable); } }; - -make_properties(Mouse, [ - ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release - ['onMouseMove', 'rw', 'func'], // Handler for mouse movement - ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) -]); diff --git a/core/rfb.js b/core/rfb.js index 55a9650f..2b2dc849 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -13,7 +13,6 @@ 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 } from './util/browsers.js'; import Display from "./display.js"; import Keyboard from "./input/keyboard.js"; @@ -29,11 +28,7 @@ import { encodings, encodingName } from "./encodings.js"; /*jslint white: false, browser: true */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */ -export default function RFB(target, defaults) { - "use strict"; - if (!defaults) { - defaults = {}; - } +export default function RFB(target) { this._target = target; // Connection details @@ -128,25 +123,6 @@ export default function RFB(target, defaults) { this._viewportDragPos = {}; this._viewportHasMoved = false; - // set the default value on user-facing properties - set_defaults(this, defaults, { - 'local_cursor': false, // Request locally rendered cursor - 'view_only': false, // Disable client mouse/keyboard - 'disconnectTimeout': 3, // Time (s) to wait for disconnection - '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 - 'onCredentialsRequired': function () { }, // onCredentialsRequired(rfb, types): VNC credentials are required - 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received - 'onBell': function () { }, // onBell(rfb): RFB Bell message received - 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized - 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received - 'onCapabilities': function () { } // onCapabilities(rfb, caps): the supported capabilities has changed - }); - // main setup Log.Debug(">> RFB.constructor"); @@ -171,20 +147,20 @@ export default function RFB(target, defaults) { // NB: nothing that needs explicit teardown should be done // before this point, since this can throw an exception try { - this._display = new Display(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(this._target, - {onKeyEvent: this._handleKeyEvent.bind(this)}); + this._keyboard = new Keyboard(this._target); + this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); - this._mouse = new Mouse(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)); @@ -238,7 +214,74 @@ export default function RFB(target, defaults) { }; RFB.prototype = { - // Public methods + // ===== PROPERTIES ===== + + disconnectTimeout: 3, + viewportDrag: false, + + _localCursor: false, + get localCursor() { return this._localCursor; }, + set localCursor(cursor) { + if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { + this._localCursor = false; + this._display.disableLocalCursor(); //Only show server-side cursor + } else { + if (browserSupportsCursorURIs()) { + this._localCursor = 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(); + } + }, + + _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 scale() { return this._display.scale; }, + set scale(scale) { this._display.scale = scale; }, + + get viewport() { return this._display.viewport; }, + set viewport(viewport) { this._display.viewport = viewport; }, + + // ===== EVENT HANDLERS ===== + + onupdatestate: function () {}, // onupdatestate(rfb, state, oldstate): connection state change + onnotification: function () {}, // onnotification(rfb, msg, level, options): notification for the UI + ondisconnected: function () {}, // ondisconnected(rfb, reason): disconnection finished + oncredentialsrequired: function () {}, // oncredentialsrequired(rfb, types): VNC credentials are required + onclipboard: function () {}, // onclipboard(rfb, text): RFB clipboard contents received + onbell: function () {}, // onbell(rfb): RFB Bell message received + onfbresize: function () {}, // onfbresize(rfb, width, height): frame buffer resized + ondesktopname: function () {}, // ondesktopname(rfb, name): desktop name received + oncapabilities: function () {}, // oncapabilities(rfb, caps): the supported capabilities has changed + + // ===== PUBLIC METHODS ===== + connect: function (url, options) { if (!url) { this._fail(_("Must specify URL")); @@ -271,7 +314,7 @@ RFB.prototype = { }, sendCtrlAltDel: function () { - if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } + if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return false; } Log.Info("Sending Ctrl-Alt-Del"); this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); @@ -299,7 +342,7 @@ RFB.prototype = { // 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 false; } if (down === undefined) { this.sendKey(keysym, code, true); @@ -328,7 +371,7 @@ RFB.prototype = { }, 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); }, @@ -351,7 +394,7 @@ RFB.prototype = { // 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) { + this._viewOnly) { return false; } @@ -366,7 +409,7 @@ RFB.prototype = { }, - // Private methods + // ===== PRIVATE METHODS ===== _connect: function () { Log.Debug(">> RFB.connect"); @@ -420,8 +463,8 @@ RFB.prototype = { }, _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 when disconnected, unless in @@ -500,7 +543,7 @@ RFB.prototype = { // State change actions this._rfb_connection_state = state; - this._onUpdateState(this, state, oldstate); + this.onupdatestate(this, state, oldstate); var smsg = "New state '" + state + "', was '" + oldstate + "'."; Log.Debug(smsg); @@ -516,13 +559,13 @@ RFB.prototype = { switch (state) { case 'disconnected': - // Call onDisconnected callback after onUpdateState since + // Call ondisconnected callback after onupdatestate since // we don't know if the UI only displays the latest message if (this._rfb_disconnect_reason !== "") { - this._onDisconnected(this, this._rfb_disconnect_reason); + this.ondisconnected(this, this._rfb_disconnect_reason); } else { // No reason means clean disconnect - this._onDisconnected(this); + this.ondisconnected(this); } break; @@ -595,15 +638,15 @@ RFB.prototype = { } if (options) { - this._onNotification(this, msg, level, options); + this.onnotification(this, msg, level, options); } else { - this._onNotification(this, msg, level); + this.onnotification(this, msg, level); } }, _setCapability: function (cap, val) { this._capabilities[cap] = val; - this._onCapabilities(this, this._capabilities); + this.oncapabilities(this, this._capabilities); }, _handle_message: function () { @@ -658,14 +701,14 @@ RFB.prototype = { // 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); @@ -692,7 +735,7 @@ RFB.prototype = { 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); @@ -813,7 +856,7 @@ RFB.prototype = { if (!this._rfb_credentials.username || !this._rfb_credentials.password || !this._rfb_credentials.target) { - this._onCredentialsRequired(this, ["username", "password", "target"]); + this.oncredentialsrequired(this, ["username", "password", "target"]); return false; } @@ -830,7 +873,7 @@ RFB.prototype = { if (this._sock.rQwait("auth challenge", 16)) { return false; } if (!this._rfb_credentials.password) { - this._onCredentialsRequired(this, ["password"]); + this.oncredentialsrequired(this, ["password"]); return false; } @@ -1067,12 +1110,12 @@ RFB.prototype = { } // we're past the point where we could backtrack, so it's safe to call this - this._onDesktopName(this, this._fb_name); + this.ondesktopname(this, this._fb_name); 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; @@ -1122,7 +1165,7 @@ RFB.prototype = { encs.push(encodings.pseudoEncodingFence); encs.push(encodings.pseudoEncodingContinuousUpdates); - if (this._local_cursor && this._fb_depth == 24) { + if (this._localCursor && this._fb_depth == 24) { encs.push(encodings.pseudoEncodingCursor); } @@ -1181,9 +1224,9 @@ RFB.prototype = { var text = this._sock.rQshiftStr(length); - if (this._view_only) { return true; } + if (this._viewOnly) { return true; } - this._onClipboard(this, text); + this.onclipboard(this, text); return true; }, @@ -1279,7 +1322,7 @@ RFB.prototype = { case 2: // Bell Log.Debug("Bell"); - this._onBell(this); + this.onbell(this); return true; case 3: // ServerCutText @@ -1431,7 +1474,7 @@ RFB.prototype = { this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); this._display.resize(this._fb_width, this._fb_height); - this._onFBResize(this, this._fb_width, this._fb_height); + this.onfbresize(this, this._fb_width, this._fb_height); this._timing.fbu_rt_start = (new Date()).getTime(); this._updateContinuousUpdates(); @@ -1444,86 +1487,6 @@ RFB.prototype = { }, }; -make_properties(RFB, [ - ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor - ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard - ['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) - ['scale', 'rw', 'float'], // Display area scale factor - ['viewport', 'rw', 'bool'], // Use viewport clipping - ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection - ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags - ['capabilities', 'ro', 'arr'], // Supported capabilities - - // 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 - ['onCredentialsRequired', 'rw', 'func'], // onCredentialsRequired(rfb, types): VNC credentials are required - ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received - ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received - ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized - ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received - ['onCapabilities', 'rw', 'func'] // onCapabilities(rfb, caps): the supported capabilities has changed -]); - -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 (browserSupportsCursorURIs()) { - 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(); - } - } -}; - -RFB.prototype.set_touchButton = function (button) { - this._mouse.set_touchButton(button); -}; - -RFB.prototype.get_touchButton = function () { - return this._mouse.get_touchButton(); -}; - -RFB.prototype.set_scale = function (scale) { - this._display.set_scale(scale); -}; - -RFB.prototype.get_scale = function () { - return this._display.get_scale(); -}; - -RFB.prototype.set_viewport = function (viewport) { - this._display.set_viewport(viewport); -}; - -RFB.prototype.get_viewport = function () { - return this._display.get_viewport(); -}; - // Class Methods RFB.messages = { keyEvent: function (sock, keysym, down) { diff --git a/core/util/properties.js b/core/util/properties.js deleted file mode 100644 index 7b08a644..00000000 --- a/core/util/properties.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Licensed under MPL 2.0 (see LICENSE.txt) - * - * See README.md for usage and integration instructions. - */ - -/* - * Getter/Setter Creation Utilities - */ - -import * as Log from './logging.js'; - -function make_property (proto, name, mode, type) { - "use strict"; - - var getter; - if (type === 'arr') { - getter = function (idx) { - if (typeof idx !== 'undefined') { - return this['_' + name][idx]; - } else { - return this['_' + name]; - } - }; - } else { - getter = function () { - return this['_' + name]; - }; - } - - var make_setter = function (process_val) { - if (process_val) { - return function (val, idx) { - if (typeof idx !== 'undefined') { - this['_' + name][idx] = process_val(val); - } else { - this['_' + name] = process_val(val); - } - }; - } else { - return function (val, idx) { - if (typeof idx !== 'undefined') { - this['_' + name][idx] = val; - } else { - this['_' + name] = val; - } - }; - } - }; - - var setter; - if (type === 'bool') { - setter = make_setter(function (val) { - if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { - return false; - } else { - return true; - } - }); - } else if (type === 'int') { - setter = make_setter(function (val) { return parseInt(val, 10); }); - } else if (type === 'float') { - setter = make_setter(parseFloat); - } else if (type === 'str') { - setter = make_setter(String); - } else if (type === 'func') { - setter = make_setter(function (val) { - if (!val) { - return function () {}; - } else { - return val; - } - }); - } else if (type === 'arr' || type === 'dom' || type == 'raw') { - setter = make_setter(); - } else { - throw new Error('Unknown property type ' + type); // some sanity checking - } - - // set the getter - if (typeof proto['get_' + name] === 'undefined') { - proto['get_' + name] = getter; - } - - // set the setter if needed - if (typeof proto['set_' + name] === 'undefined') { - if (mode === 'rw') { - proto['set_' + name] = setter; - } else if (mode === 'wo') { - proto['set_' + name] = function (val, idx) { - if (typeof this['_' + name] !== 'undefined') { - throw new Error(name + " can only be set once"); - } - setter.call(this, val, idx); - }; - } - } - - // make a special setter that we can use in set defaults - proto['_raw_set_' + name] = function (val, idx) { - setter.call(this, val, idx); - //delete this['_init_set_' + name]; // remove it after use - }; -}; - -export function make_properties (constructor, arr) { - "use strict"; - for (var i = 0; i < arr.length; i++) { - make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]); - } -}; - -export function set_defaults (obj, conf, defaults) { - var defaults_keys = Object.keys(defaults); - conf = conf || {}; - var conf_keys = Object.keys(conf); - var keys_obj = {}; - var i; - for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; } - for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; } - var keys = Object.keys(keys_obj); - - for (i = 0; i < keys.length; i++) { - var setter = obj['_raw_set_' + keys[i]]; - if (!setter) { - Log.Warn('Invalid property ' + keys[i]); - continue; - } - - if (keys[i] in conf) { - setter.call(obj, conf[keys[i]]); - } else { - setter.call(obj, defaults[keys[i]]); - } - } -}; - diff --git a/docs/API-internal.md b/docs/API-internal.md index b9f3be8c..6a45d38f 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -26,14 +26,7 @@ with transparent binary data support. [Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page. -## 1.2 Configuration Attributes - -The Mouse, Keyboard and Display objects have a similar API for -configuration options as the RFB object. See the official API -documentation for details. - - -## 1.3 Callbacks +## 1.2 Callbacks For the Mouse, Keyboard and Display objects the callback functions are assigned to configuration attributes, just as for the RFB object. The @@ -61,8 +54,8 @@ callback event name, and the callback function. | name | parameters | description | ------------- | ------------------- | ------------ -| onMouseButton | (x, y, down, bmask) | Handler for mouse button click/release -| onMouseMove | (x, y) | Handler for mouse movement +| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release +| onmousemove | (x, y) | Handler for mouse movement ## 2.2 Keyboard Module @@ -82,7 +75,7 @@ None | name | parameters | description | ---------- | -------------------- | ------------ -| onKeyPress | (keysym, code, down) | Handler for key press/release +| onkeypress | (keysym, code, down) | Handler for key press/release ## 2.3 Display Module @@ -91,7 +84,6 @@ None | name | type | mode | default | description | ----------- | ----- | ---- | ------- | ------------ -| 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 @@ -131,4 +123,4 @@ None | name | parameters | description | ------- | ---------- | ------------ -| onFlush | () | A display flush has been requested and we are now ready to resume FBU processing +| onflush | () | A display flush has been requested and we are now ready to resume FBU processing diff --git a/docs/API.md b/docs/API.md index 63293c0b..64627698 100644 --- a/docs/API.md +++ b/docs/API.md @@ -6,31 +6,10 @@ is instantiated once per connection. ## 1 Configuration Attributes -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 'view_only' configuration option -enabled, then confirms it was set, then disables it: - - var rfb = new RFB(target, {'view_only': true}); - if (rfb.get_view_only()) { - alert("View Only is set"); - } - rfb.set_view_only(false); - -Some attributes are read-only and cannot be changed. An exception will -be thrown if an attempt is made to set one of these attributs. The -attribute mode is one of the following: - - RO - read only - RW - read write - WO - write once - | name | type | mode | default | description | ----------------- | ----- | ---- | ---------- | ------------ -| local_cursor | bool | RW | false | Request locally rendered cursor -| view_only | bool | RW | false | Disable client mouse/keyboard +| localCursor | bool | RW | false | Request locally rendered cursor +| viewOnly | bool | RW | false | Disable client mouse/keyboard | touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. | scale | float | RW | 1.0 | Display area scale factor | viewport | bool | RW | false | Use viewport clipping @@ -41,10 +20,6 @@ attribute mode is one of the following: ## 2 Methods -In addition to the getter and setter methods to modify configuration -attributes, the RFB object has other methods that are available in the -object instance. - | name | parameters | description | ------------------ | ------------------------------- | ------------ | connect | (url, options) | Connect to the given URL @@ -79,15 +54,15 @@ functions. | 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. -| onCredentialsRequired | (rfb, types) | VNC credentials are required (use sendCredentials) -| onClipboard | (rfb, text) | RFB clipboard contents received -| onBell | (rfb) | RFB Bell message received -| onFBResize | (rfb, width, height) | Frame buffer (remote desktop) size changed -| onDesktopName | (rfb, name) | VNC desktop name recieved -| onCapabilities | (rfb, capabilities) | The supported capabilities has changed +| 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. +| oncredentialsrequired | (rfb, types) | VNC credentials are required (use sendCredentials) +| onclipboard | (rfb, text) | RFB clipboard contents received +| onbell | (rfb) | RFB Bell message received +| onfbresize | (rfb, width, height) | Frame buffer (remote desktop) size changed +| ondesktopname | (rfb, name) | VNC desktop name recieved +| oncapabilities | (rfb, capabilities) | The supported capabilities has changed __RFB onUpdateState callback details__ diff --git a/tests/playback.js b/tests/playback.js index c2567a95..03748c58 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -77,10 +77,10 @@ export default function RecordingPlayer (frames, encoding, disconnected, notific RecordingPlayer.prototype = { run: function (realtime, trafficManagement) { // initialize a new RFB - this._rfb = new RFB(document.getElementById('VNC_canvas'), - {'view_only': true, - 'onDisconnected': this._handleDisconnect.bind(this), - 'onNotification': this._notification}); + this._rfb = new RFB(document.getElementById('VNC_canvas')); + this._rfb.viewOnly = true; + this._rfb.ondisconnected = this._handleDisconnect.bind(this); + this._rfb.onnotification = this._notification; this._enablePlaybackMode(); // reset the frame index and timer @@ -152,12 +152,12 @@ RecordingPlayer.prototype = { // Avoid having excessive queue buildup in non-realtime mode if (this._trafficManagement && this._rfb._flushing) { let player = this; - let orig = this._rfb._display.get_onFlush(); - this._rfb._display.set_onFlush(function () { - player._rfb._display.set_onFlush(orig); + let orig = this._rfb._display.onflush; + this._rfb._display.onflush = function () { + player._rfb._display.onflush = orig; player._rfb._onFlush(); player._doPacket(); - }); + }; return; } @@ -182,12 +182,12 @@ RecordingPlayer.prototype = { _finish() { if (this._rfb._display.pending()) { var player = this; - this._rfb._display.set_onFlush(function () { + this._rfb._display.onflush = function () { if (player._rfb._flushing) { player._rfb._onFlush(); } player._finish(); - }); + }; this._rfb._display.flush(); } else { this._running = false; diff --git a/tests/test.display.js b/tests/test.display.js index 32d054e3..675b43d7 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -39,7 +39,8 @@ describe('Display/Canvas Helper', function () { describe('viewport handling', function () { var display; beforeEach(function () { - display = new Display(document.createElement('canvas'), { viewport: true }); + display = new Display(document.createElement('canvas')); + display.viewport = true; display.resize(5, 5); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -102,7 +103,7 @@ describe('Display/Canvas Helper', function () { }); it('should show the entire framebuffer when disabling the viewport', function() { - display.set_viewport(false); + display.viewport = false; expect(display.absX(0)).to.equal(0); expect(display.absY(0)).to.equal(0); expect(display._target.width).to.equal(5); @@ -110,7 +111,7 @@ describe('Display/Canvas Helper', function () { }); it('should ignore viewport changes when the viewport is disabled', function() { - display.set_viewport(false); + display.viewport = false; display.viewportChangeSize(2, 2); display.viewportChangePos(1, 1); expect(display.absX(0)).to.equal(0); @@ -120,8 +121,8 @@ describe('Display/Canvas Helper', function () { }); it('should show the entire framebuffer just after enabling the viewport', function() { - display.set_viewport(false); - display.set_viewport(true); + display.viewport = false; + display.viewport = true; expect(display.absX(0)).to.equal(0); expect(display.absY(0)).to.equal(0); expect(display._target.width).to.equal(5); @@ -132,7 +133,8 @@ describe('Display/Canvas Helper', function () { describe('resizing', function () { var display; beforeEach(function () { - display = new Display(document.createElement('canvas'), { viewport: false }); + display = new Display(document.createElement('canvas')); + display.viewport = false; display.resize(4, 4); }); @@ -157,7 +159,7 @@ describe('Display/Canvas Helper', function () { describe('viewport', function () { beforeEach(function () { - display.set_viewport(true); + display.viewport = true; display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); }); @@ -194,7 +196,8 @@ describe('Display/Canvas Helper', function () { beforeEach(function () { canvas = document.createElement('canvas'); - display = new Display(canvas, { viewport: true }); + display = new Display(canvas); + display.viewport = true; display.resize(4, 4); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -206,21 +209,21 @@ describe('Display/Canvas Helper', function () { }); it('should not change the bitmap size of the canvas', function () { - display.set_scale(2.0); + display.scale = 2.0; expect(canvas.width).to.equal(3); expect(canvas.height).to.equal(3); }); it('should change the effective rendered size of the canvas', function () { - display.set_scale(2.0); + display.scale = 2.0; expect(canvas.clientWidth).to.equal(6); expect(canvas.clientHeight).to.equal(6); }); it('should not change when resizing', function () { - display.set_scale(2.0); + display.scale = 2.0; display.resize(5, 5); - expect(display.get_scale()).to.equal(2.0); + expect(display.scale).to.equal(2.0); expect(canvas.width).to.equal(3); expect(canvas.height).to.equal(3); expect(canvas.clientWidth).to.equal(6); @@ -234,7 +237,8 @@ describe('Display/Canvas Helper', function () { beforeEach(function () { canvas = document.createElement('canvas'); - display = new Display(canvas, { viewport: true }); + display = new Display(canvas); + display.viewport = true; display.resize(4, 3); document.body.appendChild(canvas); }); @@ -309,12 +313,12 @@ describe('Display/Canvas Helper', function () { it('should draw the logo on #clear with a logo set', function (done) { display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) }; display.clear(); - display.set_onFlush(function () { + display.onflush = function () { expect(display).to.have.displayed(checked_data); expect(display._fb_width).to.equal(4); expect(display._fb_height).to.equal(4); done(); - }); + }; display.flush(); }); @@ -350,10 +354,10 @@ describe('Display/Canvas Helper', function () { it('should support drawing images via #imageRect', function (done) { display.imageRect(0, 0, "image/png", make_image_png(checked_data)); display.flip(); - display.set_onFlush(function () { + display.onflush = function () { expect(display).to.have.displayed(checked_data); done(); - }); + }; display.flush(); }); @@ -442,11 +446,11 @@ describe('Display/Canvas Helper', function () { }); it('should call callback when queue is flushed', function () { - display.set_onFlush(sinon.spy()); + display.onflush = sinon.spy(); display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - expect(display.get_onFlush()).to.not.have.been.called; + expect(display.onflush).to.not.have.been.called; display.flush(); - expect(display.get_onFlush()).to.have.been.calledOnce; + expect(display.onflush).to.have.been.calledOnce; }); it('should draw a blit image on type "blit"', function () { diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 52f21f0e..513d797f 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -31,105 +31,105 @@ describe('Key Event Handling', function() { describe('Decode Keyboard Events', function() { it('should decode keydown events', function(done) { if (isIE() || isEdge()) this.skip(); - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); }); it('should decode keyup events', function(done) { if (isIE() || isEdge()) this.skip(); var calls = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); if (calls++ === 1) { expect(down).to.be.equal(false); done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); }); describe('Legacy keypress Events', function() { it('should wait for keypress when needed', function() { - var callback = sinon.spy(); - var kbd = new Keyboard(document, {onKeyEvent: callback}); + var kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); - expect(callback).to.not.have.been.called; + expect(kbd.onkeyevent).to.not.have.been.called; }); it('should decode keypress events', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61})); }); it('should ignore keypress with different code', function() { - var callback = sinon.spy(); - var kbd = new Keyboard(document, {onKeyEvent: callback}); + var kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61})); - expect(callback).to.not.have.been.called; + expect(kbd.onkeyevent).to.not.have.been.called; }); it('should handle keypress with missing code', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61})); }); it('should guess key if no keypress and numeric key', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x32); expect(code).to.be.equal('Digit2'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32})); }); it('should guess key if no keypress and alpha key', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false})); }); it('should guess key if no keypress and alpha key (with shift)', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x41); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true})); }); it('should not guess key if no keypress and unknown key', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09})); }); }); @@ -168,8 +168,8 @@ describe('Key Event Handling', function() { it('should fake keyup events for virtual keyboards', function(done) { if (isIE() || isEdge()) this.skip(); var count = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch (count++) { case 0: expect(keysym).to.be.equal(0x61); @@ -182,7 +182,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(false); done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'})); }); @@ -215,8 +215,8 @@ describe('Key Event Handling', function() { it('should fake keyup events on iOS', function(done) { if (isIE() || isEdge()) this.skip(); var count = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch (count++) { case 0: expect(keysym).to.be.equal(0x61); @@ -229,7 +229,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(false); done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); }); }); @@ -240,67 +240,67 @@ describe('Key Event Handling', function() { if (isIE() || isEdge()) this.skip(); }); it('should send release using the same keysym as the press', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); if (!down) { done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'})); }); it('should send the same keysym for multiple presses', function() { var count = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); count++; - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); expect(count).to.be.equal(2); }); it('should do nothing on keyup events if no keys are down', function() { - var callback = sinon.spy(); - var kbd = new Keyboard(document, {onKeyEvent: callback}); + var kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); - expect(callback).to.not.have.been.called; + expect(kbd.onkeyevent).to.not.have.been.called; }); describe('Legacy Events', function() { it('should track keys using keyCode if no code', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Platform65'); if (!down) { done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'})); }); it('should ignore compositing code', function() { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Unidentified'); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'})); }); it('should track keys using keyIdentifier if no code', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Platform65'); if (!down) { done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'})); }); @@ -335,8 +335,8 @@ describe('Key Event Handling', function() { it('should change Alt to AltGraph', function() { var count = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch (count++) { case 0: expect(keysym).to.be.equal(0xFF7E); @@ -347,27 +347,27 @@ describe('Key Event Handling', function() { expect(code).to.be.equal('AltRight'); break; } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); expect(count).to.be.equal(2); }); it('should change left Super to Alt', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0xFFE9); expect(code).to.be.equal('MetaLeft'); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); }); it('should change right Super to left Super', function(done) { - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0xFFEB); expect(code).to.be.equal('MetaRight'); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2})); }); }); @@ -400,8 +400,8 @@ describe('Key Event Handling', function() { it('should generate fake undo/redo events on press when AltGraph is down', function() { var times_called = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch(times_called++) { case 0: expect(keysym).to.be.equal(0xFFE3); @@ -439,7 +439,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(true); break; } - }}); + }; // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); @@ -449,8 +449,8 @@ describe('Key Event Handling', function() { }); it('should no do anything on key release', function() { var times_called = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch(times_called++) { case 7: expect(keysym).to.be.equal(0x61); @@ -458,7 +458,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(false); break; } - }}); + }; // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); @@ -469,8 +469,8 @@ describe('Key Event Handling', function() { }); it('should not consider a char modifier to be down on the modifier key itself', function() { var times_called = 0; - var kbd = new Keyboard(document, { - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch(times_called++) { case 0: expect(keysym).to.be.equal(0xFFE3); @@ -488,7 +488,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(true); break; } - }}); + }; // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); diff --git a/tests/test.mouse.js b/tests/test.mouse.js index 905b524c..248a9334 100644 --- a/tests/test.mouse.js +++ b/tests/test.mouse.js @@ -33,55 +33,51 @@ describe('Mouse Event Handling', function() { describe('Decode Mouse Events', function() { it('should decode mousedown events', function(done) { - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - expect(bmask).to.be.equal(0x01); - expect(down).to.be.equal(1); - done(); - } - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + expect(bmask).to.be.equal(0x01); + expect(down).to.be.equal(1); + done(); + }; mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); }); it('should decode mouseup events', function(done) { var calls = 0; - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - expect(bmask).to.be.equal(0x01); - if (calls++ === 1) { - expect(down).to.not.be.equal(1); - done(); - } + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + expect(bmask).to.be.equal(0x01); + if (calls++ === 1) { + expect(down).to.not.be.equal(1); + done(); } - }); + }; mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' })); }); it('should decode mousemove events', function(done) { - var mouse = new Mouse(target, { - onMouseMove: function(x, y) { - // Note that target relative coordinates are sent - expect(x).to.be.equal(40); - expect(y).to.be.equal(10); - done(); - } - }); + var mouse = new Mouse(target); + mouse.onmousemove = function(x, y) { + // Note that target relative coordinates are sent + expect(x).to.be.equal(40); + expect(y).to.be.equal(10); + done(); + }; mouse._handleMouseMove(mouseevent('mousemove', { clientX: 50, clientY: 20 })); }); it('should decode mousewheel events', function(done) { var calls = 0; - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - calls++; - expect(bmask).to.be.equal(1<<6); - if (calls === 1) { - expect(down).to.be.equal(1); - } else if (calls === 2) { - expect(down).to.not.be.equal(1); - done(); - } + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + expect(bmask).to.be.equal(1<<6); + if (calls === 1) { + expect(down).to.be.equal(1); + } else if (calls === 2) { + expect(down).to.not.be.equal(1); + done(); } - }); + }; mouse._handleMouseWheel(mouseevent('mousewheel', { deltaX: 50, deltaY: 0, deltaMode: 0})); @@ -95,21 +91,20 @@ describe('Mouse Event Handling', function() { it('should use same pos for 2nd tap if close enough', function(done) { var calls = 0; - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - done(); - } + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + done(); } - }); + }; // touch events are sent in an array of events // with one item for each touch point mouse._handleMouseDown(touchevent( @@ -127,21 +122,20 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if far apart', function(done) { var calls = 0; - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.not.be.equal(68); + expect(y).to.not.be.equal(36); + done(); } - }); + }; mouse._handleMouseDown(touchevent( 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); this.clock.tick(10); @@ -157,21 +151,20 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if not soon enough', function(done) { var calls = 0; - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.not.be.equal(68); + expect(y).to.not.be.equal(36); + done(); } - }); + }; mouse._handleMouseDown(touchevent( 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); this.clock.tick(10); @@ -187,21 +180,20 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if not touch', function(done) { var calls = 0; - var mouse = new Mouse(target, { - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.not.be.equal(68); + expect(y).to.not.be.equal(36); + done(); } - }); + }; mouse._handleMouseDown(mouseevent( 'mousedown', { button: '0x01', clientX: 78, clientY: 46 })); this.clock.tick(10); @@ -223,8 +215,8 @@ describe('Mouse Event Handling', function() { afterEach(function () { this.clock.restore(); }); it('should accumulate wheel events if small enough', function () { - var callback = sinon.spy(); - var mouse = new Mouse(target, { onMouseButton: callback }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -242,7 +234,7 @@ describe('Mouse Event Handling', function() { 'mousewheel', { clientX: 18, clientY: 40, deltaX: 4, deltaY: 0, deltaMode: 0 })); - expect(callback).to.have.callCount(2); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up this.clock.tick(10); mouse._handleMouseWheel(mouseevent( @@ -252,12 +244,12 @@ describe('Mouse Event Handling', function() { expect(mouse._accumulatedWheelDeltaX).to.be.equal(4); expect(mouse._accumulatedWheelDeltaY).to.be.equal(9); - expect(callback).to.have.callCount(2); // still + expect(mouse.onmousebutton).to.have.callCount(2); // still }); it('should not accumulate large wheel events', function () { - var callback = sinon.spy(); - var mouse = new Mouse(target, { onMouseButton: callback }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -271,24 +263,24 @@ describe('Mouse Event Handling', function() { 'mousewheel', { clientX: 18, clientY: 40, deltaX: 400, deltaY: 400, deltaMode: 0 })); - expect(callback).to.have.callCount(8); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up }); it('should send even small wheel events after a timeout', function () { - var callback = sinon.spy(); - var mouse = new Mouse(target, { onMouseButton: callback }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, deltaX: 1, deltaY: 0, deltaMode: 0 })); this.clock.tick(51); // timeout on 50 ms - expect(callback).to.have.callCount(2); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up }); it('should account for non-zero deltaMode', function () { - var callback = sinon.spy(); - var mouse = new Mouse(target, { onMouseButton: callback }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -300,7 +292,7 @@ describe('Mouse Event Handling', function() { 'mousewheel', { clientX: 18, clientY: 40, deltaX: 1, deltaY: 0, deltaMode: 2 })); - expect(callback).to.have.callCount(4); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up }); }); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index a1d2f9e9..918d5dc9 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -9,9 +9,8 @@ import { encodings } from '../core/encodings.js'; import FakeWebSocket from './fake.websocket.js'; import sinon from '../vendor/sinon.js'; -function make_rfb (extra_opts) { - extra_opts = extra_opts || {}; - return new RFB(document.createElement('canvas'), extra_opts); +function make_rfb () { + return new RFB(document.createElement('canvas')); } var push8 = function (arr, num) { @@ -136,7 +135,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); client._rfb_connection_state = 'connected'; - client._view_only = false; + client._viewOnly = false; }); it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { @@ -159,7 +158,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the keys if we are set as view_only', function () { - client._view_only = true; + client._viewOnly = true; client.sendCtrlAltDel(); expect(client._sock.flush).to.not.have.been.called; }); @@ -172,7 +171,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); client._rfb_connection_state = 'connected'; - client._view_only = false; + client._viewOnly = false; }); it('should send a single key with the given code and state (down = true)', function () { @@ -197,7 +196,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the key if we are set as view_only', function () { - client._view_only = true; + client._viewOnly = true; client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; }); @@ -226,7 +225,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); client._rfb_connection_state = 'connected'; - client._view_only = false; + client._viewOnly = false; }); it('should send the given text in a paste event', function () { @@ -250,7 +249,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); client._rfb_connection_state = 'connected'; - client._view_only = false; + client._viewOnly = false; client._supportsSetDesktopSize = true; }); @@ -292,7 +291,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); client._rfb_connection_state = 'connected'; - client._view_only = false; + client._viewOnly = false; client._rfb_xvp_ver = 1; }); @@ -340,9 +339,9 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the updateState callback', function () { - client.set_onUpdateState(sinon.spy()); + client.onupdatestate = sinon.spy(); client._updateConnectionState('connecting'); - var spy = client.get_onUpdateState(); + var spy = client.onupdatestate; expect(spy.args[0][1]).to.equal('connecting'); }); @@ -359,19 +358,19 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should ignore state changes to the same state', function () { - client.set_onUpdateState(sinon.spy()); + client.onupdatestate = sinon.spy(); client._rfb_connection_state = 'connecting'; client._updateConnectionState('connecting'); - var spy = client.get_onUpdateState(); + var spy = client.onupdatestate; expect(spy).to.not.have.been.called; }); it('should ignore illegal state changes', function () { - client.set_onUpdateState(sinon.spy()); + client.onupdatestate = sinon.spy(); client._rfb_connection_state = 'connected'; client._updateConnectionState('disconnected'); expect(client._rfb_connection_state).to.not.equal('disconnected'); - var spy = client.get_onUpdateState(); + var spy = client.onupdatestate; expect(spy).to.not.have.been.called; }); }); @@ -416,9 +415,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should result in disconnect callback with message when reason given', function () { client._rfb_connection_state = 'connected'; - client.set_onDisconnected(sinon.spy()); + client.ondisconnected = sinon.spy(); client._fail('a reason'); - var spy = client.get_onDisconnected(); + var spy = client.ondisconnected; this.clock.tick(2000); expect(spy).to.have.been.calledOnce; expect(spy.args[0].length).to.equal(2); @@ -432,18 +431,18 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); }); it('should call the notification callback', function () { - client.set_onNotification(sinon.spy()); + client.onnotification = sinon.spy(); client._notification('notify!', 'warn'); - var spy = client.get_onNotification(); + var spy = client.onnotification; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.equal('notify!'); expect(spy.args[0][2]).to.equal('warn'); }); it('should not call the notification callback when level is invalid', function () { - client.set_onNotification(sinon.spy()); + client.onnotification = sinon.spy(); client._notification('notify!', 'invalid'); - var spy = client.get_onNotification(); + var spy = client.onnotification; expect(spy).to.not.have.been.called; }); }); @@ -484,7 +483,7 @@ describe('Remote Frame Buffer Protocol Client', function() { sinon.spy(client, '_updateConnectionState'); client._sock._websocket.close = function () {}; // explicitly don't call onclose client._updateConnectionState('disconnecting'); - this.clock.tick(client.get_disconnectTimeout() * 1000); + this.clock.tick(client.disconnectTimeout * 1000); expect(client._updateConnectionState).to.have.been.calledTwice; expect(client._rfb_disconnect_reason).to.not.equal(""); expect(client._rfb_connection_state).to.equal("disconnected"); @@ -492,9 +491,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not fail if Websock.onclose gets called within the disconnection timeout', function () { client._updateConnectionState('disconnecting'); - this.clock.tick(client.get_disconnectTimeout() * 500); + this.clock.tick(client.disconnectTimeout * 500); client._sock._websocket.close(); - this.clock.tick(client.get_disconnectTimeout() * 500 + 1); + this.clock.tick(client.disconnectTimeout * 500 + 1); expect(client._rfb_connection_state).to.equal('disconnected'); }); @@ -510,39 +509,39 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); }); it('should call the disconnect callback if the state is "disconnected"', function () { - client.set_onDisconnected(sinon.spy()); + client.ondisconnected = sinon.spy(); client._rfb_connection_state = 'disconnecting'; client._rfb_disconnect_reason = "error"; client._updateConnectionState('disconnected'); - var spy = client.get_onDisconnected(); + var spy = client.ondisconnected; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.equal("error"); }); it('should not call the disconnect callback if the state is not "disconnected"', function () { - client.set_onDisconnected(sinon.spy()); + client.ondisconnected = sinon.spy(); client._updateConnectionState('disconnecting'); - var spy = client.get_onDisconnected(); + var spy = client.ondisconnected; expect(spy).to.not.have.been.called; }); it('should call the disconnect callback without msg when no reason given', function () { - client.set_onDisconnected(sinon.spy()); + client.ondisconnected = sinon.spy(); client._rfb_connection_state = 'disconnecting'; client._rfb_disconnect_reason = ""; client._updateConnectionState('disconnected'); - var spy = client.get_onDisconnected(); + var spy = client.ondisconnected; expect(spy).to.have.been.calledOnce; expect(spy.args[0].length).to.equal(1); }); it('should call the updateState callback before the disconnect callback', function () { - client.set_onDisconnected(sinon.spy()); - client.set_onUpdateState(sinon.spy()); + client.ondisconnected = sinon.spy(); + client.onupdatestate = sinon.spy(); client._rfb_connection_state = 'disconnecting'; client._updateConnectionState('disconnected'); - var updateStateSpy = client.get_onUpdateState(); - var disconnectSpy = client.get_onDisconnected(); + var updateStateSpy = client.onupdatestate; + var disconnectSpy = client.ondisconnected; expect(updateStateSpy.calledBefore(disconnectSpy)).to.be.true; }); }); @@ -790,14 +789,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the onCredentialsRequired callback if missing a password', function () { - client.set_onCredentialsRequired(sinon.spy()); + client.oncredentialsrequired = sinon.spy(); send_security(2, client); var challenge = []; for (var i = 0; i < 16; i++) { challenge[i] = i; } client._sock._websocket._receive_data(new Uint8Array(challenge)); - var spy = client.get_onCredentialsRequired(); + var spy = client.oncredentialsrequired; expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.have.members(["password"]); @@ -849,23 +848,23 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the onCredentialsRequired callback if all credentials are missing', function() { - client.set_onCredentialsRequired(sinon.spy()); + client.oncredentialsrequired = sinon.spy(); client._rfb_credentials = {}; send_security(22, client); - var spy = client.get_onCredentialsRequired(); + var spy = client.oncredentialsrequired; expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.have.members(["username", "password", "target"]); }); it('should call the onCredentialsRequired callback if some credentials are missing', function() { - client.set_onCredentialsRequired(sinon.spy()); + client.oncredentialsrequired = sinon.spy(); client._rfb_credentials = { username: 'user', target: 'target' }; send_security(22, client); - var spy = client.get_onCredentialsRequired(); + var spy = client.oncredentialsrequired; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.have.members(["username", "password", "target"]); }); @@ -1098,10 +1097,10 @@ describe('Remote Frame Buffer Protocol Client', function() { // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them it('should set the framebuffer name and call the callback', function () { - client.set_onDesktopName(sinon.spy()); + client.ondesktopname = sinon.spy(); send_server_init({ name: 'some name' }, client); - var spy = client.get_onDesktopName(); + var spy = client.ondesktopname; expect(client._fb_name).to.equal('some name'); expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.equal('some name'); @@ -1127,11 +1126,11 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the resize callback and resize the display', function () { - client.set_onFBResize(sinon.spy()); + client.onfbresize = sinon.spy(); sinon.spy(client._display, 'resize'); send_server_init({ width: 27, height: 32 }, client); - var spy = client.get_onFBResize(); + var spy = client.onfbresize; expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(27, 32); expect(spy).to.have.been.calledOnce; @@ -1559,11 +1558,11 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle the DesktopSize pseduo-encoding', function () { - client.set_onFBResize(sinon.spy()); + client.onfbresize = sinon.spy(); sinon.spy(client._display, 'resize'); send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client); - var spy = client.get_onFBResize(); + var spy = client.onfbresize; expect(spy).to.have.been.calledOnce; expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); @@ -1582,7 +1581,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._fb_height = 4; client._display.resize(4, 4); sinon.spy(client._display, 'resize'); - client.set_onFBResize(sinon.spy()); + client.onfbresize = sinon.spy(); }); function make_screen_data (nr_of_screens) { @@ -1602,10 +1601,10 @@ describe('Remote Frame Buffer Protocol Client', function() { } it('should call callback when resize is supported', function () { - client.set_onCapabilities(sinon.spy()); + client.oncapabilities = sinon.spy(); expect(client._supportsSetDesktopSize).to.be.false; - expect(client.get_capabilities().resize).to.be.false; + expect(client.capabilities.resize).to.be.false; var reason_for_change = 0; // server initiated var status_code = 0; // No error @@ -1615,9 +1614,9 @@ describe('Remote Frame Buffer Protocol Client', function() { make_screen_data(1), client); expect(client._supportsSetDesktopSize).to.be.true; - expect(client.get_onCapabilities()).to.have.been.calledOnce; - expect(client.get_onCapabilities().args[0][1].resize).to.be.true; - expect(client.get_capabilities().resize).to.be.true; + expect(client.oncapabilities).to.have.been.calledOnce; + expect(client.oncapabilities.args[0][1].resize).to.be.true; + expect(client.capabilities.resize).to.be.true; }), it('should handle a resize requested by this client', function () { @@ -1634,7 +1633,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(20, 50); - var spy = client.get_onFBResize(); + var spy = client.onfbresize; expect(spy).to.have.been.calledOnce; expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); }); @@ -1653,7 +1652,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(20, 50); - var spy = client.get_onFBResize(); + var spy = client.onfbresize; expect(spy).to.have.been.calledOnce; expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); }); @@ -1672,7 +1671,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(60, 50); - var spy = client.get_onFBResize(); + var spy = client.onfbresize; expect(spy).to.have.been.calledOnce; expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50); }); @@ -1690,7 +1689,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.not.have.been.called; - var spy = client.get_onFBResize(); + var spy = client.onfbresize; expect(spy).to.not.have.been.called; }); }); @@ -1708,20 +1707,20 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('XVP Message Handling', function () { it('should send a notification on XVP_FAIL', function () { - client.set_onNotification(sinon.spy()); + client.onnotification = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0])); - var spy = client.get_onNotification(); + var spy = client.onnotification; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.equal('XVP Operation Failed'); }); it('should set the XVP version and fire the callback with the version on XVP_INIT', function () { - client.set_onCapabilities(sinon.spy()); + client.oncapabilities = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1])); expect(client._rfb_xvp_ver).to.equal(10); - expect(client.get_onCapabilities()).to.have.been.calledOnce; - expect(client.get_onCapabilities().args[0][1].power).to.be.true; - expect(client.get_capabilities().power).to.be.true; + expect(client.oncapabilities).to.have.been.calledOnce; + expect(client.oncapabilities.args[0][1].power).to.be.true; + expect(client.capabilities.power).to.be.true; }); it('should fail on unknown XVP message types', function () { @@ -1736,18 +1735,18 @@ describe('Remote Frame Buffer Protocol Client', function() { var data = [3, 0, 0, 0]; push32(data, expected_str.length); for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); } - client.set_onClipboard(sinon.spy()); + client.onclipboard = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array(data)); - var spy = client.get_onClipboard(); + var spy = client.onclipboard; expect(spy).to.have.been.calledOnce; expect(spy.args[0][1]).to.equal(expected_str); }); it('should fire the bell callback on Bell', function () { - client.set_onBell(sinon.spy()); + client.onbell = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([2])); - expect(client.get_onBell()).to.have.been.calledOnce; + expect(client.onbell).to.have.been.calledOnce; }); it('should respond correctly to ServerFence', function () { @@ -1832,26 +1831,26 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send button messages in view-only mode', function () { - client._view_only = true; - client._mouse._onMouseButton(0, 0, 1, 0x001); + client._viewOnly = true; + client._handleMouseButton(0, 0, 1, 0x001); expect(client._sock.flush).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { - client._view_only = true; - client._mouse._onMouseMove(0, 0); + client._viewOnly = true; + client._handleMouseMove(0, 0); expect(client._sock.flush).to.not.have.been.called; }); it('should send a pointer event on mouse button presses', function () { - client._mouse._onMouseButton(10, 12, 1, 0x001); + client._handleMouseButton(10, 12, 1, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a mask of 1 on mousedown', function () { - client._mouse._onMouseButton(10, 12, 1, 0x001); + client._handleMouseButton(10, 12, 1, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); expect(client._sock).to.have.sent(pointer_msg._sQ); @@ -1859,22 +1858,22 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a mask of 0 on mouseup', function () { client._mouse_buttonMask = 0x001; - client._mouse._onMouseButton(10, 12, 0, 0x001); + client._handleMouseButton(10, 12, 0, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should send a pointer event on mouse movement', function () { - client._mouse._onMouseMove(10, 12); + client._handleMouseMove(10, 12); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000); expect(client._sock).to.have.sent(pointer_msg._sQ); }); it('should set the button mask so that future mouse movements use it', function () { - client._mouse._onMouseButton(10, 12, 1, 0x010); - client._mouse._onMouseMove(13, 9); + client._handleMouseButton(10, 12, 1, 0x010); + client._handleMouseMove(13, 9); var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010); RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); @@ -1888,19 +1887,19 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not send movement messages when viewport dragging', function () { client._viewportDragging = true; client._display.viewportChangePos = sinon.spy(); - client._mouse._onMouseMove(13, 9); + client._handleMouseMove(13, 9); expect(client._sock.flush).to.not.have.been.called; }); it('should not send button messages when initiating viewport dragging', function () { client._viewportDrag = true; - client._mouse._onMouseButton(13, 9, 0x001); + client._handleMouseButton(13, 9, 0x001); expect(client._sock.flush).to.not.have.been.called; }); it('should be initiate viewport dragging on a button down event, if enabled', function () { client._viewportDrag = true; - client._mouse._onMouseButton(13, 9, 0x001); + client._handleMouseButton(13, 9, 0x001); expect(client._viewportDragging).to.be.true; expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 }); }); @@ -1908,7 +1907,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should terminate viewport dragging on a button up event, if enabled', function () { client._viewportDrag = true; client._viewportDragging = true; - client._mouse._onMouseButton(13, 9, 0x000); + client._handleMouseButton(13, 9, 0x000); expect(client._viewportDragging).to.be.false; }); @@ -1924,7 +1923,7 @@ describe('Remote Frame Buffer Protocol Client', function() { client._viewportDragPos = { x: oldX, y: oldY }; client._display.viewportChangePos = sinon.spy(); - client._mouse._onMouseMove(newX, newY); + client._handleMouseMove(newX, newY); expect(client._viewportDragging).to.be.true; expect(client._viewportHasMoved).to.be.true; @@ -1943,20 +1942,20 @@ describe('Remote Frame Buffer Protocol Client', function() { client._sock._websocket._open(); sinon.spy(client._sock, 'flush'); client._rfb_connection_state = 'connected'; - client._view_only = false; + client._viewOnly = false; }); it('should send a key message on a key press', function () { var keyevent = {}; - client._keyboard._onKeyEvent(0x41, 'KeyA', true); + client._handleKeyEvent(0x41, 'KeyA', true); var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(key_msg, 0x41, 1); expect(client._sock).to.have.sent(key_msg._sQ); }); it('should not send messages in view-only mode', function () { - client._view_only = true; - client._keyboard._onKeyEvent('a', 'KeyA', true); + client._viewOnly = true; + client._handleKeyEvent('a', 'KeyA', true); expect(client._sock.flush).to.not.have.been.called; }); }); @@ -1996,9 +1995,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should process all normal messages directly', function () { client._sock._websocket._open(); client._rfb_connection_state = 'connected'; - client.set_onBell(sinon.spy()); + client.onbell = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02])); - expect(client.get_onBell()).to.have.been.calledTwice; + expect(client.onbell).to.have.been.calledTwice; }); // open events diff --git a/vnc_lite.html b/vnc_lite.html index 95cf2865..f0c8ace5 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -206,7 +206,7 @@ function updatePowerButtons() { var powerbuttons; powerbuttons = document.getElementById('noVNC_power_buttons'); - if (rfb.get_capabilities().power) { + if (rfb.capabilities.power) { powerbuttons.style.display = 'inline'; } else { powerbuttons.style.display = 'none'; @@ -256,15 +256,15 @@ } try { - rfb = new RFB(document.getElementById('noVNC_canvas'), - {'local_cursor': WebUtil.getConfigVar('cursor', true), - 'view_only': WebUtil.getConfigVar('view_only', false), - 'onNotification': notification, - 'onUpdateState': updateState, - 'onDisconnected': disconnected, - 'onCapabilities': function () { updatePowerButtons(); initialResize(); }, - 'onCredentialsRequired': credentials, - 'onDesktopName': updateDesktopName}); + rfb = new RFB(document.getElementById('noVNC_canvas')); + rfb.localCursor = WebUtil.getConfigVar('cursor', true); + rfb.viewOnly = WebUtil.getConfigVar('view_only', false); + rfb.onnotification = notification; + rfb.onupdatestate = updateState; + rfb.ondisconnected = disconnected; + rfb.oncapabilities = function () { updatePowerButtons(); initialResize(); }; + rfb.oncredentialsrequired = credentials; + rfb.ondesktopname = updateDesktopName; } catch (exc) { status('Unable to create RFB client -- ' + exc, 'error'); return; // don't continue trying to connect From 4f90c1d387dbd0de156a5f29f2868c05c7aa7854 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 16 Oct 2017 14:47:03 +0200 Subject: [PATCH 22/34] Follow MDN style in API documentation --- docs/API.md | 531 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 460 insertions(+), 71 deletions(-) diff --git a/docs/API.md b/docs/API.md index 64627698..ad16990a 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,94 +1,483 @@ -# API +# noVNC API The interface of the noVNC client consists of a single RFB object that is instantiated once per connection. +## RFB -## 1 Configuration Attributes +The `RFB` object represents a single connection to a VNC server. It +communicates using a WebSocket that must provide a standard RFB +protocol stream. -| name | type | mode | default | description -| ----------------- | ----- | ---- | ---------- | ------------ -| localCursor | bool | RW | false | Request locally rendered cursor -| viewOnly | bool | RW | false | Disable client mouse/keyboard -| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. -| scale | float | RW | 1.0 | Display area scale factor -| viewport | bool | RW | false | Use viewport clipping -| disconnectTimeout | int | RW | 3 | Time (in seconds) to wait for disconnection -| viewportDrag | bool | RW | false | Move the viewport on mouse drags -| capabilities | arr | RO | [] | Supported capabilities (can include: 'power', 'resize') +### Constructor +[`RFB()`](#rfb-1) + - Creates and returns a new `RFB` object. -## 2 Methods +### Properties -| name | parameters | description -| ------------------ | ------------------------------- | ------------ -| connect | (url, options) | Connect to the given URL -| disconnect | () | Disconnect -| sendCredentials | (credentials) | Send credentials after onCredentialsRequired callback -| sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence -| machineShutdown | () | Request a shutdown of the remote machine. -| machineReboot | () | Request a reboot of the remote machine. -| machineReset | () | Request a reset of the remote machine. -| 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 -| autoscale | (width, height, downscaleOnly) | Scale the display -| clippingDisplay | () | Check if the remote display is larger than the client display -| requestDesktopSize | (width, height) | Send a request to change the remote desktop size. -| viewportChangeSize | (width, height) | Change size of the viewport +`localCursor` + - Is a `boolean` indicating if a client side cursor should be + requested. Disabled by default. -__connect() details__ +`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. -The connect() call supports the following options: +`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. -| name | type | default | description -| ----------------- | ----- | ---------- | ------------ -| shared | bool | true | Request shared VNC mode -| credentials | obj | {} | Credentials to use when authenticating -| repeaterID | str | '' | UltraVNC RepeaterID to connect to +`scale` + - 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. +`viewport` + - 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. -## 3 Callbacks +`viewportDrag` + - Is a `boolean` indicating if mouse events should control the + relative position of a clipped canvas. Only relevant if `viewport` + is enabled. Disabled by default. -The RFB object has certain events that can be hooked with callback -functions. +`disconnectTimeout` + - Is a `long` indicating how many seconds to wait for a disconnect + request to complete. Is set to `3` by default. -| 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. -| oncredentialsrequired | (rfb, types) | VNC credentials are required (use sendCredentials) -| onclipboard | (rfb, text) | RFB clipboard contents received -| onbell | (rfb) | RFB Bell message received -| onfbresize | (rfb, width, height) | Frame buffer (remote desktop) size changed -| ondesktopname | (rfb, name) | VNC desktop name recieved -| oncapabilities | (rfb, capabilities) | The supported capabilities has changed +`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 -__RFB onUpdateState callback details__ +### Event handlers -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. +[`RFB.onupdatestate()`](#rfbonupdatestate) + - An event handler called when the connection state of the `RFB` + object changes. -| connection state | description -| ---------------- | ------------ -| connecting | starting to connect -| connected | connected normally -| disconnecting | starting to disconnect -| disconnected | disconnected - permanent end-state for this RFB object +[`RFB.onnotification()`](#rfbonnotification) + - An event handler called when the `RFB` usage has a message to + display to the user. -__RFB onCredentialsRequired callback details__ +[`RFB.ondisconnected()`](#rfbondisconnected) + - An event handler called when the `RFB` object disconnects. -The onCredentialsRequired callback is called when the server requests more -credentials than was specified to connect(). The types argument is a list -of all the credentials that are required. Currently the following are -defined: +[`RFB.oncredentialsrequired()`](#rfboncredentialsrequired) + - An event hander called when more credentials must be given to + continue. -| name | description -| -------- | ------------ -| username | User that authenticates -| password | Password for user -| target | String specifying target machine or session +[`RFB.onclipboard()`](#rfbonclipboard) + - An event handler called when clipboard data is received from the + server. + +[`RFB.onbell()`](#rfbonbell) + - An event handler called when a audible bell request is received + from the server. + +[`RFB.onfbresize()`](#rfbonfbresize) + - An event handler called when the framebuffer size is changed. + +[`RFB.ondesktopname()`](#rfbondesktopname) + - An event handler called when the remote desktop name changes. + +[`RFB.oncapabilities()`](#rfboncapabilities) + - An event handler called when `RFB.capabilities` is updated. + +### Methods + +[`RFB.connect()`](#rfbconnect) + - Connect to a server. + +[`RFB.disconnect()`](#rfbdisconnect) + - Disconnect from the server. + +[`RFB.sendCredentials()`](#rfbsendcredentials) + - Send credentials to server. Should be called after + [`oncredentialsrequired`](#rfboncredentialsrequired) has been + called. + +[`RFB.sendKey()`](#rfbsendKey) + - Send a key event. + +[`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.scale` so that the framebuffer fits a specified container. + +[`RFB.clippingDisplay()`](#rfbclippingDisplay) + - Returns `true` if the framebuffer is larger than what is currently + displayed on the canvas. + +[`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. The object will +initially be disconnected and [`RFB.connect()`](#rfbconnect) must be +called before the object will be useful. + +##### Syntax + + var rfb = new RFB( target ); + +###### 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. + +#### RFB.onupdatestate + +The `onupdatestate` event handler is fired after the noVNC connection +state changes. 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. + +##### Syntax + + RFB.onupdatestate = function(rfb, state) { ... } + +#### RFB.onnotification + +The `onnotification` event handler is fired when the `RFB` object wants +a message displayed to the user. **`msg`** is a `DOMString` specifying +the actual message, and **`level`** is a `DOMString` indicating the +severity of the message. The following levels are currently defined: + + - `"normal"` + - `"warn"` + - `"error"` + +**`options`** is currently unused. + +##### Syntax + + RFB.onnotification = function(rfb, msg, level, options) { ... } + +#### RFB.ondisconnected + +The `ondisconnected` event handler is fired when the connection has +been terminated. **`reason`** is `undefined` for a clean termination +and a `DOMString` specifying the reason in the event of an unexpected +termination. + +##### Syntax + + RFB.ondisconnected = function(rfb, reason) { ... } + +#### RFB.oncredentialsrequired + +The `oncredentialsrequired` event handler is fired when the server +requests more credentials than were specified to +[`RFB.connect()`](#rfbconnect). The **`types`** argument is a list of +all the credentials that are required. + +##### Syntax + + RFB.oncredentialsrequired = function(rfb, types) { ... } + +#### RFB.onclipboard + +The `onclipboard` event handler is fired when the server has sent +clipboard data. + +##### Syntax + + RFB.onclipboard = function(rfb, text) { ... } + +#### RFB.onbell + +The `onbell` event handler is fired when the server has requested an +audible bell. + +##### Syntax + + RFB.onbell = function(rfb) { ... } + +#### RFB.onfbresize + +The `onfbresize` event handler is fired when the framebuffer has +changed dimensions. + +##### Syntax + + RFB.onfbresize = function(rfb, width, height) { ... } + +#### RFB.ondesktopname + +The `ondesktopname` event handler is fired when the name of the remote +desktop changes. + +##### Syntax + + RFB.ondesktopname = function(rfb, name) { ... } + +#### RFB.oncapabilities + +The `oncapabilities` event handler is fired whenever an entry is added +or removed from `RFB.capabilities`. + +##### Syntax + + RFB.oncapabilities = function(rfb, capabilites) { ... } + +#### RFB.connect() + +The `RFB.connect()` method is used to initiate a new connection to a +specified VNC server. + +##### Syntax + + RFB.connect( url [, options] ); + +###### Parameters + +**`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. + +#### 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 `RFB.oncredentialsrequired` has been fired. + +##### Syntax + + RFB.sendCredentials( credentials ); + +###### Parameters + +**`credentials`** + - An `Object` specifying the credentials to provide to the server + when authenticating. See [`RFB.connect()`](#rfbconnect) 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.scale` to fit given dimensions. + +##### Syntax + + RFB.autoscale( width, height, downscaleOnly ); + +###### 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. + +**`downscaleOnly`** + - A `boolean` specifying if the scale must be kept below `1.0`. + +#### RFB.clippingDisplay() + +The `RFB.clippingDisplay()` method is used to determine if the +framebuffer is larger than the current canvas, i.e. it is being +clipped. + +##### Syntax + + RFB.clippingDisplay( ); + +###### Return value + +Returns a `boolean` indicating if the framebuffer is currently being +clipped. + +#### 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. +[`RFB.onfbresize`](#rfbonfbresize) will be called 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.viewport` 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. From 15a62695102df4f412e44243c5371d56d4eba964 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 12:57:34 +0200 Subject: [PATCH 23/34] Remove some unused return values --- core/rfb.js | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 2b2dc849..2767e53c 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -298,7 +298,6 @@ RFB.prototype = { this._rfb_init_state = ''; this._updateConnectionState('connecting'); - return true; }, disconnect: function () { @@ -314,7 +313,7 @@ RFB.prototype = { }, sendCtrlAltDel: function () { - if (this._rfb_connection_state !== 'connected' || this._viewOnly) { 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); @@ -323,8 +322,6 @@ RFB.prototype = { 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; }, machineShutdown: function () { @@ -342,12 +339,12 @@ RFB.prototype = { // 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._viewOnly) { 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]; @@ -361,13 +358,11 @@ RFB.prototype = { 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) { @@ -395,17 +390,15 @@ RFB.prototype = { requestDesktopSize: function (width, height) { if (this._rfb_connection_state !== 'connected' || this._viewOnly) { - return false; + 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); }, From a80aa41628cf84dc7f290e6d40d93e4d690df5c4 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 13:03:50 +0200 Subject: [PATCH 24/34] Change clippingDisplay() to a property It fits much better as a property given that it only tells what the current state of things are. --- app/ui.js | 3 +-- core/display.js | 10 +++++----- core/rfb.js | 7 ++----- docs/API-internal.md | 2 +- docs/API.md | 23 ++++------------------- tests/test.display.js | 6 ++---- 6 files changed, 15 insertions(+), 36 deletions(-) diff --git a/app/ui.js b/app/ui.js index 626b5f46..20a8912d 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1377,8 +1377,7 @@ var UI = { // Check if viewport drag is possible. It is only possible // if the remote display is clipping the client display. - if (UI.rfb.viewport && - UI.rfb.clippingDisplay()) { + if (UI.rfb.viewport && UI.rfb.isClipped) { clipping = true; } diff --git a/core/display.js b/core/display.js index 07d4ef5b..13d47773 100644 --- a/core/display.js +++ b/core/display.js @@ -106,6 +106,11 @@ Display.prototype = { return this._fb_height; }, + get isClipped() { + var vp = this._viewportLoc; + return this._fb_width > vp.w || this._fb_height > vp.h; + }, + logo: null, // ===== EVENT HANDLERS ===== @@ -512,11 +517,6 @@ Display.prototype = { this._target.style.cursor = "none"; }, - clippingDisplay: function () { - var vp = this._viewportLoc; - return this._fb_width > vp.w || this._fb_height > vp.h; - }, - autoscale: function (containerWidth, containerHeight, downscaleOnly) { var vp = this._viewportLoc; var targetAspectRatio = containerWidth / containerHeight; diff --git a/core/rfb.js b/core/rfb.js index 2767e53c..3ab3bcad 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -268,6 +268,8 @@ RFB.prototype = { get viewport() { return this._display.viewport; }, set viewport(viewport) { this._display.viewport = viewport; }, + get isClipped() { return this._display.isClipped; }, + // ===== EVENT HANDLERS ===== onupdatestate: function () {}, // onupdatestate(rfb, state, oldstate): connection state change @@ -380,11 +382,6 @@ RFB.prototype = { this._display.viewportChangeSize(width, height); }, - clippingDisplay: function () { - if (this._rfb_connection_state !== 'connected') { return false; } - return this._display.clippingDisplay(); - }, - // 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) { diff --git a/docs/API-internal.md b/docs/API-internal.md index 6a45d38f..21741dc7 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -89,6 +89,7 @@ None | viewport | bool | RW | false | Use viewport clipping | width | int | RO | | Display area width | height | int | RO | | Display area height +| isClipped | bool | RO | | Is the remote display is larger than the client display ### 2.3.2 Methods @@ -116,7 +117,6 @@ None | 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 diff --git a/docs/API.md b/docs/API.md index ad16990a..6c948a53 100644 --- a/docs/API.md +++ b/docs/API.md @@ -46,6 +46,10 @@ protocol stream. relative position of a clipped canvas. Only relevant if `viewport` is enabled. Disabled by default. +`isClipped` *Read only* + - Is a `boolean` indicating if the framebuffer is larger than the + current canvas, i.e. it is being clipped. + `disconnectTimeout` - Is a `long` indicating how many seconds to wait for a disconnect request to complete. Is set to `3` by default. @@ -128,10 +132,6 @@ protocol stream. [`RFB.autoscale()`](#rfbautoscale) - Set `RFB.scale` so that the framebuffer fits a specified container. -[`RFB.clippingDisplay()`](#rfbclippingDisplay) - - Returns `true` if the framebuffer is larger than what is currently - displayed on the canvas. - [`RFB.requestDesktopSize()`](#rfbrequestDesktopSize) - Send a request to change the remote desktop size. @@ -426,21 +426,6 @@ The `RFB.autoscale()` method is used to automatically adjust **`downscaleOnly`** - A `boolean` specifying if the scale must be kept below `1.0`. -#### RFB.clippingDisplay() - -The `RFB.clippingDisplay()` method is used to determine if the -framebuffer is larger than the current canvas, i.e. it is being -clipped. - -##### Syntax - - RFB.clippingDisplay( ); - -###### Return value - -Returns a `boolean` indicating if the framebuffer is currently being -clipped. - #### RFB.requestDesktopSize() The `RFB.requestDesktopSize()` method is used to request a change of diff --git a/tests/test.display.js b/tests/test.display.js index 675b43d7..33ff8f12 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -92,14 +92,12 @@ describe('Display/Canvas Helper', function () { }); it('should report clipping when framebuffer > viewport', function () { - var clipping = display.clippingDisplay(); - expect(clipping).to.be.true; + expect(display.isClipped).to.be.true; }); it('should report not clipping when framebuffer = viewport', function () { display.viewportChangeSize(5, 5); - var clipping = display.clippingDisplay(); - expect(clipping).to.be.false; + expect(display.isClipped).to.be.false; }); it('should show the entire framebuffer when disabling the viewport', function() { From 0460e5fdbeba2683ba3142ca6f403324bba606e8 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 13:12:30 +0200 Subject: [PATCH 25/34] Improve naming for viewport properties --- app/ui.js | 18 +++++++++--------- core/display.js | 12 ++++++------ core/rfb.js | 12 ++++++------ docs/API-internal.md | 16 ++++++++-------- docs/API.md | 17 +++++++++-------- tests/test.display.js | 18 +++++++++--------- tests/test.rfb.js | 8 ++++---- 7 files changed, 51 insertions(+), 50 deletions(-) diff --git a/app/ui.js b/app/ui.js index 20a8912d..e9be3281 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1224,7 +1224,7 @@ var UI = { if (screen && UI.connected) { var resizeMode = UI.getSetting('resize'); - UI.rfb.scale = 1.0; + UI.rfb.viewportScale = 1.0; // Make sure the viewport is adjusted first UI.updateViewClip(); @@ -1306,7 +1306,7 @@ var UI = { updateViewClip: function() { if (!UI.rfb) return; - var cur_clip = UI.rfb.viewport; + var cur_clip = UI.rfb.clipViewport; var new_clip = UI.getSetting('view_clip'); var resizeSetting = UI.getSetting('resize'); @@ -1319,7 +1319,7 @@ var UI = { } if (cur_clip !== new_clip) { - UI.rfb.viewport = new_clip; + UI.rfb.clipViewport = new_clip; } var size = UI.screenSize(); @@ -1357,7 +1357,7 @@ var UI = { toggleViewDrag: function() { if (!UI.rfb) return; - var drag = UI.rfb.viewportDrag; + var drag = UI.rfb.dragViewport; UI.setViewDrag(!drag); }, @@ -1365,7 +1365,7 @@ var UI = { setViewDrag: function(drag) { if (!UI.rfb) return; - UI.rfb.viewportDrag = drag; + UI.rfb.dragViewport = drag; UI.updateViewDrag(); }, @@ -1377,21 +1377,21 @@ var UI = { // Check if viewport drag is possible. It is only possible // if the remote display is clipping the client display. - if (UI.rfb.viewport && UI.rfb.isClipped) { + if (UI.rfb.clipViewport && UI.rfb.isClipped) { clipping = true; } var viewDragButton = document.getElementById('noVNC_view_drag_button'); if (!clipping && - UI.rfb.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.viewportDrag = false; + UI.rfb.dragViewport = false; } - if (UI.rfb.viewportDrag) { + if (UI.rfb.dragViewport) { viewDragButton.classList.add("noVNC_selected"); } else { viewDragButton.classList.remove("noVNC_selected"); diff --git a/core/display.js b/core/display.js index 13d47773..87b385ad 100644 --- a/core/display.js +++ b/core/display.js @@ -89,10 +89,10 @@ Display.prototype = { this._rescale(scale); }, - _viewport: false, - get viewport() { return this._viewport; }, - set viewport(viewport) { - this._viewport = viewport; + _clipViewport: false, + get clipViewport() { return this._clipViewport; }, + set clipViewport(viewport) { + this._clipViewport = viewport; // May need to readjust the viewport dimensions var vp = this._viewportLoc; this.viewportChangeSize(vp.w, vp.h); @@ -124,7 +124,7 @@ Display.prototype = { deltaX = Math.floor(deltaX); deltaY = Math.floor(deltaY); - if (!this._viewport) { + if (!this._clipViewport) { deltaX = -vp.w; // clamped later of out of bounds deltaY = -vp.h; } @@ -163,7 +163,7 @@ Display.prototype = { viewportChangeSize: function(width, height) { - if (!this._viewport || + if (!this._clipViewport || typeof(width) === "undefined" || typeof(height) === "undefined") { diff --git a/core/rfb.js b/core/rfb.js index 3ab3bcad..6bbf61b0 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -217,7 +217,7 @@ RFB.prototype = { // ===== PROPERTIES ===== disconnectTimeout: 3, - viewportDrag: false, + dragViewport: false, _localCursor: false, get localCursor() { return this._localCursor; }, @@ -262,11 +262,11 @@ RFB.prototype = { get touchButton() { return this._mouse.touchButton; }, set touchButton(button) { this._mouse.touchButton = button; }, - get scale() { return this._display.scale; }, - set scale(scale) { this._display.scale = scale; }, + get viewportScale() { return this._display.scale; }, + set viewportScale(scale) { this._display.scale = scale; }, - get viewport() { return this._display.viewport; }, - set viewport(viewport) { this._display.viewport = viewport; }, + get clipViewport() { return this._display.clipViewport; }, + set clipViewport(viewport) { this._display.clipViewport = viewport; }, get isClipped() { return this._display.isClipped; }, @@ -679,7 +679,7 @@ RFB.prototype = { this._mouse_buttonMask &= ~bmask; } - if (this._viewportDrag) { + if (this.dragViewport) { if (down && !this._viewportDragging) { this._viewportDragging = true; this._viewportDragPos = {'x': x, 'y': y}; diff --git a/docs/API-internal.md b/docs/API-internal.md index 21741dc7..252b7865 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -82,14 +82,14 @@ None ### 2.3.1 Configuration Attributes -| name | type | mode | default | description -| ----------- | ----- | ---- | ------- | ------------ -| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data} -| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0 -| viewport | bool | RW | false | Use viewport clipping -| width | int | RO | | Display area width -| height | int | RO | | Display area height -| isClipped | bool | RO | | Is the remote display is larger than the client display +| name | type | mode | default | description +| ------------ | ----- | ---- | ------- | ------------ +| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data} +| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0 +| clipViewport | bool | RW | false | Use viewport clipping +| width | int | RO | | Display area width +| height | int | RO | | Display area height +| isClipped | bool | RO | | Is the remote display is larger than the client display ### 2.3.2 Methods diff --git a/docs/API.md b/docs/API.md index 6c948a53..e8fe0556 100644 --- a/docs/API.md +++ b/docs/API.md @@ -31,20 +31,20 @@ protocol stream. [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button). Is set to `1` by default. -`scale` +`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. -`viewport` +`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. -`viewportDrag` +`dragViewport` - Is a `boolean` indicating if mouse events should control the - relative position of a clipped canvas. Only relevant if `viewport` - is enabled. Disabled by default. + relative position of a clipped canvas. Only relevant if + `clipViewport` is enabled. Disabled by default. `isClipped` *Read only* - Is a `boolean` indicating if the framebuffer is larger than the @@ -130,7 +130,8 @@ protocol stream. - Send clipboard contents to server. [`RFB.autoscale()`](#rfbautoscale) - - Set `RFB.scale` so that the framebuffer fits a specified container. + - Set `RFB.viewportScale` so that the framebuffer fits a specified + container. [`RFB.requestDesktopSize()`](#rfbrequestDesktopSize) - Send a request to change the remote desktop size. @@ -409,7 +410,7 @@ to the remote server. #### RFB.autoscale() The `RFB.autoscale()` method is used to automatically adjust -`RFB.scale` to fit given dimensions. +`RFB.viewportScale` to fit given dimensions. ##### Syntax @@ -453,7 +454,7 @@ actually changes dimensions. 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.viewport` is set to `false`. +This method has no effect if `RFB.clipViewport` is set to `false`. ##### Syntax diff --git a/tests/test.display.js b/tests/test.display.js index 33ff8f12..cb2460b9 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -40,7 +40,7 @@ describe('Display/Canvas Helper', function () { var display; beforeEach(function () { display = new Display(document.createElement('canvas')); - display.viewport = true; + display.clipViewport = true; display.resize(5, 5); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -101,7 +101,7 @@ describe('Display/Canvas Helper', function () { }); it('should show the entire framebuffer when disabling the viewport', function() { - display.viewport = false; + display.clipViewport = false; expect(display.absX(0)).to.equal(0); expect(display.absY(0)).to.equal(0); expect(display._target.width).to.equal(5); @@ -109,7 +109,7 @@ describe('Display/Canvas Helper', function () { }); it('should ignore viewport changes when the viewport is disabled', function() { - display.viewport = false; + display.clipViewport = false; display.viewportChangeSize(2, 2); display.viewportChangePos(1, 1); expect(display.absX(0)).to.equal(0); @@ -119,8 +119,8 @@ describe('Display/Canvas Helper', function () { }); it('should show the entire framebuffer just after enabling the viewport', function() { - display.viewport = false; - display.viewport = true; + display.clipViewport = false; + display.clipViewport = true; expect(display.absX(0)).to.equal(0); expect(display.absY(0)).to.equal(0); expect(display._target.width).to.equal(5); @@ -132,7 +132,7 @@ describe('Display/Canvas Helper', function () { var display; beforeEach(function () { display = new Display(document.createElement('canvas')); - display.viewport = false; + display.clipViewport = false; display.resize(4, 4); }); @@ -157,7 +157,7 @@ describe('Display/Canvas Helper', function () { describe('viewport', function () { beforeEach(function () { - display.viewport = true; + display.clipViewport = true; display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); }); @@ -195,7 +195,7 @@ describe('Display/Canvas Helper', function () { beforeEach(function () { canvas = document.createElement('canvas'); display = new Display(canvas); - display.viewport = true; + display.clipViewport = true; display.resize(4, 4); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -236,7 +236,7 @@ describe('Display/Canvas Helper', function () { beforeEach(function () { canvas = document.createElement('canvas'); display = new Display(canvas); - display.viewport = true; + display.clipViewport = true; display.resize(4, 3); document.body.appendChild(canvas); }); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 918d5dc9..b945fe2e 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1892,20 +1892,20 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send button messages when initiating viewport dragging', function () { - client._viewportDrag = true; + client.dragViewport = true; client._handleMouseButton(13, 9, 0x001); expect(client._sock.flush).to.not.have.been.called; }); it('should be initiate viewport dragging on a button down event, if enabled', function () { - client._viewportDrag = true; + client.dragViewport = true; client._handleMouseButton(13, 9, 0x001); expect(client._viewportDragging).to.be.true; expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 }); }); it('should terminate viewport dragging on a button up event, if enabled', function () { - client._viewportDrag = true; + client.dragViewport = true; client._viewportDragging = true; client._handleMouseButton(13, 9, 0x000); expect(client._viewportDragging).to.be.false; @@ -1917,7 +1917,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var newX = 123 + 11 * window.devicePixelRatio; var newY = 109 + 4 * window.devicePixelRatio; - client._viewportDrag = true; + client.dragViewport = true; client._viewportDragging = true; client._viewportHasMoved = false; client._viewportDragPos = { x: oldX, y: oldY }; From 8d1f0a3de8630300c6ffb87e98b8cb1d438f0f00 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 13:19:21 +0200 Subject: [PATCH 26/34] Remove local cursor setting We have no reason to disable this except for touch devices, which we can handle without having a setting for it. --- app/ui.js | 23 +---------------------- core/rfb.js | 26 +++----------------------- docs/API.md | 4 ---- vnc.html | 4 ---- vnc_lite.html | 1 - 5 files changed, 4 insertions(+), 54 deletions(-) diff --git a/app/ui.js b/app/ui.js index e9be3281..37e30413 100644 --- a/app/ui.js +++ b/app/ui.js @@ -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"; @@ -167,7 +167,6 @@ var UI = { 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); @@ -378,8 +377,6 @@ var UI = { .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); @@ -464,12 +461,6 @@ var UI = { UI.enableDisableViewClip(); - if (cursorURIsSupported() && !isTouchDevice) { - UI.enableSetting('cursor'); - } else { - UI.disableSetting('cursor'); - } - if (UI.connected) { UI.disableSetting('encrypt'); UI.disableSetting('shared'); @@ -885,12 +876,6 @@ var UI = { // 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'); @@ -1062,7 +1047,6 @@ var UI = { UI.closeAllPanels(); UI.closeConnectPanel(); - UI.updateLocalCursor(); UI.updateViewOnly(); var url; @@ -1677,11 +1661,6 @@ var UI = { } }, - updateLocalCursor: function() { - if (!UI.rfb) return; - UI.rfb.localCursor = UI.getSetting('cursor'); - }, - updateViewOnly: function() { if (!UI.rfb) return; UI.rfb.viewOnly = UI.getSetting('view_only'); diff --git a/core/rfb.js b/core/rfb.js index 6bbf61b0..1f5d964d 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -13,7 +13,7 @@ import * as Log from './util/logging.js'; import _ from './util/localization.js'; import { decodeUTF8 } from './util/strings.js'; -import { browserSupportsCursorURIs } from './util/browsers.js'; +import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js'; import Display from "./display.js"; import Keyboard from "./input/keyboard.js"; import Mouse from "./input/mouse.js"; @@ -219,27 +219,6 @@ RFB.prototype = { disconnectTimeout: 3, dragViewport: false, - _localCursor: false, - get localCursor() { return this._localCursor; }, - set localCursor(cursor) { - if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { - this._localCursor = false; - this._display.disableLocalCursor(); //Only show server-side cursor - } else { - if (browserSupportsCursorURIs()) { - this._localCursor = 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(); - } - }, - _viewOnly: false, get viewOnly() { return this._viewOnly; }, set viewOnly(viewOnly) { @@ -1155,7 +1134,8 @@ RFB.prototype = { encs.push(encodings.pseudoEncodingFence); encs.push(encodings.pseudoEncodingContinuousUpdates); - if (this._localCursor && this._fb_depth == 24) { + if (browserSupportsCursorURIs() && + !isTouchDevice && this._fb_depth == 24) { encs.push(encodings.pseudoEncodingCursor); } diff --git a/docs/API.md b/docs/API.md index e8fe0556..a70f1916 100644 --- a/docs/API.md +++ b/docs/API.md @@ -16,10 +16,6 @@ protocol stream. ### Properties -`localCursor` - - Is a `boolean` indicating if a client side cursor should be - requested. Disabled by default. - `viewOnly` - Is a `boolean` indicating if any events (e.g. key presses or mouse movement) should be prevented from being sent to the server. diff --git a/vnc.html b/vnc.html index e2799d6e..76314e58 100644 --- a/vnc.html +++ b/vnc.html @@ -221,10 +221,6 @@
  • Advanced
      -
    • - -
    • -

    • diff --git a/vnc_lite.html b/vnc_lite.html index f0c8ace5..153244b9 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -257,7 +257,6 @@ try { rfb = new RFB(document.getElementById('noVNC_canvas')); - rfb.localCursor = WebUtil.getConfigVar('cursor', true); rfb.viewOnly = WebUtil.getConfigVar('view_only', false); rfb.onnotification = notification; rfb.onupdatestate = updateState; From 002907d2ce71ade88be10309b8d36ba0cc1cfba5 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 13:23:03 +0200 Subject: [PATCH 27/34] Remove "downscale only" mode The normal scaling mode should be sufficient for most use cases, so let's keep the interface simple. --- app/ui.js | 9 ++++----- core/display.js | 6 +----- core/rfb.js | 4 ++-- docs/API-internal.md | 2 +- docs/API.md | 5 +---- tests/test.display.js | 14 -------------- vnc.html | 1 - 7 files changed, 9 insertions(+), 32 deletions(-) diff --git a/app/ui.js b/app/ui.js index 37e30413..6d910dd4 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1240,7 +1240,7 @@ var UI = { if (!UI.rfb) return; var resizeMode = UI.getSetting('resize'); - if (resizeMode !== 'scale' && resizeMode !== 'downscale') { + if (resizeMode !== 'scale') { return; } @@ -1250,8 +1250,7 @@ var UI = { return; } - var downscaleOnly = resizeMode === 'downscale'; - UI.rfb.autoscale(screen.w, screen.h, downscaleOnly); + UI.rfb.autoscale(screen.w, screen.h); UI.fixScrollbars(); }, @@ -1294,7 +1293,7 @@ var UI = { 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) { @@ -1324,7 +1323,7 @@ var UI = { 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 { diff --git a/core/display.js b/core/display.js index 87b385ad..e61802a6 100644 --- a/core/display.js +++ b/core/display.js @@ -517,7 +517,7 @@ Display.prototype = { this._target.style.cursor = "none"; }, - autoscale: function (containerWidth, containerHeight, downscaleOnly) { + autoscale: function (containerWidth, containerHeight) { var vp = this._viewportLoc; var targetAspectRatio = containerWidth / containerHeight; var fbAspectRatio = vp.w / vp.h; @@ -529,10 +529,6 @@ Display.prototype = { scaleRatio = containerHeight / vp.h; } - if (scaleRatio > 1.0 && downscaleOnly) { - scaleRatio = 1.0; - } - this._rescale(scaleRatio); }, diff --git a/core/rfb.js b/core/rfb.js index 1f5d964d..882b52b5 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -351,9 +351,9 @@ RFB.prototype = { RFB.messages.clientCutText(this._sock, text); }, - autoscale: function (width, height, downscaleOnly) { + autoscale: function (width, height) { if (this._rfb_connection_state !== 'connected') { return; } - this._display.autoscale(width, height, downscaleOnly); + this._display.autoscale(width, height); }, viewportChangeSize: function(width, height) { diff --git a/docs/API-internal.md b/docs/API-internal.md index 252b7865..f030dc38 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -117,7 +117,7 @@ None | changeCursor | (pixels, mask, hotx, hoty, w, h) | Change cursor appearance | defaultCursor | () | Restore default cursor appearance | disableLocalCursor | () | Disable local (client-side) cursor -| autoscale | (containerWidth, containerHeight, downscaleOnly) | Scale the display +| autoscale | (containerWidth, containerHeight) | Scale the display ### 2.3.3 Callbacks diff --git a/docs/API.md b/docs/API.md index a70f1916..18e96068 100644 --- a/docs/API.md +++ b/docs/API.md @@ -410,7 +410,7 @@ The `RFB.autoscale()` method is used to automatically adjust ##### Syntax - RFB.autoscale( width, height, downscaleOnly ); + RFB.autoscale( width, height ); ###### Parameters @@ -420,9 +420,6 @@ The `RFB.autoscale()` method is used to automatically adjust **`height`** - A `long` specifying the maximum height of the canvas in CSS pixels. -**`downscaleOnly`** - - A `boolean` specifying if the scale must be kept below `1.0`. - #### RFB.requestDesktopSize() The `RFB.requestDesktopSize()` method is used to request a change of diff --git a/tests/test.display.js b/tests/test.display.js index cb2460b9..b8e9b51f 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -272,20 +272,6 @@ describe('Display/Canvas Helper', function () { expect(canvas.width).to.equal(4); expect(canvas.height).to.equal(3); }); - - it('should not upscale when downscaleOnly is true', function () { - display.autoscale(2, 2, true); - expect(display.absX(9)).to.equal(18); - expect(display.absY(18)).to.equal(36); - expect(canvas.clientWidth).to.equal(2); - expect(canvas.clientHeight).to.equal(2); - - display.autoscale(16, 9, true); - expect(display.absX(9)).to.equal(9); - expect(display.absY(18)).to.equal(18); - expect(canvas.clientWidth).to.equal(4); - expect(canvas.clientHeight).to.equal(3); - }); }); describe('drawing', function () { diff --git a/vnc.html b/vnc.html index 76314e58..fba59b1b 100644 --- a/vnc.html +++ b/vnc.html @@ -213,7 +213,6 @@
    • From 68e09edcdc7395aff9d4102ad04d1fa3b354c7c3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 13:26:49 +0200 Subject: [PATCH 28/34] Remove disconnectTimeout property Callers should not need to modify this timeout. --- core/rfb.js | 6 ++++-- docs/API.md | 4 ---- tests/test.rfb.js | 6 +++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index 882b52b5..39e02dd2 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -28,6 +28,9 @@ import { encodings, encodingName } from "./encodings.js"; /*jslint white: false, browser: true */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */ +// How many seconds to wait for a disconnect to finish +var DISCONNECT_TIMEOUT = 3; + export default function RFB(target) { this._target = target; @@ -216,7 +219,6 @@ export default function RFB(target) { RFB.prototype = { // ===== PROPERTIES ===== - disconnectTimeout: 3, dragViewport: false, _viewOnly: false, @@ -548,7 +550,7 @@ RFB.prototype = { this._disconnTimer = setTimeout(function () { this._rfb_disconnect_reason = _("Disconnect timeout"); this._updateConnectionState('disconnected'); - }.bind(this), this._disconnectTimeout * 1000); + }.bind(this), DISCONNECT_TIMEOUT * 1000); break; } }, diff --git a/docs/API.md b/docs/API.md index 18e96068..7b6596b9 100644 --- a/docs/API.md +++ b/docs/API.md @@ -46,10 +46,6 @@ protocol stream. - Is a `boolean` indicating if the framebuffer is larger than the current canvas, i.e. it is being clipped. -`disconnectTimeout` - - Is a `long` indicating how many seconds to wait for a disconnect - request to complete. Is set to `3` by default. - `capabilities` *Read only* - Is an `Object` indicating which optional extensions are available on the server. Some methods may only be called if the corresponding diff --git a/tests/test.rfb.js b/tests/test.rfb.js index b945fe2e..abe48a3f 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -483,7 +483,7 @@ describe('Remote Frame Buffer Protocol Client', function() { sinon.spy(client, '_updateConnectionState'); client._sock._websocket.close = function () {}; // explicitly don't call onclose client._updateConnectionState('disconnecting'); - this.clock.tick(client.disconnectTimeout * 1000); + this.clock.tick(3 * 1000); expect(client._updateConnectionState).to.have.been.calledTwice; expect(client._rfb_disconnect_reason).to.not.equal(""); expect(client._rfb_connection_state).to.equal("disconnected"); @@ -491,9 +491,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not fail if Websock.onclose gets called within the disconnection timeout', function () { client._updateConnectionState('disconnecting'); - this.clock.tick(client.disconnectTimeout * 500); + this.clock.tick(3 * 1000 / 2); client._sock._websocket.close(); - this.clock.tick(client.disconnectTimeout * 500 + 1); + this.clock.tick(3 * 1000 / 2 + 1); expect(client._rfb_connection_state).to.equal('disconnected'); }); From 4a3699536312adb7fda0bb2c68ac7dcd1fdbd71e Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 16:31:49 +0200 Subject: [PATCH 29/34] Remove redundant setup of fake timer handling We already set this up globally, so no need for these extra ones. --- tests/test.rfb.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index abe48a3f..0a55715d 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -112,9 +112,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('#sendCredentials', function () { - beforeEach(function () { this.clock = sinon.useFakeTimers(); }); - afterEach(function () { this.clock.restore(); }); - it('should set the rfb credentials properly"', function () { client.sendCredentials({ password: 'pass' }); expect(client._rfb_credentials).to.deep.equal({ password: 'pass' }); @@ -321,14 +318,9 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('#_updateConnectionState', function () { var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); client = make_rfb(); }); - afterEach(function () { - this.clock.restore(); - }); - it('should clear the disconnect timer if the state is not "disconnecting"', function () { var spy = sinon.spy(); client._disconnTimer = setTimeout(spy, 50); @@ -378,15 +370,10 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('#_fail', function () { var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); client = make_rfb(); client.connect('wss://host:8675'); }); - afterEach(function () { - this.clock.restore(); - }); - it('should close the WebSocket connection', function () { sinon.spy(client._sock, 'close'); client._fail(); @@ -470,15 +457,10 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('disconnecting', function () { var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); client = make_rfb(); client.connect('wss://host:8675'); }); - afterEach(function () { - this.clock.restore(); - }); - it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () { sinon.spy(client, '_updateConnectionState'); client._sock._websocket.close = function () {}; // explicitly don't call onclose @@ -1965,11 +1947,8 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); client.connect('wss://host:8675'); - this.clock = sinon.useFakeTimers(); }); - afterEach(function () { this.clock.restore(); }); - // message events it ('should do nothing if we receive an empty message and have nothing in the queue', function () { client._rfb_connection_state = 'connected'; From 057b8fec7a29931cd6778d35e691db22daa44f28 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 16:43:34 +0200 Subject: [PATCH 30/34] Clean up beforeEach() code in RFB tests There was a lot of redundancy that could be removed. --- tests/test.rfb.js | 179 ++++++++++++---------------------------------- 1 file changed, 45 insertions(+), 134 deletions(-) diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 0a55715d..5a41918b 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -58,7 +58,7 @@ describe('Remote Frame Buffer Protocol Client', function() { this.clock.restore(); }); - describe('Public API Basic Behavior', function () { + describe('Connecting/Disconnecting', function () { var client; beforeEach(function () { client = make_rfb(); @@ -124,17 +124,19 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._init_msg).to.have.been.calledOnce; }); }); + }); + + describe('Public API Basic Behavior', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + client._rfb_connection_state = 'connected'; + }); describe('#sendCtrlAlDel', function () { - beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._viewOnly = false; - }); - it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { var expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 0xFFE3, 1); @@ -149,12 +151,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the keys if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.sendCtrlAltDel(); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the keys if we are set as view_only', function () { + sinon.spy(client._sock, 'flush'); client._viewOnly = true; client.sendCtrlAltDel(); expect(client._sock.flush).to.not.have.been.called; @@ -162,15 +166,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('#sendKey', function () { - beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._viewOnly = false; - }); - it('should send a single key with the given code and state (down = true)', function () { var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 123, 1); @@ -187,12 +182,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the key if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the key if we are set as view_only', function () { + sinon.spy(client._sock, 'flush'); client._viewOnly = true; client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; @@ -216,15 +213,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('#clipboardPasteFrom', function () { - beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._viewOnly = false; - }); - it('should send the given text in a paste event', function () { var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}}; RFB.messages.clientCutText(expected, 'abc'); @@ -233,6 +221,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the text if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.clipboardPasteFrom('abc'); expect(client._sock.flush).to.not.have.been.called; @@ -241,12 +230,6 @@ describe('Remote Frame Buffer Protocol Client', function() { describe("#requestDesktopSize", function () { beforeEach(function() { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._viewOnly = false; client._supportsSetDesktopSize = true; }); @@ -269,12 +252,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () { + sinon.spy(client._sock, 'flush'); client._supportsSetDesktopSize = false; client.requestDesktopSize(1,2); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the request if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.requestDesktopSize(1,2); expect(client._sock.flush).to.not.have.been.called; @@ -283,12 +268,6 @@ describe('Remote Frame Buffer Protocol Client', function() { describe("XVP operations", function () { beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._viewOnly = false; client._rfb_xvp_ver = 1; }); @@ -308,6 +287,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send XVP operations with higher versions than we support', function () { + sinon.spy(client._sock, 'flush'); client._xvpOp(2, 7); expect(client._sock.flush).to.not.have.been.called; }); @@ -532,14 +512,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Protocol Initialization States', function () { - describe('ProtocolVersion', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - }); + var client; + beforeEach(function () { + client = make_rfb(); + client.connect('wss://host:8675'); + client._sock._websocket._open(); + }); + describe('ProtocolVersion', function () { function send_ver (ver, client) { var arr = new Uint8Array(12); for (var i = 0; i < ver.length; i++) { @@ -551,13 +531,6 @@ describe('Remote Frame Buffer Protocol Client', function() { } describe('version parsing', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - }); - it('should interpret version 003.003 as version 3.3', function () { send_ver('003.003', client); expect(client._rfb_version).to.equal(3.3); @@ -623,10 +596,13 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Repeater', function () { - it('should interpret version 000.000 as a repeater', function () { + beforeEach(function () { client = make_rfb(); client.connect('wss://host:8675', { repeaterID: "12345" }); client._sock._websocket._open(); + }); + + it('should interpret version 000.000 as a repeater', function () { send_ver('000.000', client); expect(client._rfb_version).to.equal(0); @@ -636,9 +612,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle two step repeater negotiation', function () { - client = make_rfb(); - client.connect('wss://host:8675', { repeaterID: "12345" }); - client._sock._websocket._open(); send_ver('000.000', client); send_ver('003.008', client); expect(client._rfb_version).to.equal(3.8); @@ -647,12 +620,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Security', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; }); @@ -711,12 +679,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Authentication', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; }); @@ -760,12 +723,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('VNC Authentication (type 2) Handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; }); @@ -810,12 +768,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('XVP Authentication (type 22) Handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; }); @@ -867,12 +820,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('TightVNC Authentication (type 16) Handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; send_security(16, client); @@ -957,12 +905,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('SecurityResult', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'SecurityResult'; }); @@ -1022,12 +965,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('ServerInitialisation', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); client._rfb_init_state = 'ServerInitialisation'; }); @@ -1298,14 +1236,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Message Encoding Handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; // a really small frame client._fb_width = 4; client._fb_height = 4; @@ -1385,19 +1316,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('the HEXTILE encoding handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; - // a really small frame - client._fb_width = 4; - client._fb_height = 4; - client._display.resize(4, 4); - }); - it('should handle a tile with fg, bg specified, normal subrects', function () { var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; var rect = []; @@ -1801,25 +1719,26 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Asynchronous Events', function () { - describe('Mouse event handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - }); + var client; + beforeEach(function () { + client = make_rfb(); + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + client._rfb_connection_state = 'connected'; + }); + describe('Mouse event handlers', function () { it('should not send button messages in view-only mode', function () { client._viewOnly = true; + sinon.spy(client._sock, 'flush'); client._handleMouseButton(0, 0, 1, 0x001); expect(client._sock.flush).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { client._viewOnly = true; + sinon.spy(client._sock, 'flush'); client._handleMouseMove(0, 0); expect(client._sock.flush).to.not.have.been.called; }); @@ -1869,12 +1788,14 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not send movement messages when viewport dragging', function () { client._viewportDragging = true; client._display.viewportChangePos = sinon.spy(); + sinon.spy(client._sock, 'flush'); client._handleMouseMove(13, 9); expect(client._sock.flush).to.not.have.been.called; }); it('should not send button messages when initiating viewport dragging', function () { client.dragViewport = true; + sinon.spy(client._sock, 'flush'); client._handleMouseButton(13, 9, 0x001); expect(client._sock.flush).to.not.have.been.called; }); @@ -1916,17 +1837,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Keyboard Event Handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._viewOnly = false; - }); - it('should send a key message on a key press', function () { var keyevent = {}; client._handleKeyEvent(0x41, 'KeyA', true); @@ -1937,6 +1847,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not send messages in view-only mode', function () { client._viewOnly = true; + sinon.spy(client._sock, 'flush'); client._handleKeyEvent('a', 'KeyA', true); expect(client._sock.flush).to.not.have.been.called; }); From 2f4516f293d4c64394f28ca62640cad5bb773656 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 20 Oct 2017 16:46:36 +0200 Subject: [PATCH 31/34] Integrate connect() in to constructor An RFB object represents a single connection so it doesn't make sense to have one without it trying to connect right away. Matches the behaviour of other APIs, e.g. WebSocket. --- app/ui.js | 39 +++++-------- core/rfb.js | 38 ++++++------ docs/API.md | 88 ++++++++++++---------------- tests/playback.js | 11 +--- tests/test.rfb.js | 143 ++++++++++++++++++---------------------------- vnc_lite.html | 28 ++++----- 6 files changed, 134 insertions(+), 213 deletions(-) diff --git a/app/ui.js b/app/ui.js index 6d910dd4..52fcf161 100644 --- a/app/ui.js +++ b/app/ui.js @@ -199,27 +199,6 @@ var UI = { } }, - initRFB: function() { - try { - UI.rfb = new RFB(document.getElementById('noVNC_canvas')); - UI.rfb.onnotification = UI.notification; - UI.rfb.onupdatestate = UI.updateState; - UI.rfb.ondisconnected = UI.disconnectFinished; - UI.rfb.oncredentialsrequired = UI.credentials; - UI.rfb.oncapabilities = function () { UI.updatePowerButton(); UI.initialResize(); }; - UI.rfb.onclipboard = UI.clipboardReceive; - UI.rfb.onbell = UI.bell; - UI.rfb.onfbresize = UI.updateSessionSize; - UI.rfb.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 * ============== @@ -1042,8 +1021,6 @@ var UI = { return; } - if (!UI.initRFB()) return; - UI.closeAllPanels(); UI.closeConnectPanel(); @@ -1059,9 +1036,19 @@ var UI = { } url += '/' + path; - UI.rfb.connect(url, { shared: UI.getSetting('shared'), - repeaterID: UI.getSetting('repeaterID'), - credentials: { password: password } }); + UI.rfb = new RFB(document.getElementById('noVNC_canvas'), url, + { shared: UI.getSetting('shared'), + repeaterID: UI.getSetting('repeaterID'), + credentials: { password: password } }); + UI.rfb.onnotification = UI.notification; + UI.rfb.onupdatestate = UI.updateState; + UI.rfb.ondisconnected = UI.disconnectFinished; + UI.rfb.oncredentialsrequired = UI.credentials; + UI.rfb.oncapabilities = function () { UI.updatePowerButton(); UI.initialResize(); }; + UI.rfb.onclipboard = UI.clipboardReceive; + UI.rfb.onbell = UI.bell; + UI.rfb.onfbresize = UI.updateSessionSize; + UI.rfb.ondesktopname = UI.updateDesktopName; }, disconnect: function() { diff --git a/core/rfb.js b/core/rfb.js index 39e02dd2..28e1a4a4 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -31,12 +31,22 @@ import { encodings, encodingName } from "./encodings.js"; // How many seconds to wait for a disconnect to finish var DISCONNECT_TIMEOUT = 3; -export default function RFB(target) { +export default function RFB(target, url, options) { + if (!target) { + throw Error("Must specify target"); + } + if (!url) { + throw Error("Must specify URL"); + } + this._target = target; + this._url = url; // Connection details - this._url = ''; - this._rfb_credentials = {}; + 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 = ''; @@ -213,6 +223,10 @@ export default function RFB(target) { Log.Warn("WebSocket on-error event"); }); + // 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"); }; @@ -265,24 +279,6 @@ RFB.prototype = { // ===== PUBLIC METHODS ===== - connect: function (url, options) { - if (!url) { - this._fail(_("Must specify URL")); - return; - } - - this._url = url; - - options = options || {} - - this._rfb_credentials = options.credentials || {}; - this._shared = 'shared' in options ? !!options.shared : true; - this._repeaterID = options.repeaterID || ''; - - this._rfb_init_state = ''; - this._updateConnectionState('connecting'); - }, - disconnect: function () { this._updateConnectionState('disconnecting'); this._sock.off('error'); diff --git a/docs/API.md b/docs/API.md index 7b6596b9..8627f6c9 100644 --- a/docs/API.md +++ b/docs/API.md @@ -92,9 +92,6 @@ protocol stream. ### Methods -[`RFB.connect()`](#rfbconnect) - - Connect to a server. - [`RFB.disconnect()`](#rfbdisconnect) - Disconnect from the server. @@ -135,13 +132,12 @@ protocol stream. #### RFB() -The `RFB()` constructor returns a new `RFB` object. The object will -initially be disconnected and [`RFB.connect()`](#rfbconnect) must be -called before the object will be useful. +The `RFB()` constructor returns a new `RFB` object and initiates a new +connection to a specified VNC server. ##### Syntax - var rfb = new RFB( target ); + var rfb = new RFB( target, url [, options] ); ###### Parameters @@ -150,6 +146,35 @@ called before the object will be useful. 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. + #### RFB.onupdatestate The `onupdatestate` event handler is fired after the noVNC connection @@ -201,9 +226,9 @@ termination. #### RFB.oncredentialsrequired The `oncredentialsrequired` event handler is fired when the server -requests more credentials than were specified to -[`RFB.connect()`](#rfbconnect). The **`types`** argument is a list of -all the credentials that are required. +requests more credentials than were specified to [`RFB()`](#rfb-1). The +**`types`** argument is a list of all the credentials that are +required. ##### Syntax @@ -254,46 +279,6 @@ or removed from `RFB.capabilities`. RFB.oncapabilities = function(rfb, capabilites) { ... } -#### RFB.connect() - -The `RFB.connect()` method is used to initiate a new connection to a -specified VNC server. - -##### Syntax - - RFB.connect( url [, options] ); - -###### Parameters - -**`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. - #### RFB.disconnect() The `RFB.disconnect()` method is used to disconnect from the currently @@ -316,8 +301,7 @@ credentials after `RFB.oncredentialsrequired` has been fired. **`credentials`** - An `Object` specifying the credentials to provide to the server - when authenticating. See [`RFB.connect()`](#rfbconnect) for - details. + when authenticating. See [`RFB()`](#rfb-1) for details. #### RFB.sendKey() diff --git a/tests/playback.js b/tests/playback.js index 03748c58..2cd74998 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -77,7 +77,7 @@ export default function RecordingPlayer (frames, encoding, disconnected, notific RecordingPlayer.prototype = { run: function (realtime, trafficManagement) { // initialize a new RFB - this._rfb = new RFB(document.getElementById('VNC_canvas')); + this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test'); this._rfb.viewOnly = true; this._rfb.ondisconnected = this._handleDisconnect.bind(this); this._rfb.onnotification = this._notification; @@ -92,9 +92,6 @@ RecordingPlayer.prototype = { this._running = true; - // launch the tests - this._rfb.connect('wss://test'); - this._queueNextPacket(); }, @@ -104,12 +101,8 @@ RecordingPlayer.prototype = { this._rfb._sock.close = function () {}; this._rfb._sock.flush = function () {}; this._rfb._checkEvents = function () {}; - this._rfb.connect = function (url) { - this._url = url; - this._rfb_credentials = {}; + this._rfb._connect = function () { this._sock.init('binary', 'ws'); - this._rfb_connection_state = 'connecting'; - this._rfb_init_state = 'ProtocolVersion'; }; }, diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 5a41918b..c218ae07 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -9,10 +9,6 @@ import { encodings } from '../core/encodings.js'; import FakeWebSocket from './fake.websocket.js'; import sinon from '../vendor/sinon.js'; -function make_rfb () { - return new RFB(document.createElement('canvas')); -} - var push8 = function (arr, num) { "use strict"; arr.push(num & 0xFF); @@ -33,12 +29,13 @@ var push32 = function (arr, num) { }; describe('Remote Frame Buffer Protocol Client', function() { - "use strict"; + var clock; + before(FakeWebSocket.replace); after(FakeWebSocket.restore); before(function () { - this.clock = sinon.useFakeTimers(); + this.clock = clock = sinon.useFakeTimers(); // Use a single set of buffers instead of reallocating to // speed up tests var sock = new Websock(); @@ -58,38 +55,46 @@ describe('Remote Frame Buffer Protocol Client', function() { this.clock.restore(); }); + function make_rfb (url, options) { + url = url || 'wss://host:8675'; + var rfb = new RFB(document.createElement('canvas'), url, options); + clock.tick(); + rfb._sock._websocket._open(); + rfb._rfb_connection_state = 'connected'; + return rfb; + } + describe('Connecting/Disconnecting', function () { - var client; - beforeEach(function () { - client = make_rfb(); - }); - - describe('#connect', function () { - beforeEach(function () { client._updateConnectionState = sinon.spy(); }); - + describe('#RFB', function () { it('should set the current state to "connecting"', function () { - client.connect('wss://host:8675'); - expect(client._updateConnectionState).to.have.been.calledOnce; - expect(client._updateConnectionState).to.have.been.calledWith('connecting'); + var client = new RFB(document.createElement('canvas'), 'wss://host:8675'); + client.onupdatestate = sinon.spy(); + this.clock.tick(); + expect(client.onupdatestate).to.have.been.calledOnce; + expect(client.onupdatestate).to.have.been.calledWith(client, 'connecting'); }); - it('should not try to connect if we are missing a URL', function () { - client._fail = sinon.spy(); - client._rfb_connection_state = ''; - client.connect(undefined); - expect(client._fail).to.have.been.calledOnce; - expect(client._updateConnectionState).to.not.have.been.called; - expect(client._rfb_connection_state).to.equal(''); + it('should actually connect to the websocket', function () { + var client = new RFB(document.createElement('canvas'), 'ws://HOST:8675/PATH'); + sinon.spy(client._sock, 'open'); + this.clock.tick(); + expect(client._sock.open).to.have.been.calledOnce; + expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); }); }); describe('#disconnect', function () { - beforeEach(function () { client._updateConnectionState = sinon.spy(); }); + var client; + beforeEach(function () { + client = make_rfb(); + }); it('should set the current state to "disconnecting"', function () { + client.onupdatestate = sinon.spy(); client.disconnect(); - expect(client._updateConnectionState).to.have.been.calledOnce; - expect(client._updateConnectionState).to.have.been.calledWith('disconnecting'); + expect(client.onupdatestate).to.have.been.calledTwice; + expect(client.onupdatestate).to.have.been.calledWith(client, 'disconnecting'); + expect(client.onupdatestate).to.have.been.calledWith(client, 'disconnected'); }); it('should unregister error event handler', function () { @@ -112,6 +117,12 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('#sendCredentials', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._rfb_connection_state = 'connecting'; + }); + it('should set the rfb credentials properly"', function () { client.sendCredentials({ password: 'pass' }); expect(client._rfb_credentials).to.deep.equal({ password: 'pass' }); @@ -130,10 +141,6 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; }); describe('#sendCtrlAlDel', function () { @@ -304,7 +311,8 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should clear the disconnect timer if the state is not "disconnecting"', function () { var spy = sinon.spy(); client._disconnTimer = setTimeout(spy, 50); - client._updateConnectionState('connecting'); + client._rfb_connection_state = 'connecting'; + client._updateConnectionState('connected'); this.clock.tick(51); expect(spy).to.not.have.been.called; expect(client._disconnTimer).to.be.null; @@ -312,9 +320,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should call the updateState callback', function () { client.onupdatestate = sinon.spy(); - client._updateConnectionState('connecting'); + client._updateConnectionState('disconnecting'); var spy = client.onupdatestate; - expect(spy.args[0][1]).to.equal('connecting'); + expect(spy.args[0][1]).to.equal('disconnecting'); }); it('should set the rfb_connection_state', function () { @@ -351,7 +359,6 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client.connect('wss://host:8675'); }); it('should close the WebSocket connection', function () { @@ -416,29 +423,10 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Connection States', function () { - describe('connecting', function () { - var client; - beforeEach(function () { client = make_rfb(); }); - - it('should actually connect to the websocket', function () { - sinon.spy(client._sock, 'open'); - client._updateConnectionState('connecting'); - expect(client._sock.open).to.have.been.calledOnce; - }); - - it('should use a url specified to connect', function () { - sinon.spy(client._sock, 'open'); - client._url = 'ws://HOST:8675/PATH'; - client._updateConnectionState('connecting'); - expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); - }); - }); - describe('disconnecting', function () { var client; beforeEach(function () { client = make_rfb(); - client.connect('wss://host:8675'); }); it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () { @@ -482,6 +470,7 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not call the disconnect callback if the state is not "disconnected"', function () { client.ondisconnected = sinon.spy(); + client._sock._websocket.close = function () {}; // explicitly don't call onclose client._updateConnectionState('disconnecting'); var spy = client.ondisconnected; expect(spy).to.not.have.been.called; @@ -515,8 +504,7 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); + client._rfb_connection_state = 'connecting'; }); describe('ProtocolVersion', function () { @@ -597,9 +585,8 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('Repeater', function () { beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675', { repeaterID: "12345" }); - client._sock._websocket._open(); + client = make_rfb('wss://host:8675', { repeaterID: "12345" }); + client._rfb_connection_state = 'connecting'; }); it('should interpret version 000.000 as a repeater', function () { @@ -933,31 +920,25 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('ClientInitialisation', function () { - var client; - - beforeEach(function () { - client = make_rfb(); - }); - it('should transition to the ServerInitialisation state', function () { - client.connect('wss://host:8675'); - client._sock._websocket._open(); + var client = make_rfb(); + client._rfb_connection_state = 'connecting'; client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._rfb_init_state).to.equal('ServerInitialisation'); }); it('should send 1 if we are in shared mode', function () { - client.connect('wss://host:8675', { shared: true }); - client._sock._websocket._open(); + var client = make_rfb('wss://host:8675', { shared: true }); + client._rfb_connection_state = 'connecting'; client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._sock).to.have.sent(new Uint8Array([1])); }); it('should send 0 if we are not in shared mode', function () { - client.connect('wss://host:8675', { shared: false }); - client._sock._websocket._open(); + var client = make_rfb('wss://host:8675', { shared: false }); + client._rfb_connection_state = 'connecting'; client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._sock).to.have.sent(new Uint8Array([0])); @@ -1120,9 +1101,6 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('wss://host:8675'); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; client._fb_name = 'some device'; client._fb_width = 640; client._fb_height = 20; @@ -1722,10 +1700,6 @@ describe('Remote Frame Buffer Protocol Client', function() { var client; beforeEach(function () { client = make_rfb(); - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; }); describe('Mouse event handlers', function () { @@ -1854,28 +1828,21 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('WebSocket event handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('wss://host:8675'); - }); - // message events it ('should do nothing if we receive an empty message and have nothing in the queue', function () { - client._rfb_connection_state = 'connected'; client._normal_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([])); expect(client._normal_msg).to.not.have.been.called; }); it('should handle a message in the connected state as a normal message', function () { - client._rfb_connection_state = 'connected'; client._normal_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); expect(client._normal_msg).to.have.been.calledOnce; }); it('should handle a message in any non-disconnected/failed state like an init message', function () { + client._rfb_connection_state = 'connecting'; client._rfb_init_state = 'ProtocolVersion'; client._init_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); @@ -1883,8 +1850,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should process all normal messages directly', function () { - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; client.onbell = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02])); expect(client.onbell).to.have.been.calledTwice; @@ -1892,6 +1857,8 @@ describe('Remote Frame Buffer Protocol Client', function() { // open events it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () { + client = new RFB(document.createElement('canvas'), 'wss://host:8675'); + this.clock.tick(); client._sock._websocket._open(); expect(client._rfb_init_state).to.equal('ProtocolVersion'); }); diff --git a/vnc_lite.html b/vnc_lite.html index 153244b9..f457423b 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -255,20 +255,6 @@ status('Must specify host and port in URL', 'error'); } - try { - rfb = new RFB(document.getElementById('noVNC_canvas')); - rfb.viewOnly = WebUtil.getConfigVar('view_only', false); - rfb.onnotification = notification; - rfb.onupdatestate = updateState; - rfb.ondisconnected = disconnected; - rfb.oncapabilities = function () { updatePowerButtons(); initialResize(); }; - rfb.oncredentialsrequired = credentials; - rfb.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', @@ -284,9 +270,17 @@ } url += '/' + path; - rfb.connect(url, { repeaterID: WebUtil.getConfigVar('repeaterID', ''), - shared: WebUtil.getConfigVar('shared', true), - credentials: { password: password } }); + 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.onnotification = notification; + rfb.onupdatestate = updateState; + rfb.ondisconnected = disconnected; + rfb.oncapabilities = function () { updatePowerButtons(); initialResize(); }; + rfb.oncredentialsrequired = credentials; + rfb.ondesktopname = updateDesktopName; })(); From 65fdfeae13c773683020d3152231e9074d35cd40 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Oct 2017 09:30:47 +0200 Subject: [PATCH 32/34] Remove unused options argument in onnotification event handler --- app/ui.js | 2 +- core/rfb.js | 9 ++------- docs/API.md | 4 +--- vnc_lite.html | 2 +- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/app/ui.js b/app/ui.js index 52fcf161..1202fe26 100644 --- a/app/ui.js +++ b/app/ui.js @@ -534,7 +534,7 @@ var UI = { document.getElementById('noVNC_status').classList.remove("noVNC_open"); }, - notification: function (rfb, msg, level, options) { + notification: function (rfb, msg, level) { UI.showStatus(msg, level); }, diff --git a/core/rfb.js b/core/rfb.js index 28e1a4a4..55d20995 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -588,11 +588,10 @@ RFB.prototype = { * 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': @@ -604,11 +603,7 @@ RFB.prototype = { return; } - if (options) { - this.onnotification(this, msg, level, options); - } else { - this.onnotification(this, msg, level); - } + this.onnotification(this, msg, level); }, _setCapability: function (cap, val) { diff --git a/docs/API.md b/docs/API.md index 8627f6c9..519d3bfe 100644 --- a/docs/API.md +++ b/docs/API.md @@ -206,11 +206,9 @@ severity of the message. The following levels are currently defined: - `"warn"` - `"error"` -**`options`** is currently unused. - ##### Syntax - RFB.onnotification = function(rfb, msg, level, options) { ... } + RFB.onnotification = function(rfb, msg, level) { ... } #### RFB.ondisconnected diff --git a/vnc_lite.html b/vnc_lite.html index f457423b..1a952f1b 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -189,7 +189,7 @@ status(reason, "error"); } } - function notification(rfb, msg, level, options) { + function notification(rfb, msg, level) { status(msg, level); } From e89eef94aaec7e64d279f76bcb1fbca75ce26358 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 27 Oct 2017 13:22:36 +0200 Subject: [PATCH 33/34] Use standard EventTarget interface for events --- app/ui.js | 50 +++++------ core/rfb.js | 60 +++++++------ core/util/eventtarget.js | 40 +++++++++ core/util/polyfill.js | 54 ++++++++++++ docs/API.md | 169 ++++++++++++++++--------------------- tests/test.rfb.js | 178 ++++++++++++++++++++------------------- vnc_lite.html | 36 ++++---- 7 files changed, 340 insertions(+), 247 deletions(-) create mode 100644 core/util/eventtarget.js create mode 100644 core/util/polyfill.js diff --git a/app/ui.js b/app/ui.js index 1202fe26..877a72e8 100644 --- a/app/ui.js +++ b/app/ui.js @@ -390,7 +390,7 @@ var UI = { * VISUAL * ------v------*/ - updateState: function(rfb, state, oldstate) { + updateState: function(event) { var msg; document.documentElement.classList.remove("noVNC_connecting"); @@ -398,7 +398,7 @@ var UI = { 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"); @@ -534,8 +534,8 @@ var UI = { document.getElementById('noVNC_status').classList.remove("noVNC_open"); }, - notification: function (rfb, msg, level) { - UI.showStatus(msg, level); + notification: function (e) { + UI.showStatus(e.detail.message, e.detail.level); }, activateControlbar: function(event) { @@ -966,9 +966,9 @@ var UI = { } }, - 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"); }, @@ -1040,15 +1040,15 @@ var UI = { { shared: UI.getSetting('shared'), repeaterID: UI.getSetting('repeaterID'), credentials: { password: password } }); - UI.rfb.onnotification = UI.notification; - UI.rfb.onupdatestate = UI.updateState; - UI.rfb.ondisconnected = UI.disconnectFinished; - UI.rfb.oncredentialsrequired = UI.credentials; - UI.rfb.oncapabilities = function () { UI.updatePowerButton(); UI.initialResize(); }; - UI.rfb.onclipboard = UI.clipboardReceive; - UI.rfb.onbell = UI.bell; - UI.rfb.onfbresize = UI.updateSessionSize; - UI.rfb.ondesktopname = UI.updateDesktopName; + 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() { @@ -1072,9 +1072,9 @@ var UI = { 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"); @@ -1105,7 +1105,7 @@ var UI = { * PASSWORD * ------v------*/ - credentials: function(rfb, types) { + credentials: function(e) { // FIXME: handle more types document.getElementById('noVNC_password_dlg') .classList.add('noVNC_open'); @@ -1656,7 +1656,7 @@ var UI = { WebUtil.init_logging(UI.getSetting('logging')); }, - updateSessionSize: function(rfb, width, height) { + updateSessionSize: function(e) { UI.updateViewClip(); UI.updateScaling(); UI.fixScrollbars(); @@ -1674,13 +1674,13 @@ var UI = { 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 --git a/core/rfb.js b/core/rfb.js index 55d20995..a3546b00 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -14,6 +14,7 @@ import * as Log from './util/logging.js'; import _ from './util/localization.js'; import { decodeUTF8 } from './util/strings.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,6 +25,7 @@ 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 */ @@ -265,18 +267,6 @@ RFB.prototype = { get isClipped() { return this._display.isClipped; }, - // ===== EVENT HANDLERS ===== - - onupdatestate: function () {}, // onupdatestate(rfb, state, oldstate): connection state change - onnotification: function () {}, // onnotification(rfb, msg, level, options): notification for the UI - ondisconnected: function () {}, // ondisconnected(rfb, reason): disconnection finished - oncredentialsrequired: function () {}, // oncredentialsrequired(rfb, types): VNC credentials are required - onclipboard: function () {}, // onclipboard(rfb, text): RFB clipboard contents received - onbell: function () {}, // onbell(rfb): RFB Bell message received - onfbresize: function () {}, // onfbresize(rfb, width, height): frame buffer resized - ondesktopname: function () {}, // ondesktopname(rfb, name): desktop name received - oncapabilities: function () {}, // oncapabilities(rfb, caps): the supported capabilities has changed - // ===== PUBLIC METHODS ===== disconnect: function () { @@ -510,7 +500,8 @@ RFB.prototype = { // 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); @@ -526,14 +517,16 @@ RFB.prototype = { 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': @@ -603,12 +596,16 @@ RFB.prototype = { return; } - 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; - this.oncapabilities(this, this._capabilities); + var event = new CustomEvent("capabilities", + { detail: { capabilities: this._capabilities } }); + this.dispatchEvent(event); }, _handle_message: function () { @@ -818,7 +815,9 @@ RFB.prototype = { if (!this._rfb_credentials.username || !this._rfb_credentials.password || !this._rfb_credentials.target) { - this.oncredentialsrequired(this, ["username", "password", "target"]); + var event = new CustomEvent("credentialsrequired", + { detail: { types: ["username", "password", "target"] } }); + this.dispatchEvent(event); return false; } @@ -835,7 +834,9 @@ RFB.prototype = { if (this._sock.rQwait("auth challenge", 16)) { return false; } if (!this._rfb_credentials.password) { - this.oncredentialsrequired(this, ["password"]); + var event = new CustomEvent("credentialsrequired", + { detail: { types: ["password"] } }); + this.dispatchEvent(event); return false; } @@ -1072,7 +1073,9 @@ RFB.prototype = { } // 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); @@ -1189,7 +1192,9 @@ RFB.prototype = { if (this._viewOnly) { return true; } - this.onclipboard(this, text); + var event = new CustomEvent("clipboard", + { detail: { text: text } }); + this.dispatchEvent(event); return true; }, @@ -1285,7 +1290,8 @@ RFB.prototype = { case 2: // Bell Log.Debug("Bell"); - this.onbell(this); + var event = new CustomEvent("bell", { detail: {} }); + this.dispatchEvent(event); return true; case 3: // ServerCutText @@ -1437,7 +1443,11 @@ RFB.prototype = { 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(); @@ -1450,6 +1460,8 @@ RFB.prototype = { }, }; +Object.assign(RFB.prototype, EventTargetMixin); + // Class Methods RFB.messages = { keyEvent: function (sock, keysym, down) { diff --git a/core/util/eventtarget.js b/core/util/eventtarget.js new file mode 100644 index 00000000..61bc7a1c --- /dev/null +++ b/core/util/eventtarget.js @@ -0,0 +1,40 @@ +/* + * noVNC: HTML5 VNC client + * Copyright 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +var EventTargetMixin = { + _listeners: null, + + addEventListener: function(type, callback) { + if (!this._listeners) { + this._listeners = new Map(); + } + if (!this._listeners.has(type)) { + this._listeners.set(type, new Set()); + } + this._listeners.get(type).add(callback); + }, + + removeEventListener: function(type, callback) { + if (!this._listeners || !this._listeners.has(type)) { + return; + } + this._listeners.get(type).delete(callback); + }, + + dispatchEvent: function(event) { + if (!this._listeners || !this._listeners.has(event.type)) { + return true; + } + this._listeners.get(event.type).forEach(function (callback) { + callback.call(this, event); + }, this); + return !event.defaultPrevented; + }, +}; + +export default EventTargetMixin; diff --git a/core/util/polyfill.js b/core/util/polyfill.js new file mode 100644 index 00000000..8c600e6f --- /dev/null +++ b/core/util/polyfill.js @@ -0,0 +1,54 @@ +/* + * noVNC: HTML5 VNC client + * Copyright 2017 Pierre Ossman for noVNC + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +/* Polyfills to provide new APIs in old browsers */ + +/* Object.assign() (taken from MDN) */ +if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, "assign", { + value: function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + writable: true, + configurable: true + }); +} + +/* CustomEvent constructor (taken from MDN) */ +(function () { + function CustomEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + + CustomEvent.prototype = window.Event.prototype; + + if (typeof window.CustomEvent !== "function") { + window.CustomEvent = CustomEvent; + } +})(); diff --git a/docs/API.md b/docs/API.md index 519d3bfe..983d7357 100644 --- a/docs/API.md +++ b/docs/API.md @@ -56,39 +56,41 @@ protocol stream. | `power` | `boolean` | Machine power control is available | `resize` | `boolean` | The framebuffer can be resized -### Event handlers +### Events -[`RFB.onupdatestate()`](#rfbonupdatestate) - - An event handler called when the connection state of the `RFB` - object changes. +[`updatestate`](#updatestate) + - The `updatestate` event is fired when the connection state of the + `RFB` object changes. -[`RFB.onnotification()`](#rfbonnotification) - - An event handler called when the `RFB` usage has a message to - display to the user. +[`notification`](#notification) + - The `notification` event is fired when the `RFB` usage has a + message to display to the user. -[`RFB.ondisconnected()`](#rfbondisconnected) - - An event handler called when the `RFB` object disconnects. +[`disconnect`](#disconnected) + - The `disconnect` event is fired when the `RFB` object disconnects. -[`RFB.oncredentialsrequired()`](#rfboncredentialsrequired) - - An event hander called when more credentials must be given to - continue. +[`credentialsrequired`](#credentialsrequired) + - The `credentialsrequired` event is fired when more credentials must + be given to continue. -[`RFB.onclipboard()`](#rfbonclipboard) - - An event handler called when clipboard data is received from the - server. +[`clipboard`](#clipboard) + - The `clipboard` event is fired when clipboard data is received from + the server. -[`RFB.onbell()`](#rfbonbell) - - An event handler called when a audible bell request is received +[`bell`](#bell) + - The `bell` event is fired when a audible bell request is received from the server. -[`RFB.onfbresize()`](#rfbonfbresize) - - An event handler called when the framebuffer size is changed. +[`fbresize`](#fbresize) + - The `fbresize` event is fired when the framebuffer size is changed. -[`RFB.ondesktopname()`](#rfbondesktopname) - - An event handler called when the remote desktop name changes. +[`desktopname`](#desktopname) + - The `desktopname` event is fired when the remote desktop name + changes. -[`RFB.oncapabilities()`](#rfboncapabilities) - - An event handler called when `RFB.capabilities` is updated. +[`capabilities`](#capabilities) + - The `capabilities` event is fired when `RFB.capabilities` is + updated. ### Methods @@ -96,9 +98,8 @@ protocol stream. - Disconnect from the server. [`RFB.sendCredentials()`](#rfbsendcredentials) - - Send credentials to server. Should be called after - [`oncredentialsrequired`](#rfboncredentialsrequired) has been - called. + - Send credentials to server. Should be called after the + [`credentialsrequired`](#credentialsrequired) event has fired. [`RFB.sendKey()`](#rfbsendKey) - Send a key event. @@ -175,10 +176,13 @@ connection to a specified VNC server. - A `DOMString` specifying the ID to provide to any VNC repeater encountered. -#### RFB.onupdatestate +#### updatestate -The `onupdatestate` event handler is fired after the noVNC connection -state changes. Here is a list of the states that are reported: +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 | ----------------- | ------------ @@ -191,91 +195,66 @@ 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. -##### Syntax +#### notification - RFB.onupdatestate = function(rfb, state) { ... } +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: -#### RFB.onnotification +| Property | Type | Description +| --------- | ----------- | ----------- +| `message` | `DOMString` | The message to display +| `level` | `DOMString` | The severity of the message -The `onnotification` event handler is fired when the `RFB` object wants -a message displayed to the user. **`msg`** is a `DOMString` specifying -the actual message, and **`level`** is a `DOMString` indicating the -severity of the message. The following levels are currently defined: +The following levels are currently defined: - `"normal"` - `"warn"` - `"error"` -##### Syntax +#### disconnect - RFB.onnotification = function(rfb, msg, level) { ... } +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. -#### RFB.ondisconnected +#### credentialsrequired -The `ondisconnected` event handler is fired when the connection has -been terminated. **`reason`** is `undefined` for a clean termination -and a `DOMString` specifying the reason in the event of an unexpected -termination. +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. -##### Syntax +#### clipboard - RFB.ondisconnected = function(rfb, reason) { ... } +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. -#### RFB.oncredentialsrequired +#### bell -The `oncredentialsrequired` event handler is fired when the server -requests more credentials than were specified to [`RFB()`](#rfb-1). The -**`types`** argument is a list of all the credentials that are -required. +The `bell` event is fired when the server has requested an audible +bell. -##### Syntax +#### fbresize - RFB.oncredentialsrequired = function(rfb, types) { ... } +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. -#### RFB.onclipboard +#### desktopname -The `onclipboard` event handler is fired when the server has sent -clipboard data. +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. -##### Syntax +#### capabilities - RFB.onclipboard = function(rfb, text) { ... } - -#### RFB.onbell - -The `onbell` event handler is fired when the server has requested an -audible bell. - -##### Syntax - - RFB.onbell = function(rfb) { ... } - -#### RFB.onfbresize - -The `onfbresize` event handler is fired when the framebuffer has -changed dimensions. - -##### Syntax - - RFB.onfbresize = function(rfb, width, height) { ... } - -#### RFB.ondesktopname - -The `ondesktopname` event handler is fired when the name of the remote -desktop changes. - -##### Syntax - - RFB.ondesktopname = function(rfb, name) { ... } - -#### RFB.oncapabilities - -The `oncapabilities` event handler is fired whenever an entry is added -or removed from `RFB.capabilities`. - -##### Syntax - - RFB.oncapabilities = function(rfb, capabilites) { ... } +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() @@ -289,7 +268,7 @@ connected server. #### RFB.sendCredentials() The `RFB.sendCredentials()` method is used to provide the missing -credentials after `RFB.oncredentialsrequired` has been fired. +credentials after a `credentialsrequired` event has been fired. ##### Syntax @@ -405,7 +384,7 @@ 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. -[`RFB.onfbresize`](#rfbonfbresize) will be called when the framebuffer +The [`fbresize`](#fbresize) event will be fired when the framebuffer actually changes dimensions. ##### Syntax diff --git a/tests/test.rfb.js b/tests/test.rfb.js index c218ae07..4dbe1840 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -68,10 +68,11 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('#RFB', function () { it('should set the current state to "connecting"', function () { var client = new RFB(document.createElement('canvas'), 'wss://host:8675'); - client.onupdatestate = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); this.clock.tick(); - expect(client.onupdatestate).to.have.been.calledOnce; - expect(client.onupdatestate).to.have.been.calledWith(client, 'connecting'); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.state).to.equal('connecting'); }); it('should actually connect to the websocket', function () { @@ -90,11 +91,12 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should set the current state to "disconnecting"', function () { - client.onupdatestate = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client.disconnect(); - expect(client.onupdatestate).to.have.been.calledTwice; - expect(client.onupdatestate).to.have.been.calledWith(client, 'disconnecting'); - expect(client.onupdatestate).to.have.been.calledWith(client, 'disconnected'); + expect(spy).to.have.been.calledTwice; + expect(spy.args[0][0].detail.state).to.equal('disconnecting'); + expect(spy.args[1][0].detail.state).to.equal('disconnected'); }); it('should unregister error event handler', function () { @@ -319,10 +321,10 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the updateState callback', function () { - client.onupdatestate = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client._updateConnectionState('disconnecting'); - var spy = client.onupdatestate; - expect(spy.args[0][1]).to.equal('disconnecting'); + expect(spy.args[0][0].detail.state).to.equal('disconnecting'); }); it('should set the rfb_connection_state', function () { @@ -338,19 +340,19 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should ignore state changes to the same state', function () { - client.onupdatestate = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client._rfb_connection_state = 'connecting'; client._updateConnectionState('connecting'); - var spy = client.onupdatestate; expect(spy).to.not.have.been.called; }); it('should ignore illegal state changes', function () { - client.onupdatestate = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client._rfb_connection_state = 'connected'; client._updateConnectionState('disconnected'); expect(client._rfb_connection_state).to.not.equal('disconnected'); - var spy = client.onupdatestate; expect(spy).to.not.have.been.called; }); }); @@ -389,13 +391,12 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should result in disconnect callback with message when reason given', function () { client._rfb_connection_state = 'connected'; - client.ondisconnected = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._fail('a reason'); - var spy = client.ondisconnected; this.clock.tick(2000); expect(spy).to.have.been.calledOnce; - expect(spy.args[0].length).to.equal(2); - expect(spy.args[0][1]).to.equal('a reason'); + expect(spy.args[0][0].detail.reason).to.equal('a reason'); }); }); @@ -405,18 +406,18 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); }); it('should call the notification callback', function () { - client.onnotification = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("notification", spy); client._notification('notify!', 'warn'); - var spy = client.onnotification; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('notify!'); - expect(spy.args[0][2]).to.equal('warn'); + expect(spy.args[0][0].detail.message).to.equal('notify!'); + expect(spy.args[0][0].detail.level).to.equal('warn'); }); it('should not call the notification callback when level is invalid', function () { - client.onnotification = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("notification", spy); client._notification('notify!', 'invalid'); - var spy = client.onnotification; expect(spy).to.not.have.been.called; }); }); @@ -459,40 +460,40 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); }); it('should call the disconnect callback if the state is "disconnected"', function () { - client.ondisconnected = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._rfb_connection_state = 'disconnecting'; client._rfb_disconnect_reason = "error"; client._updateConnectionState('disconnected'); - var spy = client.ondisconnected; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal("error"); + expect(spy.args[0][0].detail.reason).to.equal("error"); }); it('should not call the disconnect callback if the state is not "disconnected"', function () { - client.ondisconnected = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._sock._websocket.close = function () {}; // explicitly don't call onclose client._updateConnectionState('disconnecting'); - var spy = client.ondisconnected; expect(spy).to.not.have.been.called; }); it('should call the disconnect callback without msg when no reason given', function () { - client.ondisconnected = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._rfb_connection_state = 'disconnecting'; client._rfb_disconnect_reason = ""; client._updateConnectionState('disconnected'); - var spy = client.ondisconnected; expect(spy).to.have.been.calledOnce; expect(spy.args[0].length).to.equal(1); }); it('should call the updateState callback before the disconnect callback', function () { - client.ondisconnected = sinon.spy(); - client.onupdatestate = sinon.spy(); + var updateStateSpy = sinon.spy(); + var disconnectSpy = sinon.spy(); + client.addEventListener("disconnect", disconnectSpy); + client.addEventListener("updatestate", updateStateSpy); client._rfb_connection_state = 'disconnecting'; client._updateConnectionState('disconnected'); - var updateStateSpy = client.onupdatestate; - var disconnectSpy = client.ondisconnected; expect(updateStateSpy.calledBefore(disconnectSpy)).to.be.true; }); }); @@ -715,18 +716,18 @@ describe('Remote Frame Buffer Protocol Client', function() { client._rfb_version = 3.8; }); - it('should call the onCredentialsRequired callback if missing a password', function () { - client.oncredentialsrequired = sinon.spy(); + it('should fire the credentialsrequired event if missing a password', function () { + var spy = sinon.spy(); + client.addEventListener("credentialsrequired", spy); send_security(2, client); var challenge = []; for (var i = 0; i < 16; i++) { challenge[i] = i; } client._sock._websocket._receive_data(new Uint8Array(challenge)); - var spy = client.oncredentialsrequired; expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.have.members(["password"]); + expect(spy.args[0][0].detail.types).to.have.members(["password"]); }); it('should encrypt the password with DES and then send it back', function () { @@ -769,26 +770,26 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; }); - it('should call the onCredentialsRequired callback if all credentials are missing', function() { - client.oncredentialsrequired = sinon.spy(); + it('should fire the credentialsrequired event if all credentials are missing', function() { + var spy = sinon.spy(); + client.addEventListener("credentialsrequired", spy); client._rfb_credentials = {}; send_security(22, client); - var spy = client.oncredentialsrequired; expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.have.members(["username", "password", "target"]); + expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]); }); - it('should call the onCredentialsRequired callback if some credentials are missing', function() { - client.oncredentialsrequired = sinon.spy(); + it('should fire the credentialsrequired event if some credentials are missing', function() { + var spy = sinon.spy(); + client.addEventListener("credentialsrequired", spy); client._rfb_credentials = { username: 'user', target: 'target' }; send_security(22, client); - var spy = client.oncredentialsrequired; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.have.members(["username", "password", "target"]); + expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]); }); it('should send user and target separately', function () { @@ -998,13 +999,13 @@ describe('Remote Frame Buffer Protocol Client', function() { // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them it('should set the framebuffer name and call the callback', function () { - client.ondesktopname = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("desktopname", spy); send_server_init({ name: 'some name' }, client); - var spy = client.ondesktopname; expect(client._fb_name).to.equal('some name'); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('some name'); + expect(spy.args[0][0].detail.name).to.equal('some name'); }); it('should handle the extended init message of the tight encoding', function () { @@ -1027,16 +1028,16 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the resize callback and resize the display', function () { - client.onfbresize = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("fbresize", spy); sinon.spy(client._display, 'resize'); send_server_init({ width: 27, height: 32 }, client); - var spy = client.onfbresize; expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(27, 32); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal(27); - expect(spy.args[0][2]).to.equal(32); + expect(spy.args[0][0].detail.width).to.equal(27); + expect(spy.args[0][0].detail.height).to.equal(32); }); it('should grab the mouse and keyboard', function () { @@ -1436,13 +1437,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle the DesktopSize pseduo-encoding', function () { - client.onfbresize = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("fbresize", spy); sinon.spy(client._display, 'resize'); send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client); - var spy = client.onfbresize; expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + expect(spy.args[0][0].detail.width).to.equal(20); + expect(spy.args[0][0].detail.height).to.equal(50); expect(client._fb_width).to.equal(20); expect(client._fb_height).to.equal(50); @@ -1452,6 +1454,8 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('the ExtendedDesktopSize pseudo-encoding handler', function () { + var resizeSpy; + beforeEach(function () { client._supportsSetDesktopSize = false; // a really small frame @@ -1459,7 +1463,8 @@ describe('Remote Frame Buffer Protocol Client', function() { client._fb_height = 4; client._display.resize(4, 4); sinon.spy(client._display, 'resize'); - client.onfbresize = sinon.spy(); + resizeSpy = sinon.spy(); + client.addEventListener("fbresize", resizeSpy); }); function make_screen_data (nr_of_screens) { @@ -1479,7 +1484,8 @@ describe('Remote Frame Buffer Protocol Client', function() { } it('should call callback when resize is supported', function () { - client.oncapabilities = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("capabilities", spy); expect(client._supportsSetDesktopSize).to.be.false; expect(client.capabilities.resize).to.be.false; @@ -1492,8 +1498,8 @@ describe('Remote Frame Buffer Protocol Client', function() { make_screen_data(1), client); expect(client._supportsSetDesktopSize).to.be.true; - expect(client.oncapabilities).to.have.been.calledOnce; - expect(client.oncapabilities.args[0][1].resize).to.be.true; + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.capabilities.resize).to.be.true; expect(client.capabilities.resize).to.be.true; }), @@ -1511,9 +1517,9 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(20, 50); - var spy = client.onfbresize; - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + expect(resizeSpy).to.have.been.calledOnce; + expect(resizeSpy.args[0][0].detail.width).to.equal(20); + expect(resizeSpy.args[0][0].detail.height).to.equal(50); }); it('should handle a resize requested by another client', function () { @@ -1530,9 +1536,9 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(20, 50); - var spy = client.onfbresize; - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + expect(resizeSpy).to.have.been.calledOnce; + expect(resizeSpy.args[0][0].detail.width).to.equal(20); + expect(resizeSpy.args[0][0].detail.height).to.equal(50); }); it('should be able to recieve requests which contain data for multiple screens', function () { @@ -1549,9 +1555,9 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(60, 50); - var spy = client.onfbresize; - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50); + expect(resizeSpy).to.have.been.calledOnce; + expect(resizeSpy.args[0][0].detail.width).to.equal(60); + expect(resizeSpy.args[0][0].detail.height).to.equal(50); }); it('should not handle a failed request', function () { @@ -1567,8 +1573,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.not.have.been.called; - var spy = client.onfbresize; - expect(spy).to.not.have.been.called; + expect(resizeSpy).to.not.have.been.called; }); }); @@ -1585,19 +1590,20 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('XVP Message Handling', function () { it('should send a notification on XVP_FAIL', function () { - client.onnotification = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("notification", spy); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0])); - var spy = client.onnotification; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('XVP Operation Failed'); + expect(spy.args[0][0].detail.message).to.equal('XVP Operation Failed'); }); it('should set the XVP version and fire the callback with the version on XVP_INIT', function () { - client.oncapabilities = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("capabilities", spy); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1])); expect(client._rfb_xvp_ver).to.equal(10); - expect(client.oncapabilities).to.have.been.calledOnce; - expect(client.oncapabilities.args[0][1].power).to.be.true; + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.capabilities.power).to.be.true; expect(client.capabilities.power).to.be.true; }); @@ -1613,18 +1619,19 @@ describe('Remote Frame Buffer Protocol Client', function() { var data = [3, 0, 0, 0]; push32(data, expected_str.length); for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); } - client.onclipboard = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("clipboard", spy); client._sock._websocket._receive_data(new Uint8Array(data)); - var spy = client.onclipboard; expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal(expected_str); + expect(spy.args[0][0].detail.text).to.equal(expected_str); }); it('should fire the bell callback on Bell', function () { - client.onbell = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("bell", spy); client._sock._websocket._receive_data(new Uint8Array([2])); - expect(client.onbell).to.have.been.calledOnce; + expect(spy).to.have.been.calledOnce; }); it('should respond correctly to ServerFence', function () { @@ -1850,9 +1857,10 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should process all normal messages directly', function () { - client.onbell = sinon.spy(); + var spy = sinon.spy(); + client.addEventListener("bell", spy); client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02])); - expect(client.onbell).to.have.been.calledTwice; + expect(spy).to.have.been.calledTwice; }); // open events diff --git a/vnc_lite.html b/vnc_lite.html index 1a952f1b..6575b463 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -98,10 +98,10 @@ UIresize(); doneInitialResize = true; } - function updateDesktopName(rfb, name) { - desktopName = name; + function updateDesktopName(e) { + desktopName = e.detail.name; } - function credentials(rfb, types) { + function credentials(e) { var html; var form = document.createElement('form'); @@ -148,9 +148,9 @@ document.getElementById('noVNC_status_bar').setAttribute("class", "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; @@ -172,11 +172,11 @@ 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; @@ -184,13 +184,13 @@ } } - 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) { - status(msg, level); + function notification(e) { + status(e.detail.message, e.detail.level); } window.onresize = function () { @@ -275,12 +275,12 @@ shared: WebUtil.getConfigVar('shared', true), credentials: { password: password } }); rfb.viewOnly = WebUtil.getConfigVar('view_only', false); - rfb.onnotification = notification; - rfb.onupdatestate = updateState; - rfb.ondisconnected = disconnected; - rfb.oncapabilities = function () { updatePowerButtons(); initialResize(); }; - rfb.oncredentialsrequired = credentials; - rfb.ondesktopname = updateDesktopName; + 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); })(); From a80b5fdaaf3dc9e50a8d7a982cd3f9627078986b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 30 Oct 2017 17:16:03 +0100 Subject: [PATCH 34/34] Prevent password form submission early Otherwise it might reload the page if we run in to some kind of error. --- app/ui.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/ui.js b/app/ui.js index 877a72e8..74735cce 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1120,6 +1120,9 @@ var UI = { }, 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 @@ -1128,8 +1131,6 @@ var UI = { UI.reconnect_password = password; document.getElementById('noVNC_password_dlg') .classList.remove('noVNC_open'); - // Prevent actually submitting the form - e.preventDefault(); }, /* ------^-------