@novnc/novnc 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/AUTHORS +13 -0
  2. package/LICENSE.txt +2 -1
  3. package/README.md +69 -3
  4. package/core/base64.js +35 -41
  5. package/core/decoders/copyrect.js +22 -0
  6. package/core/decoders/hextile.js +137 -0
  7. package/core/decoders/raw.js +56 -0
  8. package/core/decoders/rre.js +44 -0
  9. package/core/decoders/tight.js +315 -0
  10. package/core/decoders/tightpng.js +27 -0
  11. package/core/deflator.js +85 -0
  12. package/core/des.js +90 -95
  13. package/core/display.js +254 -297
  14. package/core/encodings.js +7 -3
  15. package/core/inflator.js +48 -20
  16. package/core/input/domkeytable.js +21 -24
  17. package/core/input/fixedkeys.js +3 -1
  18. package/core/input/gesturehandler.js +567 -0
  19. package/core/input/keyboard.js +194 -120
  20. package/core/input/keysym.js +2 -0
  21. package/core/input/keysymdef.js +3 -3
  22. package/core/input/util.js +53 -12
  23. package/core/input/vkeys.js +2 -1
  24. package/core/rfb.js +1937 -1496
  25. package/core/util/browser.js +80 -29
  26. package/core/util/cursor.js +253 -0
  27. package/core/util/element.js +32 -0
  28. package/core/util/events.js +59 -55
  29. package/core/util/eventtarget.js +25 -30
  30. package/core/util/int.js +15 -0
  31. package/core/util/logging.js +21 -16
  32. package/core/util/polyfill.js +15 -8
  33. package/core/util/strings.js +21 -8
  34. package/core/websock.js +145 -167
  35. package/docs/API.md +31 -10
  36. package/lib/base64.js +115 -0
  37. package/lib/decoders/copyrect.js +44 -0
  38. package/lib/decoders/hextile.js +173 -0
  39. package/lib/decoders/raw.js +78 -0
  40. package/lib/decoders/rre.js +65 -0
  41. package/lib/decoders/tight.js +350 -0
  42. package/lib/decoders/tightpng.js +67 -0
  43. package/lib/deflator.js +99 -0
  44. package/lib/des.js +314 -0
  45. package/lib/display.js +733 -0
  46. package/lib/encodings.js +64 -0
  47. package/lib/inflator.js +87 -0
  48. package/lib/input/domkeytable.js +282 -0
  49. package/lib/input/fixedkeys.js +123 -0
  50. package/lib/input/gesturehandler.js +642 -0
  51. package/lib/input/keyboard.js +429 -0
  52. package/lib/input/keysym.js +1135 -0
  53. package/lib/input/keysymdef.js +1354 -0
  54. package/lib/input/util.js +304 -0
  55. package/lib/input/vkeys.js +127 -0
  56. package/lib/input/xtscancodes.js +505 -0
  57. package/lib/rfb.js +3448 -0
  58. package/lib/util/browser.js +131 -0
  59. package/lib/util/cursor.js +314 -0
  60. package/lib/util/element.js +43 -0
  61. package/lib/util/events.js +142 -0
  62. package/lib/util/eventtarget.js +64 -0
  63. package/lib/util/int.js +22 -0
  64. package/lib/util/logging.js +79 -0
  65. package/lib/util/polyfill.js +72 -0
  66. package/lib/util/strings.js +38 -0
  67. package/lib/vendor/pako/lib/utils/common.js +67 -0
  68. package/lib/vendor/pako/lib/zlib/adler32.js +33 -0
  69. package/lib/vendor/pako/lib/zlib/constants.js +51 -0
  70. package/lib/vendor/pako/lib/zlib/crc32.js +42 -0
  71. package/lib/vendor/pako/lib/zlib/deflate.js +2159 -0
  72. package/lib/vendor/pako/lib/zlib/gzheader.js +53 -0
  73. package/lib/vendor/pako/lib/zlib/inffast.js +445 -0
  74. package/lib/vendor/pako/lib/zlib/inflate.js +2114 -0
  75. package/lib/vendor/pako/lib/zlib/inftrees.js +418 -0
  76. package/lib/vendor/pako/lib/zlib/messages.js +36 -0
  77. package/lib/vendor/pako/lib/zlib/trees.js +1499 -0
  78. package/lib/vendor/pako/lib/zlib/zstream.js +46 -0
  79. package/lib/vendor/promise.js +255 -0
  80. package/lib/websock.js +374 -0
  81. package/package.json +48 -28
  82. package/vendor/pako/lib/zlib/deflate.js +30 -30
  83. package/vendor/pako/lib/zlib/inflate.js +17 -17
  84. package/core/input/mouse.js +0 -280
  85. package/docs/API-internal.md +0 -125
  86. package/docs/EMBEDDING.md +0 -83
  87. package/docs/VERSION +0 -1
package/core/rfb.js CHANGED
@@ -1,294 +1,340 @@
1
1
  /*
2
2
  * noVNC: HTML5 VNC client
3
- * Copyright (C) 2012 Joel Martin
4
- * Copyright (C) 2017 Samuel Mannehed for Cendio AB
3
+ * Copyright (C) 2020 The noVNC Authors
5
4
  * Licensed under MPL 2.0 (see LICENSE.txt)
6
5
  *
7
6
  * See README.md for usage and integration instructions.
8
7
  *
9
- * TIGHT decoder portion:
10
- * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
11
8
  */
12
9
 
10
+ import { toUnsigned32bit, toSigned32bit } from './util/int.js';
13
11
  import * as Log from './util/logging.js';
14
- import { decodeUTF8 } from './util/strings.js';
15
- import { supportsCursorURIs, isTouchDevice } from './util/browser.js';
12
+ import { encodeUTF8, decodeUTF8 } from './util/strings.js';
13
+ import { dragThreshold } from './util/browser.js';
14
+ import { clientToElement } from './util/element.js';
15
+ import { setCapture } from './util/events.js';
16
16
  import EventTargetMixin from './util/eventtarget.js';
17
17
  import Display from "./display.js";
18
+ import Inflator from "./inflator.js";
19
+ import Deflator from "./deflator.js";
18
20
  import Keyboard from "./input/keyboard.js";
19
- import Mouse from "./input/mouse.js";
21
+ import GestureHandler from "./input/gesturehandler.js";
22
+ import Cursor from "./util/cursor.js";
20
23
  import Websock from "./websock.js";
21
24
  import DES from "./des.js";
22
25
  import KeyTable from "./input/keysym.js";
23
26
  import XtScancode from "./input/xtscancodes.js";
24
- import Inflator from "./inflator.js";
25
- import { encodings, encodingName } from "./encodings.js";
27
+ import { encodings } from "./encodings.js";
26
28
  import "./util/polyfill.js";
27
29
 
28
- /*jslint white: false, browser: true */
29
- /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
30
+ import RawDecoder from "./decoders/raw.js";
31
+ import CopyRectDecoder from "./decoders/copyrect.js";
32
+ import RREDecoder from "./decoders/rre.js";
33
+ import HextileDecoder from "./decoders/hextile.js";
34
+ import TightDecoder from "./decoders/tight.js";
35
+ import TightPNGDecoder from "./decoders/tightpng.js";
30
36
 
31
37
  // How many seconds to wait for a disconnect to finish
32
- var DISCONNECT_TIMEOUT = 3;
33
-
34
- export default function RFB(target, url, options) {
35
- if (!target) {
36
- throw Error("Must specify target");
37
- }
38
- if (!url) {
39
- throw Error("Must specify URL");
40
- }
41
-
42
- this._target = target;
43
- this._url = url;
44
-
45
- // Connection details
46
- options = options || {};
47
- this._rfb_credentials = options.credentials || {};
48
- this._shared = 'shared' in options ? !!options.shared : true;
49
- this._repeaterID = options.repeaterID || '';
50
-
51
- // Internal state
52
- this._rfb_connection_state = '';
53
- this._rfb_init_state = '';
54
- this._rfb_auth_scheme = '';
55
- this._rfb_clean_disconnect = true;
56
-
57
- // Server capabilities
58
- this._rfb_version = 0;
59
- this._rfb_max_version = 3.8;
60
- this._rfb_tightvnc = false;
61
- this._rfb_xvp_ver = 0;
62
-
63
- this._fb_width = 0;
64
- this._fb_height = 0;
65
-
66
- this._fb_name = "";
67
-
68
- this._capabilities = { power: false };
69
-
70
- this._supportsFence = false;
71
-
72
- this._supportsContinuousUpdates = false;
73
- this._enabledContinuousUpdates = false;
74
-
75
- this._supportsSetDesktopSize = false;
76
- this._screen_id = 0;
77
- this._screen_flags = 0;
78
-
79
- this._qemuExtKeyEventSupported = false;
80
-
81
- // Internal objects
82
- this._sock = null; // Websock object
83
- this._display = null; // Display object
84
- this._flushing = false; // Display flushing state
85
- this._keyboard = null; // Keyboard input handler object
86
- this._mouse = null; // Mouse input handler object
87
-
88
- // Timers
89
- this._disconnTimer = null; // disconnection timer
90
- this._resizeTimeout = null; // resize rate limiting
91
-
92
- // Decoder states and stats
93
- this._encHandlers = {};
94
- this._encStats = {};
95
-
96
- this._FBU = {
97
- rects: 0,
98
- subrects: 0, // RRE and HEXTILE
99
- lines: 0, // RAW
100
- tiles: 0, // HEXTILE
101
- bytes: 0,
102
- x: 0,
103
- y: 0,
104
- width: 0,
105
- height: 0,
106
- encoding: 0,
107
- subencoding: -1,
108
- background: null,
109
- zlibs: [] // TIGHT zlib streams
110
- };
111
- for (var i = 0; i < 4; i++) {
112
- this._FBU.zlibs[i] = new Inflator();
113
- }
114
-
115
- this._destBuff = null;
116
- this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
117
-
118
- this._rre_chunk_sz = 100;
119
-
120
- this._timing = {
121
- last_fbu: 0,
122
- fbu_total: 0,
123
- fbu_total_cnt: 0,
124
- full_fbu_total: 0,
125
- full_fbu_cnt: 0,
126
-
127
- fbu_rt_start: 0,
128
- fbu_rt_total: 0,
129
- fbu_rt_cnt: 0,
130
- pixels: 0
131
- };
132
-
133
- // Mouse state
134
- this._mouse_buttonMask = 0;
135
- this._mouse_arr = [];
136
- this._viewportDragging = false;
137
- this._viewportDragPos = {};
138
- this._viewportHasMoved = false;
139
-
140
- // Bound event handlers
141
- this._eventHandlers = {
142
- focusCanvas: this._focusCanvas.bind(this),
143
- windowResize: this._windowResize.bind(this),
144
- };
145
-
146
- // main setup
147
- Log.Debug(">> RFB.constructor");
148
-
149
- // Create DOM elements
150
- this._screen = document.createElement('div');
151
- this._screen.style.display = 'flex';
152
- this._screen.style.width = '100%';
153
- this._screen.style.height = '100%';
154
- this._screen.style.overflow = 'auto';
155
- this._screen.style.backgroundColor = 'rgb(40, 40, 40)';
156
- this._canvas = document.createElement('canvas');
157
- this._canvas.style.margin = 'auto';
158
- // Some browsers add an outline on focus
159
- this._canvas.style.outline = 'none';
160
- // IE miscalculates width without this :(
161
- this._canvas.style.flexShrink = '0';
162
- this._canvas.width = 0;
163
- this._canvas.height = 0;
164
- this._canvas.tabIndex = -1;
165
- this._screen.appendChild(this._canvas);
166
-
167
- // populate encHandlers with bound versions
168
- this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
169
- this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
170
- this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this);
171
- this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this);
172
- this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this);
173
-
174
- this._encHandlers[encodings.pseudoEncodingDesktopSize] = RFB.encodingHandlers.DesktopSize.bind(this);
175
- this._encHandlers[encodings.pseudoEncodingLastRect] = RFB.encodingHandlers.last_rect.bind(this);
176
- this._encHandlers[encodings.pseudoEncodingCursor] = RFB.encodingHandlers.Cursor.bind(this);
177
- this._encHandlers[encodings.pseudoEncodingQEMUExtendedKeyEvent] = RFB.encodingHandlers.QEMUExtendedKeyEvent.bind(this);
178
- this._encHandlers[encodings.pseudoEncodingExtendedDesktopSize] = RFB.encodingHandlers.ExtendedDesktopSize.bind(this);
179
-
180
- // NB: nothing that needs explicit teardown should be done
181
- // before this point, since this can throw an exception
182
- try {
183
- this._display = new Display(this._canvas);
184
- } catch (exc) {
185
- Log.Error("Display exception: " + exc);
186
- throw exc;
187
- }
188
- this._display.onflush = this._onFlush.bind(this);
189
- this._display.clear();
190
-
191
- this._keyboard = new Keyboard(this._canvas);
192
- this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
193
-
194
- this._mouse = new Mouse(this._canvas);
195
- this._mouse.onmousebutton = this._handleMouseButton.bind(this);
196
- this._mouse.onmousemove = this._handleMouseMove.bind(this);
197
-
198
- this._sock = new Websock();
199
- this._sock.on('message', this._handle_message.bind(this));
200
- this._sock.on('open', function () {
201
- if ((this._rfb_connection_state === 'connecting') &&
202
- (this._rfb_init_state === '')) {
203
- this._rfb_init_state = 'ProtocolVersion';
204
- Log.Debug("Starting VNC handshake");
205
- } else {
206
- this._fail("Unexpected server connection while " +
207
- this._rfb_connection_state);
208
- }
209
- }.bind(this));
210
- this._sock.on('close', function (e) {
211
- Log.Debug("WebSocket on-close event");
212
- var msg = "";
213
- if (e.code) {
214
- msg = "(code: " + e.code;
215
- if (e.reason) {
216
- msg += ", reason: " + e.reason;
217
- }
218
- msg += ")";
219
- }
220
- switch (this._rfb_connection_state) {
221
- case 'connecting':
222
- this._fail("Connection closed " + msg);
223
- break;
224
- case 'connected':
225
- // Handle disconnects that were initiated server-side
226
- this._updateConnectionState('disconnecting');
227
- this._updateConnectionState('disconnected');
228
- break;
229
- case 'disconnecting':
230
- // Normal disconnection path
231
- this._updateConnectionState('disconnected');
232
- break;
233
- case 'disconnected':
234
- this._fail("Unexpected server disconnect " +
235
- "when already disconnected " + msg);
236
- break;
237
- default:
238
- this._fail("Unexpected server disconnect before connecting " +
239
- msg);
240
- break;
38
+ const DISCONNECT_TIMEOUT = 3;
39
+ const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
40
+
41
+ // Minimum wait (ms) between two mouse moves
42
+ const MOUSE_MOVE_DELAY = 17;
43
+
44
+ // Wheel thresholds
45
+ const WHEEL_STEP = 50; // Pixels needed for one step
46
+ const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
47
+
48
+ // Gesture thresholds
49
+ const GESTURE_ZOOMSENS = 75;
50
+ const GESTURE_SCRLSENS = 50;
51
+ const DOUBLE_TAP_TIMEOUT = 1000;
52
+ const DOUBLE_TAP_THRESHOLD = 50;
53
+
54
+ // Extended clipboard pseudo-encoding formats
55
+ const extendedClipboardFormatText = 1;
56
+ /*eslint-disable no-unused-vars */
57
+ const extendedClipboardFormatRtf = 1 << 1;
58
+ const extendedClipboardFormatHtml = 1 << 2;
59
+ const extendedClipboardFormatDib = 1 << 3;
60
+ const extendedClipboardFormatFiles = 1 << 4;
61
+ /*eslint-enable */
62
+
63
+ // Extended clipboard pseudo-encoding actions
64
+ const extendedClipboardActionCaps = 1 << 24;
65
+ const extendedClipboardActionRequest = 1 << 25;
66
+ const extendedClipboardActionPeek = 1 << 26;
67
+ const extendedClipboardActionNotify = 1 << 27;
68
+ const extendedClipboardActionProvide = 1 << 28;
69
+
70
+
71
+ export default class RFB extends EventTargetMixin {
72
+ constructor(target, url, options) {
73
+ if (!target) {
74
+ throw new Error("Must specify target");
75
+ }
76
+ if (!url) {
77
+ throw new Error("Must specify URL");
78
+ }
79
+
80
+ super();
81
+
82
+ this._target = target;
83
+ this._url = url;
84
+
85
+ // Connection details
86
+ options = options || {};
87
+ this._rfbCredentials = options.credentials || {};
88
+ this._shared = 'shared' in options ? !!options.shared : true;
89
+ this._repeaterID = options.repeaterID || '';
90
+ this._wsProtocols = options.wsProtocols || [];
91
+
92
+ // Internal state
93
+ this._rfbConnectionState = '';
94
+ this._rfbInitState = '';
95
+ this._rfbAuthScheme = -1;
96
+ this._rfbCleanDisconnect = true;
97
+
98
+ // Server capabilities
99
+ this._rfbVersion = 0;
100
+ this._rfbMaxVersion = 3.8;
101
+ this._rfbTightVNC = false;
102
+ this._rfbVeNCryptState = 0;
103
+ this._rfbXvpVer = 0;
104
+
105
+ this._fbWidth = 0;
106
+ this._fbHeight = 0;
107
+
108
+ this._fbName = "";
109
+
110
+ this._capabilities = { power: false };
111
+
112
+ this._supportsFence = false;
113
+
114
+ this._supportsContinuousUpdates = false;
115
+ this._enabledContinuousUpdates = false;
116
+
117
+ this._supportsSetDesktopSize = false;
118
+ this._screenID = 0;
119
+ this._screenFlags = 0;
120
+
121
+ this._qemuExtKeyEventSupported = false;
122
+
123
+ this._clipboardText = null;
124
+ this._clipboardServerCapabilitiesActions = {};
125
+ this._clipboardServerCapabilitiesFormats = {};
126
+
127
+ // Internal objects
128
+ this._sock = null; // Websock object
129
+ this._display = null; // Display object
130
+ this._flushing = false; // Display flushing state
131
+ this._keyboard = null; // Keyboard input handler object
132
+ this._gestures = null; // Gesture input handler object
133
+
134
+ // Timers
135
+ this._disconnTimer = null; // disconnection timer
136
+ this._resizeTimeout = null; // resize rate limiting
137
+ this._mouseMoveTimer = null;
138
+
139
+ // Decoder states
140
+ this._decoders = {};
141
+
142
+ this._FBU = {
143
+ rects: 0,
144
+ x: 0,
145
+ y: 0,
146
+ width: 0,
147
+ height: 0,
148
+ encoding: null,
149
+ };
150
+
151
+ // Mouse state
152
+ this._mousePos = {};
153
+ this._mouseButtonMask = 0;
154
+ this._mouseLastMoveTime = 0;
155
+ this._viewportDragging = false;
156
+ this._viewportDragPos = {};
157
+ this._viewportHasMoved = false;
158
+ this._accumulatedWheelDeltaX = 0;
159
+ this._accumulatedWheelDeltaY = 0;
160
+
161
+ // Gesture state
162
+ this._gestureLastTapTime = null;
163
+ this._gestureFirstDoubleTapEv = null;
164
+ this._gestureLastMagnitudeX = 0;
165
+ this._gestureLastMagnitudeY = 0;
166
+
167
+ // Bound event handlers
168
+ this._eventHandlers = {
169
+ focusCanvas: this._focusCanvas.bind(this),
170
+ windowResize: this._windowResize.bind(this),
171
+ handleMouse: this._handleMouse.bind(this),
172
+ handleWheel: this._handleWheel.bind(this),
173
+ handleGesture: this._handleGesture.bind(this),
174
+ };
175
+
176
+ // main setup
177
+ Log.Debug(">> RFB.constructor");
178
+
179
+ // Create DOM elements
180
+ this._screen = document.createElement('div');
181
+ this._screen.style.display = 'flex';
182
+ this._screen.style.width = '100%';
183
+ this._screen.style.height = '100%';
184
+ this._screen.style.overflow = 'auto';
185
+ this._screen.style.background = DEFAULT_BACKGROUND;
186
+ this._canvas = document.createElement('canvas');
187
+ this._canvas.style.margin = 'auto';
188
+ // Some browsers add an outline on focus
189
+ this._canvas.style.outline = 'none';
190
+ // IE miscalculates width without this :(
191
+ this._canvas.style.flexShrink = '0';
192
+ this._canvas.width = 0;
193
+ this._canvas.height = 0;
194
+ this._canvas.tabIndex = -1;
195
+ this._screen.appendChild(this._canvas);
196
+
197
+ // Cursor
198
+ this._cursor = new Cursor();
199
+
200
+ // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
201
+ // it. Result: no cursor at all until a window border or an edit field
202
+ // is hit blindly. But there are also VNC servers that draw the cursor
203
+ // in the framebuffer and don't send the empty local cursor. There is
204
+ // no way to satisfy both sides.
205
+ //
206
+ // The spec is unclear on this "initial cursor" issue. Many other
207
+ // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
208
+ // initial cursor instead.
209
+ this._cursorImage = RFB.cursors.none;
210
+
211
+ // populate decoder array with objects
212
+ this._decoders[encodings.encodingRaw] = new RawDecoder();
213
+ this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
214
+ this._decoders[encodings.encodingRRE] = new RREDecoder();
215
+ this._decoders[encodings.encodingHextile] = new HextileDecoder();
216
+ this._decoders[encodings.encodingTight] = new TightDecoder();
217
+ this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
218
+
219
+ // NB: nothing that needs explicit teardown should be done
220
+ // before this point, since this can throw an exception
221
+ try {
222
+ this._display = new Display(this._canvas);
223
+ } catch (exc) {
224
+ Log.Error("Display exception: " + exc);
225
+ throw exc;
241
226
  }
242
- this._sock.off('close');
243
- }.bind(this));
244
- this._sock.on('error', function (e) {
245
- Log.Warn("WebSocket on-error event");
246
- });
227
+ this._display.onflush = this._onFlush.bind(this);
247
228
 
248
- // Slight delay of the actual connection so that the caller has
249
- // time to set up callbacks
250
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
229
+ this._keyboard = new Keyboard(this._canvas);
230
+ this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
251
231
 
252
- Log.Debug("<< RFB.constructor");
253
- };
232
+ this._gestures = new GestureHandler();
254
233
 
255
- RFB.prototype = {
256
- // ===== PROPERTIES =====
234
+ this._sock = new Websock();
235
+ this._sock.on('message', () => {
236
+ this._handleMessage();
237
+ });
238
+ this._sock.on('open', () => {
239
+ if ((this._rfbConnectionState === 'connecting') &&
240
+ (this._rfbInitState === '')) {
241
+ this._rfbInitState = 'ProtocolVersion';
242
+ Log.Debug("Starting VNC handshake");
243
+ } else {
244
+ this._fail("Unexpected server connection while " +
245
+ this._rfbConnectionState);
246
+ }
247
+ });
248
+ this._sock.on('close', (e) => {
249
+ Log.Debug("WebSocket on-close event");
250
+ let msg = "";
251
+ if (e.code) {
252
+ msg = "(code: " + e.code;
253
+ if (e.reason) {
254
+ msg += ", reason: " + e.reason;
255
+ }
256
+ msg += ")";
257
+ }
258
+ switch (this._rfbConnectionState) {
259
+ case 'connecting':
260
+ this._fail("Connection closed " + msg);
261
+ break;
262
+ case 'connected':
263
+ // Handle disconnects that were initiated server-side
264
+ this._updateConnectionState('disconnecting');
265
+ this._updateConnectionState('disconnected');
266
+ break;
267
+ case 'disconnecting':
268
+ // Normal disconnection path
269
+ this._updateConnectionState('disconnected');
270
+ break;
271
+ case 'disconnected':
272
+ this._fail("Unexpected server disconnect " +
273
+ "when already disconnected " + msg);
274
+ break;
275
+ default:
276
+ this._fail("Unexpected server disconnect before connecting " +
277
+ msg);
278
+ break;
279
+ }
280
+ this._sock.off('close');
281
+ });
282
+ this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
283
+
284
+ // Slight delay of the actual connection so that the caller has
285
+ // time to set up callbacks
286
+ setTimeout(this._updateConnectionState.bind(this, 'connecting'));
287
+
288
+ Log.Debug("<< RFB.constructor");
289
+
290
+ // ===== PROPERTIES =====
291
+
292
+ this.dragViewport = false;
293
+ this.focusOnClick = true;
294
+
295
+ this._viewOnly = false;
296
+ this._clipViewport = false;
297
+ this._scaleViewport = false;
298
+ this._resizeSession = false;
257
299
 
258
- dragViewport: false,
259
- focusOnClick: true,
300
+ this._showDotCursor = false;
301
+ if (options.showDotCursor !== undefined) {
302
+ Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
303
+ this._showDotCursor = options.showDotCursor;
304
+ }
305
+
306
+ this._qualityLevel = 6;
307
+ this._compressionLevel = 2;
308
+ }
309
+
310
+ // ===== PROPERTIES =====
260
311
 
261
- _viewOnly: false,
262
- get viewOnly() { return this._viewOnly; },
312
+ get viewOnly() { return this._viewOnly; }
263
313
  set viewOnly(viewOnly) {
264
314
  this._viewOnly = viewOnly;
265
315
 
266
- if (this._rfb_connection_state === "connecting" ||
267
- this._rfb_connection_state === "connected") {
316
+ if (this._rfbConnectionState === "connecting" ||
317
+ this._rfbConnectionState === "connected") {
268
318
  if (viewOnly) {
269
319
  this._keyboard.ungrab();
270
- this._mouse.ungrab();
271
320
  } else {
272
321
  this._keyboard.grab();
273
- this._mouse.grab();
274
322
  }
275
323
  }
276
- },
324
+ }
277
325
 
278
- get capabilities() { return this._capabilities; },
326
+ get capabilities() { return this._capabilities; }
279
327
 
280
- get touchButton() { return this._mouse.touchButton; },
281
- set touchButton(button) { this._mouse.touchButton = button; },
328
+ get touchButton() { return 0; }
329
+ set touchButton(button) { Log.Warn("Using old API!"); }
282
330
 
283
- _clipViewport: false,
284
- get clipViewport() { return this._clipViewport; },
331
+ get clipViewport() { return this._clipViewport; }
285
332
  set clipViewport(viewport) {
286
333
  this._clipViewport = viewport;
287
334
  this._updateClip();
288
- },
335
+ }
289
336
 
290
- _scaleViewport: false,
291
- get scaleViewport() { return this._scaleViewport; },
337
+ get scaleViewport() { return this._scaleViewport; }
292
338
  set scaleViewport(scale) {
293
339
  this._scaleViewport = scale;
294
340
  // Scaling trumps clipping, so we may need to adjust
@@ -300,33 +346,81 @@ RFB.prototype = {
300
346
  if (!scale && this._clipViewport) {
301
347
  this._updateClip();
302
348
  }
303
- },
349
+ }
304
350
 
305
- _resizeSession: false,
306
- get resizeSession() { return this._resizeSession; },
351
+ get resizeSession() { return this._resizeSession; }
307
352
  set resizeSession(resize) {
308
353
  this._resizeSession = resize;
309
354
  if (resize) {
310
355
  this._requestRemoteResize();
311
356
  }
312
- },
357
+ }
358
+
359
+ get showDotCursor() { return this._showDotCursor; }
360
+ set showDotCursor(show) {
361
+ this._showDotCursor = show;
362
+ this._refreshCursor();
363
+ }
364
+
365
+ get background() { return this._screen.style.background; }
366
+ set background(cssValue) { this._screen.style.background = cssValue; }
367
+
368
+ get qualityLevel() {
369
+ return this._qualityLevel;
370
+ }
371
+ set qualityLevel(qualityLevel) {
372
+ if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {
373
+ Log.Error("qualityLevel must be an integer between 0 and 9");
374
+ return;
375
+ }
376
+
377
+ if (this._qualityLevel === qualityLevel) {
378
+ return;
379
+ }
380
+
381
+ this._qualityLevel = qualityLevel;
382
+
383
+ if (this._rfbConnectionState === 'connected') {
384
+ this._sendEncodings();
385
+ }
386
+ }
387
+
388
+ get compressionLevel() {
389
+ return this._compressionLevel;
390
+ }
391
+ set compressionLevel(compressionLevel) {
392
+ if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) {
393
+ Log.Error("compressionLevel must be an integer between 0 and 9");
394
+ return;
395
+ }
396
+
397
+ if (this._compressionLevel === compressionLevel) {
398
+ return;
399
+ }
400
+
401
+ this._compressionLevel = compressionLevel;
402
+
403
+ if (this._rfbConnectionState === 'connected') {
404
+ this._sendEncodings();
405
+ }
406
+ }
313
407
 
314
408
  // ===== PUBLIC METHODS =====
315
409
 
316
- disconnect: function () {
410
+ disconnect() {
317
411
  this._updateConnectionState('disconnecting');
318
412
  this._sock.off('error');
319
413
  this._sock.off('message');
320
414
  this._sock.off('open');
321
- },
415
+ }
322
416
 
323
- sendCredentials: function (creds) {
324
- this._rfb_credentials = creds;
325
- setTimeout(this._init_msg.bind(this), 0);
326
- },
417
+ sendCredentials(creds) {
418
+ this._rfbCredentials = creds;
419
+ setTimeout(this._initMsg.bind(this), 0);
420
+ }
327
421
 
328
- sendCtrlAltDel: function () {
329
- if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
422
+ sendCtrlAltDel() {
423
+ if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
330
424
  Log.Info("Sending Ctrl-Alt-Del");
331
425
 
332
426
  this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
@@ -335,24 +429,24 @@ RFB.prototype = {
335
429
  this.sendKey(KeyTable.XK_Delete, "Delete", false);
336
430
  this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
337
431
  this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
338
- },
432
+ }
339
433
 
340
- machineShutdown: function () {
434
+ machineShutdown() {
341
435
  this._xvpOp(1, 2);
342
- },
436
+ }
343
437
 
344
- machineReboot: function () {
438
+ machineReboot() {
345
439
  this._xvpOp(1, 3);
346
- },
440
+ }
347
441
 
348
- machineReset: function () {
442
+ machineReset() {
349
443
  this._xvpOp(1, 4);
350
- },
444
+ }
351
445
 
352
446
  // Send a key press. If 'down' is not specified then send a down key
353
447
  // followed by an up key.
354
- sendKey: function (keysym, code, down) {
355
- if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
448
+ sendKey(keysym, code, down) {
449
+ if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
356
450
 
357
451
  if (down === undefined) {
358
452
  this.sendKey(keysym, code, true);
@@ -360,7 +454,7 @@ RFB.prototype = {
360
454
  return;
361
455
  }
362
456
 
363
- var scancode = XtScancode[code];
457
+ const scancode = XtScancode[code];
364
458
 
365
459
  if (this._qemuExtKeyEventSupported && scancode) {
366
460
  // 0 is NoSymbol
@@ -376,31 +470,45 @@ RFB.prototype = {
376
470
  Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
377
471
  RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
378
472
  }
379
- },
473
+ }
380
474
 
381
- focus: function () {
475
+ focus() {
382
476
  this._canvas.focus();
383
- },
477
+ }
384
478
 
385
- blur: function () {
479
+ blur() {
386
480
  this._canvas.blur();
387
- },
481
+ }
388
482
 
389
- clipboardPasteFrom: function (text) {
390
- if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
391
- RFB.messages.clientCutText(this._sock, text);
392
- },
483
+ clipboardPasteFrom(text) {
484
+ if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
485
+
486
+ if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&
487
+ this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
488
+
489
+ this._clipboardText = text;
490
+ RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
491
+ } else {
492
+ let data = new Uint8Array(text.length);
493
+ for (let i = 0; i < text.length; i++) {
494
+ // FIXME: text can have values outside of Latin1/Uint8
495
+ data[i] = text.charCodeAt(i);
496
+ }
497
+
498
+ RFB.messages.clientCutText(this._sock, data);
499
+ }
500
+ }
393
501
 
394
502
  // ===== PRIVATE METHODS =====
395
503
 
396
- _connect: function () {
504
+ _connect() {
397
505
  Log.Debug(">> RFB.connect");
398
506
 
399
507
  Log.Info("connecting to " + this._url);
400
508
 
401
509
  try {
402
510
  // WebSocket.onopen transitions to the RFB init states
403
- this._sock.open(this._url, ['binary']);
511
+ this._sock.open(this._url, this._wsProtocols);
404
512
  } catch (e) {
405
513
  if (e.name === 'SyntaxError') {
406
514
  this._fail("Invalid host or port (" + e + ")");
@@ -412,6 +520,11 @@ RFB.prototype = {
412
520
  // Make our elements part of the page
413
521
  this._target.appendChild(this._screen);
414
522
 
523
+ this._gestures.attach(this._canvas);
524
+
525
+ this._cursor.attach(this._canvas);
526
+ this._refreshCursor();
527
+
415
528
  // Monitor size changes of the screen
416
529
  // FIXME: Use ResizeObserver, or hidden overflow
417
530
  window.addEventListener('resize', this._eventHandlers.windowResize);
@@ -420,18 +533,45 @@ RFB.prototype = {
420
533
  this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
421
534
  this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
422
535
 
536
+ // Mouse events
537
+ this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);
538
+ this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);
539
+ this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);
540
+ // Prevent middle-click pasting (see handler for why we bind to document)
541
+ this._canvas.addEventListener('click', this._eventHandlers.handleMouse);
542
+ // preventDefault() on mousedown doesn't stop this event for some
543
+ // reason so we have to explicitly block it
544
+ this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
545
+
546
+ // Wheel events
547
+ this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
548
+
549
+ // Gesture events
550
+ this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);
551
+ this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);
552
+ this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture);
553
+
423
554
  Log.Debug("<< RFB.connect");
424
- },
555
+ }
425
556
 
426
- _disconnect: function () {
557
+ _disconnect() {
427
558
  Log.Debug(">> RFB.disconnect");
559
+ this._cursor.detach();
560
+ this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);
561
+ this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);
562
+ this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);
563
+ this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);
564
+ this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);
565
+ this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);
566
+ this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
567
+ this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
568
+ this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
428
569
  this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
429
570
  this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
430
571
  window.removeEventListener('resize', this._eventHandlers.windowResize);
431
572
  this._keyboard.ungrab();
432
- this._mouse.ungrab();
573
+ this._gestures.detach();
433
574
  this._sock.close();
434
- this._print_stats();
435
575
  try {
436
576
  this._target.removeChild(this._screen);
437
577
  } catch (e) {
@@ -443,47 +583,32 @@ RFB.prototype = {
443
583
  }
444
584
  }
445
585
  clearTimeout(this._resizeTimeout);
586
+ clearTimeout(this._mouseMoveTimer);
446
587
  Log.Debug("<< RFB.disconnect");
447
- },
448
-
449
- _print_stats: function () {
450
- var stats = this._encStats;
451
-
452
- Log.Info("Encoding stats for this connection:");
453
- Object.keys(stats).forEach(function (key) {
454
- var s = stats[key];
455
- if (s[0] + s[1] > 0) {
456
- Log.Info(" " + encodingName(key) + ": " + s[0] + " rects");
457
- }
458
- });
459
-
460
- Log.Info("Encoding stats since page load:");
461
- Object.keys(stats).forEach(function (key) {
462
- var s = stats[key];
463
- Log.Info(" " + encodingName(key) + ": " + s[1] + " rects");
464
- });
465
- },
466
-
467
- _focusCanvas: function(event) {
468
- // Respect earlier handlers' request to not do side-effects
469
- if (event.defaultPrevented) {
470
- return;
471
- }
588
+ }
472
589
 
590
+ _focusCanvas(event) {
473
591
  if (!this.focusOnClick) {
474
592
  return;
475
593
  }
476
594
 
477
595
  this.focus();
478
- },
596
+ }
597
+
598
+ _setDesktopName(name) {
599
+ this._fbName = name;
600
+ this.dispatchEvent(new CustomEvent(
601
+ "desktopname",
602
+ { detail: { name: this._fbName } }));
603
+ }
479
604
 
480
- _windowResize: function (event) {
605
+ _windowResize(event) {
481
606
  // If the window resized then our screen element might have
482
607
  // as well. Update the viewport dimensions.
483
- window.requestAnimationFrame(function () {
608
+ window.requestAnimationFrame(() => {
484
609
  this._updateClip();
485
610
  this._updateScale();
486
- }.bind(this));
611
+ });
487
612
 
488
613
  if (this._resizeSession) {
489
614
  // Request changing the resolution of the remote display to
@@ -494,45 +619,45 @@ RFB.prototype = {
494
619
  clearTimeout(this._resizeTimeout);
495
620
  this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
496
621
  }
497
- },
622
+ }
498
623
 
499
624
  // Update state of clipping in Display object, and make sure the
500
625
  // configured viewport matches the current screen size
501
- _updateClip: function () {
502
- var cur_clip = this._display.clipViewport;
503
- var new_clip = this._clipViewport;
626
+ _updateClip() {
627
+ const curClip = this._display.clipViewport;
628
+ let newClip = this._clipViewport;
504
629
 
505
630
  if (this._scaleViewport) {
506
631
  // Disable viewport clipping if we are scaling
507
- new_clip = false;
632
+ newClip = false;
508
633
  }
509
634
 
510
- if (cur_clip !== new_clip) {
511
- this._display.clipViewport = new_clip;
635
+ if (curClip !== newClip) {
636
+ this._display.clipViewport = newClip;
512
637
  }
513
638
 
514
- if (new_clip) {
639
+ if (newClip) {
515
640
  // When clipping is enabled, the screen is limited to
516
641
  // the size of the container.
517
- let size = this._screenSize();
642
+ const size = this._screenSize();
518
643
  this._display.viewportChangeSize(size.w, size.h);
519
644
  this._fixScrollbars();
520
645
  }
521
- },
646
+ }
522
647
 
523
- _updateScale: function () {
648
+ _updateScale() {
524
649
  if (!this._scaleViewport) {
525
650
  this._display.scale = 1.0;
526
651
  } else {
527
- let size = this._screenSize();
652
+ const size = this._screenSize();
528
653
  this._display.autoscale(size.w, size.h);
529
654
  }
530
655
  this._fixScrollbars();
531
- },
656
+ }
532
657
 
533
658
  // Requests a change of remote desktop size. This message is an extension
534
659
  // and may only be sent if we have received an ExtendedDesktopSize message
535
- _requestRemoteResize: function () {
660
+ _requestRemoteResize() {
536
661
  clearTimeout(this._resizeTimeout);
537
662
  this._resizeTimeout = null;
538
663
 
@@ -541,31 +666,32 @@ RFB.prototype = {
541
666
  return;
542
667
  }
543
668
 
544
- let size = this._screenSize();
545
- RFB.messages.setDesktopSize(this._sock, size.w, size.h,
546
- this._screen_id, this._screen_flags);
669
+ const size = this._screenSize();
670
+ RFB.messages.setDesktopSize(this._sock,
671
+ Math.floor(size.w), Math.floor(size.h),
672
+ this._screenID, this._screenFlags);
547
673
 
548
674
  Log.Debug('Requested new desktop size: ' +
549
675
  size.w + 'x' + size.h);
550
- },
676
+ }
551
677
 
552
678
  // Gets the the size of the available screen
553
- _screenSize: function () {
554
- return { w: this._screen.offsetWidth,
555
- h: this._screen.offsetHeight };
556
- },
679
+ _screenSize() {
680
+ let r = this._screen.getBoundingClientRect();
681
+ return { w: r.width, h: r.height };
682
+ }
557
683
 
558
- _fixScrollbars: function () {
684
+ _fixScrollbars() {
559
685
  // This is a hack because Chrome screws up the calculation
560
686
  // for when scrollbars are needed. So to fix it we temporarily
561
687
  // toggle them off and on.
562
- var orig = this._screen.style.overflow;
688
+ const orig = this._screen.style.overflow;
563
689
  this._screen.style.overflow = 'hidden';
564
690
  // Force Chrome to recalculate the layout by asking for
565
691
  // an element's dimensions
566
692
  this._screen.getBoundingClientRect();
567
693
  this._screen.style.overflow = orig;
568
- },
694
+ }
569
695
 
570
696
  /*
571
697
  * Connection states:
@@ -574,8 +700,8 @@ RFB.prototype = {
574
700
  * disconnecting
575
701
  * disconnected - permanent state
576
702
  */
577
- _updateConnectionState: function (state) {
578
- var oldstate = this._rfb_connection_state;
703
+ _updateConnectionState(state) {
704
+ const oldstate = this._rfbConnectionState;
579
705
 
580
706
  if (state === oldstate) {
581
707
  Log.Debug("Already in state '" + state + "', ignoring");
@@ -629,10 +755,9 @@ RFB.prototype = {
629
755
 
630
756
  // State change actions
631
757
 
632
- this._rfb_connection_state = state;
758
+ this._rfbConnectionState = state;
633
759
 
634
- var smsg = "New state '" + state + "', was '" + oldstate + "'.";
635
- Log.Debug(smsg);
760
+ Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
636
761
 
637
762
  if (this._disconnTimer && state !== 'disconnecting') {
638
763
  Log.Debug("Clearing disconnect timer");
@@ -649,35 +774,33 @@ RFB.prototype = {
649
774
  break;
650
775
 
651
776
  case 'connected':
652
- var event = new CustomEvent("connect", { detail: {} });
653
- this.dispatchEvent(event);
777
+ this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
654
778
  break;
655
779
 
656
780
  case 'disconnecting':
657
781
  this._disconnect();
658
782
 
659
- this._disconnTimer = setTimeout(function () {
783
+ this._disconnTimer = setTimeout(() => {
660
784
  Log.Error("Disconnection timed out.");
661
785
  this._updateConnectionState('disconnected');
662
- }.bind(this), DISCONNECT_TIMEOUT * 1000);
786
+ }, DISCONNECT_TIMEOUT * 1000);
663
787
  break;
664
788
 
665
789
  case 'disconnected':
666
- event = new CustomEvent(
790
+ this.dispatchEvent(new CustomEvent(
667
791
  "disconnect", { detail:
668
- { clean: this._rfb_clean_disconnect } });
669
- this.dispatchEvent(event);
792
+ { clean: this._rfbCleanDisconnect } }));
670
793
  break;
671
794
  }
672
- },
795
+ }
673
796
 
674
797
  /* Print errors and disconnect
675
798
  *
676
799
  * The parameter 'details' is used for information that
677
800
  * should be logged but not sent to the user interface.
678
801
  */
679
- _fail: function (details) {
680
- switch (this._rfb_connection_state) {
802
+ _fail(details) {
803
+ switch (this._rfbConnectionState) {
681
804
  case 'disconnecting':
682
805
  Log.Error("Failed when disconnecting: " + details);
683
806
  break;
@@ -691,29 +814,28 @@ RFB.prototype = {
691
814
  Log.Error("RFB failure: " + details);
692
815
  break;
693
816
  }
694
- this._rfb_clean_disconnect = false; //This is sent to the UI
817
+ this._rfbCleanDisconnect = false; //This is sent to the UI
695
818
 
696
819
  // Transition to disconnected without waiting for socket to close
697
820
  this._updateConnectionState('disconnecting');
698
821
  this._updateConnectionState('disconnected');
699
822
 
700
823
  return false;
701
- },
824
+ }
702
825
 
703
- _setCapability: function (cap, val) {
826
+ _setCapability(cap, val) {
704
827
  this._capabilities[cap] = val;
705
- var event = new CustomEvent("capabilities",
706
- { detail: { capabilities: this._capabilities } });
707
- this.dispatchEvent(event);
708
- },
828
+ this.dispatchEvent(new CustomEvent("capabilities",
829
+ { detail: { capabilities: this._capabilities } }));
830
+ }
709
831
 
710
- _handle_message: function () {
711
- if (this._sock.rQlen() === 0) {
712
- Log.Warn("handle_message called on an empty receive queue");
832
+ _handleMessage() {
833
+ if (this._sock.rQlen === 0) {
834
+ Log.Warn("handleMessage called on an empty receive queue");
713
835
  return;
714
836
  }
715
837
 
716
- switch (this._rfb_connection_state) {
838
+ switch (this._rfbConnectionState) {
717
839
  case 'disconnected':
718
840
  Log.Error("Got data while disconnected");
719
841
  break;
@@ -722,31 +844,70 @@ RFB.prototype = {
722
844
  if (this._flushing) {
723
845
  break;
724
846
  }
725
- if (!this._normal_msg()) {
847
+ if (!this._normalMsg()) {
726
848
  break;
727
849
  }
728
- if (this._sock.rQlen() === 0) {
850
+ if (this._sock.rQlen === 0) {
729
851
  break;
730
852
  }
731
853
  }
732
854
  break;
733
855
  default:
734
- this._init_msg();
856
+ this._initMsg();
735
857
  break;
736
858
  }
737
- },
859
+ }
738
860
 
739
- _handleKeyEvent: function (keysym, code, down) {
861
+ _handleKeyEvent(keysym, code, down) {
740
862
  this.sendKey(keysym, code, down);
741
- },
863
+ }
742
864
 
743
- _handleMouseButton: function (x, y, down, bmask) {
744
- if (down) {
745
- this._mouse_buttonMask |= bmask;
746
- } else {
747
- this._mouse_buttonMask &= ~bmask;
865
+ _handleMouse(ev) {
866
+ /*
867
+ * We don't check connection status or viewOnly here as the
868
+ * mouse events might be used to control the viewport
869
+ */
870
+
871
+ if (ev.type === 'click') {
872
+ /*
873
+ * Note: This is only needed for the 'click' event as it fails
874
+ * to fire properly for the target element so we have
875
+ * to listen on the document element instead.
876
+ */
877
+ if (ev.target !== this._canvas) {
878
+ return;
879
+ }
880
+ }
881
+
882
+ // FIXME: if we're in view-only and not dragging,
883
+ // should we stop events?
884
+ ev.stopPropagation();
885
+ ev.preventDefault();
886
+
887
+ if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
888
+ return;
889
+ }
890
+
891
+ let pos = clientToElement(ev.clientX, ev.clientY,
892
+ this._canvas);
893
+
894
+ switch (ev.type) {
895
+ case 'mousedown':
896
+ setCapture(this._canvas);
897
+ this._handleMouseButton(pos.x, pos.y,
898
+ true, 1 << ev.button);
899
+ break;
900
+ case 'mouseup':
901
+ this._handleMouseButton(pos.x, pos.y,
902
+ false, 1 << ev.button);
903
+ break;
904
+ case 'mousemove':
905
+ this._handleMouseMove(pos.x, pos.y);
906
+ break;
748
907
  }
908
+ }
749
909
 
910
+ _handleMouseButton(x, y, down, bmask) {
750
911
  if (this.dragViewport) {
751
912
  if (down && !this._viewportDragging) {
752
913
  this._viewportDragging = true;
@@ -767,27 +928,30 @@ RFB.prototype = {
767
928
  // Otherwise we treat this as a mouse click event.
768
929
  // Send the button down event here, as the button up
769
930
  // event is sent at the end of this function.
770
- RFB.messages.pointerEvent(this._sock,
771
- this._display.absX(x),
772
- this._display.absY(y),
773
- bmask);
931
+ this._sendMouse(x, y, bmask);
774
932
  }
775
933
  }
776
934
 
777
- if (this._viewOnly) { return; } // View only, skip mouse events
935
+ // Flush waiting move event first
936
+ if (this._mouseMoveTimer !== null) {
937
+ clearTimeout(this._mouseMoveTimer);
938
+ this._mouseMoveTimer = null;
939
+ this._sendMouse(x, y, this._mouseButtonMask);
940
+ }
778
941
 
779
- if (this._rfb_connection_state !== 'connected') { return; }
780
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
781
- },
942
+ if (down) {
943
+ this._mouseButtonMask |= bmask;
944
+ } else {
945
+ this._mouseButtonMask &= ~bmask;
946
+ }
782
947
 
783
- _handleMouseMove: function (x, y) {
784
- if (this._viewportDragging) {
785
- var deltaX = this._viewportDragPos.x - x;
786
- var deltaY = this._viewportDragPos.y - y;
948
+ this._sendMouse(x, y, this._mouseButtonMask);
949
+ }
787
950
 
788
- // The goal is to trigger on a certain physical width, the
789
- // devicePixelRatio brings us a bit closer but is not optimal.
790
- var dragThreshold = 10 * (window.devicePixelRatio || 1);
951
+ _handleMouseMove(x, y) {
952
+ if (this._viewportDragging) {
953
+ const deltaX = this._viewportDragPos.x - x;
954
+ const deltaY = this._viewportDragPos.y - y;
791
955
 
792
956
  if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
793
957
  Math.abs(deltaY) > dragThreshold)) {
@@ -801,70 +965,308 @@ RFB.prototype = {
801
965
  return;
802
966
  }
803
967
 
968
+ this._mousePos = { 'x': x, 'y': y };
969
+
970
+ // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
971
+ if (this._mouseMoveTimer == null) {
972
+
973
+ const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;
974
+ if (timeSinceLastMove > MOUSE_MOVE_DELAY) {
975
+ this._sendMouse(x, y, this._mouseButtonMask);
976
+ this._mouseLastMoveTime = Date.now();
977
+ } else {
978
+ // Too soon since the latest move, wait the remaining time
979
+ this._mouseMoveTimer = setTimeout(() => {
980
+ this._handleDelayedMouseMove();
981
+ }, MOUSE_MOVE_DELAY - timeSinceLastMove);
982
+ }
983
+ }
984
+ }
985
+
986
+ _handleDelayedMouseMove() {
987
+ this._mouseMoveTimer = null;
988
+ this._sendMouse(this._mousePos.x, this._mousePos.y,
989
+ this._mouseButtonMask);
990
+ this._mouseLastMoveTime = Date.now();
991
+ }
992
+
993
+ _sendMouse(x, y, mask) {
994
+ if (this._rfbConnectionState !== 'connected') { return; }
804
995
  if (this._viewOnly) { return; } // View only, skip mouse events
805
996
 
806
- if (this._rfb_connection_state !== 'connected') { return; }
807
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
808
- },
997
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x),
998
+ this._display.absY(y), mask);
999
+ }
1000
+
1001
+ _handleWheel(ev) {
1002
+ if (this._rfbConnectionState !== 'connected') { return; }
1003
+ if (this._viewOnly) { return; } // View only, skip mouse events
1004
+
1005
+ ev.stopPropagation();
1006
+ ev.preventDefault();
1007
+
1008
+ let pos = clientToElement(ev.clientX, ev.clientY,
1009
+ this._canvas);
1010
+
1011
+ let dX = ev.deltaX;
1012
+ let dY = ev.deltaY;
1013
+
1014
+ // Pixel units unless it's non-zero.
1015
+ // Note that if deltamode is line or page won't matter since we aren't
1016
+ // sending the mouse wheel delta to the server anyway.
1017
+ // The difference between pixel and line can be important however since
1018
+ // we have a threshold that can be smaller than the line height.
1019
+ if (ev.deltaMode !== 0) {
1020
+ dX *= WHEEL_LINE_HEIGHT;
1021
+ dY *= WHEEL_LINE_HEIGHT;
1022
+ }
1023
+
1024
+ // Mouse wheel events are sent in steps over VNC. This means that the VNC
1025
+ // protocol can't handle a wheel event with specific distance or speed.
1026
+ // Therefor, if we get a lot of small mouse wheel events we combine them.
1027
+ this._accumulatedWheelDeltaX += dX;
1028
+ this._accumulatedWheelDeltaY += dY;
1029
+
1030
+ // Generate a mouse wheel step event when the accumulated delta
1031
+ // for one of the axes is large enough.
1032
+ if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
1033
+ if (this._accumulatedWheelDeltaX < 0) {
1034
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
1035
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
1036
+ } else if (this._accumulatedWheelDeltaX > 0) {
1037
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
1038
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
1039
+ }
1040
+
1041
+ this._accumulatedWheelDeltaX = 0;
1042
+ }
1043
+ if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
1044
+ if (this._accumulatedWheelDeltaY < 0) {
1045
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
1046
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
1047
+ } else if (this._accumulatedWheelDeltaY > 0) {
1048
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
1049
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
1050
+ }
1051
+
1052
+ this._accumulatedWheelDeltaY = 0;
1053
+ }
1054
+ }
1055
+
1056
+ _fakeMouseMove(ev, elementX, elementY) {
1057
+ this._handleMouseMove(elementX, elementY);
1058
+ this._cursor.move(ev.detail.clientX, ev.detail.clientY);
1059
+ }
1060
+
1061
+ _handleTapEvent(ev, bmask) {
1062
+ let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
1063
+ this._canvas);
1064
+
1065
+ // If the user quickly taps multiple times we assume they meant to
1066
+ // hit the same spot, so slightly adjust coordinates
1067
+
1068
+ if ((this._gestureLastTapTime !== null) &&
1069
+ ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&
1070
+ (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {
1071
+ let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;
1072
+ let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;
1073
+ let distance = Math.hypot(dx, dy);
1074
+
1075
+ if (distance < DOUBLE_TAP_THRESHOLD) {
1076
+ pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,
1077
+ this._gestureFirstDoubleTapEv.detail.clientY,
1078
+ this._canvas);
1079
+ } else {
1080
+ this._gestureFirstDoubleTapEv = ev;
1081
+ }
1082
+ } else {
1083
+ this._gestureFirstDoubleTapEv = ev;
1084
+ }
1085
+ this._gestureLastTapTime = Date.now();
1086
+
1087
+ this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);
1088
+ this._handleMouseButton(pos.x, pos.y, true, bmask);
1089
+ this._handleMouseButton(pos.x, pos.y, false, bmask);
1090
+ }
1091
+
1092
+ _handleGesture(ev) {
1093
+ let magnitude;
1094
+
1095
+ let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
1096
+ this._canvas);
1097
+ switch (ev.type) {
1098
+ case 'gesturestart':
1099
+ switch (ev.detail.type) {
1100
+ case 'onetap':
1101
+ this._handleTapEvent(ev, 0x1);
1102
+ break;
1103
+ case 'twotap':
1104
+ this._handleTapEvent(ev, 0x4);
1105
+ break;
1106
+ case 'threetap':
1107
+ this._handleTapEvent(ev, 0x2);
1108
+ break;
1109
+ case 'drag':
1110
+ this._fakeMouseMove(ev, pos.x, pos.y);
1111
+ this._handleMouseButton(pos.x, pos.y, true, 0x1);
1112
+ break;
1113
+ case 'longpress':
1114
+ this._fakeMouseMove(ev, pos.x, pos.y);
1115
+ this._handleMouseButton(pos.x, pos.y, true, 0x4);
1116
+ break;
1117
+
1118
+ case 'twodrag':
1119
+ this._gestureLastMagnitudeX = ev.detail.magnitudeX;
1120
+ this._gestureLastMagnitudeY = ev.detail.magnitudeY;
1121
+ this._fakeMouseMove(ev, pos.x, pos.y);
1122
+ break;
1123
+ case 'pinch':
1124
+ this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,
1125
+ ev.detail.magnitudeY);
1126
+ this._fakeMouseMove(ev, pos.x, pos.y);
1127
+ break;
1128
+ }
1129
+ break;
1130
+
1131
+ case 'gesturemove':
1132
+ switch (ev.detail.type) {
1133
+ case 'onetap':
1134
+ case 'twotap':
1135
+ case 'threetap':
1136
+ break;
1137
+ case 'drag':
1138
+ case 'longpress':
1139
+ this._fakeMouseMove(ev, pos.x, pos.y);
1140
+ break;
1141
+ case 'twodrag':
1142
+ // Always scroll in the same position.
1143
+ // We don't know if the mouse was moved so we need to move it
1144
+ // every update.
1145
+ this._fakeMouseMove(ev, pos.x, pos.y);
1146
+ while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
1147
+ this._handleMouseButton(pos.x, pos.y, true, 0x8);
1148
+ this._handleMouseButton(pos.x, pos.y, false, 0x8);
1149
+ this._gestureLastMagnitudeY += GESTURE_SCRLSENS;
1150
+ }
1151
+ while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {
1152
+ this._handleMouseButton(pos.x, pos.y, true, 0x10);
1153
+ this._handleMouseButton(pos.x, pos.y, false, 0x10);
1154
+ this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;
1155
+ }
1156
+ while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {
1157
+ this._handleMouseButton(pos.x, pos.y, true, 0x20);
1158
+ this._handleMouseButton(pos.x, pos.y, false, 0x20);
1159
+ this._gestureLastMagnitudeX += GESTURE_SCRLSENS;
1160
+ }
1161
+ while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {
1162
+ this._handleMouseButton(pos.x, pos.y, true, 0x40);
1163
+ this._handleMouseButton(pos.x, pos.y, false, 0x40);
1164
+ this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;
1165
+ }
1166
+ break;
1167
+ case 'pinch':
1168
+ // Always scroll in the same position.
1169
+ // We don't know if the mouse was moved so we need to move it
1170
+ // every update.
1171
+ this._fakeMouseMove(ev, pos.x, pos.y);
1172
+ magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
1173
+ if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
1174
+ this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
1175
+ while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
1176
+ this._handleMouseButton(pos.x, pos.y, true, 0x8);
1177
+ this._handleMouseButton(pos.x, pos.y, false, 0x8);
1178
+ this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;
1179
+ }
1180
+ while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {
1181
+ this._handleMouseButton(pos.x, pos.y, true, 0x10);
1182
+ this._handleMouseButton(pos.x, pos.y, false, 0x10);
1183
+ this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;
1184
+ }
1185
+ }
1186
+ this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
1187
+ break;
1188
+ }
1189
+ break;
1190
+
1191
+ case 'gestureend':
1192
+ switch (ev.detail.type) {
1193
+ case 'onetap':
1194
+ case 'twotap':
1195
+ case 'threetap':
1196
+ case 'pinch':
1197
+ case 'twodrag':
1198
+ break;
1199
+ case 'drag':
1200
+ this._fakeMouseMove(ev, pos.x, pos.y);
1201
+ this._handleMouseButton(pos.x, pos.y, false, 0x1);
1202
+ break;
1203
+ case 'longpress':
1204
+ this._fakeMouseMove(ev, pos.x, pos.y);
1205
+ this._handleMouseButton(pos.x, pos.y, false, 0x4);
1206
+ break;
1207
+ }
1208
+ break;
1209
+ }
1210
+ }
809
1211
 
810
1212
  // Message Handlers
811
1213
 
812
- _negotiate_protocol_version: function () {
813
- if (this._sock.rQlen() < 12) {
814
- return this._fail("Received incomplete protocol version.");
1214
+ _negotiateProtocolVersion() {
1215
+ if (this._sock.rQwait("version", 12)) {
1216
+ return false;
815
1217
  }
816
1218
 
817
- var sversion = this._sock.rQshiftStr(12).substr(4, 7);
1219
+ const sversion = this._sock.rQshiftStr(12).substr(4, 7);
818
1220
  Log.Info("Server ProtocolVersion: " + sversion);
819
- var is_repeater = 0;
1221
+ let isRepeater = 0;
820
1222
  switch (sversion) {
821
1223
  case "000.000": // UltraVNC repeater
822
- is_repeater = 1;
1224
+ isRepeater = 1;
823
1225
  break;
824
1226
  case "003.003":
825
1227
  case "003.006": // UltraVNC
826
1228
  case "003.889": // Apple Remote Desktop
827
- this._rfb_version = 3.3;
1229
+ this._rfbVersion = 3.3;
828
1230
  break;
829
1231
  case "003.007":
830
- this._rfb_version = 3.7;
1232
+ this._rfbVersion = 3.7;
831
1233
  break;
832
1234
  case "003.008":
833
1235
  case "004.000": // Intel AMT KVM
834
1236
  case "004.001": // RealVNC 4.6
835
1237
  case "005.000": // RealVNC 5.3
836
- this._rfb_version = 3.8;
1238
+ this._rfbVersion = 3.8;
837
1239
  break;
838
1240
  default:
839
1241
  return this._fail("Invalid server version " + sversion);
840
1242
  }
841
1243
 
842
- if (is_repeater) {
843
- var repeaterID = "ID:" + this._repeaterID;
1244
+ if (isRepeater) {
1245
+ let repeaterID = "ID:" + this._repeaterID;
844
1246
  while (repeaterID.length < 250) {
845
1247
  repeaterID += "\0";
846
1248
  }
847
- this._sock.send_string(repeaterID);
1249
+ this._sock.sendString(repeaterID);
848
1250
  return true;
849
1251
  }
850
1252
 
851
- if (this._rfb_version > this._rfb_max_version) {
852
- this._rfb_version = this._rfb_max_version;
1253
+ if (this._rfbVersion > this._rfbMaxVersion) {
1254
+ this._rfbVersion = this._rfbMaxVersion;
853
1255
  }
854
1256
 
855
- var cversion = "00" + parseInt(this._rfb_version, 10) +
856
- ".00" + ((this._rfb_version * 10) % 10);
857
- this._sock.send_string("RFB " + cversion + "\n");
1257
+ const cversion = "00" + parseInt(this._rfbVersion, 10) +
1258
+ ".00" + ((this._rfbVersion * 10) % 10);
1259
+ this._sock.sendString("RFB " + cversion + "\n");
858
1260
  Log.Debug('Sent ProtocolVersion: ' + cversion);
859
1261
 
860
- this._rfb_init_state = 'Security';
861
- },
1262
+ this._rfbInitState = 'Security';
1263
+ }
862
1264
 
863
- _negotiate_security: function () {
1265
+ _negotiateSecurity() {
864
1266
  // Polyfill since IE and PhantomJS doesn't have
865
1267
  // TypedArray.includes()
866
1268
  function includes(item, array) {
867
- for (var i = 0; i < array.length; i++) {
1269
+ for (let i = 0; i < array.length; i++) {
868
1270
  if (array[i] === item) {
869
1271
  return true;
870
1272
  }
@@ -872,147 +1274,253 @@ RFB.prototype = {
872
1274
  return false;
873
1275
  }
874
1276
 
875
- if (this._rfb_version >= 3.7) {
1277
+ if (this._rfbVersion >= 3.7) {
876
1278
  // Server sends supported list, client decides
877
- var num_types = this._sock.rQshift8();
878
- if (this._sock.rQwait("security type", num_types, 1)) { return false; }
879
-
880
- if (num_types === 0) {
881
- return this._handle_security_failure("no security types");
1279
+ const numTypes = this._sock.rQshift8();
1280
+ if (this._sock.rQwait("security type", numTypes, 1)) { return false; }
1281
+
1282
+ if (numTypes === 0) {
1283
+ this._rfbInitState = "SecurityReason";
1284
+ this._securityContext = "no security types";
1285
+ this._securityStatus = 1;
1286
+ return this._initMsg();
882
1287
  }
883
1288
 
884
- var types = this._sock.rQshiftBytes(num_types);
1289
+ const types = this._sock.rQshiftBytes(numTypes);
885
1290
  Log.Debug("Server security types: " + types);
886
1291
 
887
1292
  // Look for each auth in preferred order
888
- this._rfb_auth_scheme = 0;
889
1293
  if (includes(1, types)) {
890
- this._rfb_auth_scheme = 1; // None
1294
+ this._rfbAuthScheme = 1; // None
891
1295
  } else if (includes(22, types)) {
892
- this._rfb_auth_scheme = 22; // XVP
1296
+ this._rfbAuthScheme = 22; // XVP
893
1297
  } else if (includes(16, types)) {
894
- this._rfb_auth_scheme = 16; // Tight
1298
+ this._rfbAuthScheme = 16; // Tight
895
1299
  } else if (includes(2, types)) {
896
- this._rfb_auth_scheme = 2; // VNC Auth
1300
+ this._rfbAuthScheme = 2; // VNC Auth
1301
+ } else if (includes(19, types)) {
1302
+ this._rfbAuthScheme = 19; // VeNCrypt Auth
897
1303
  } else {
898
1304
  return this._fail("Unsupported security types (types: " + types + ")");
899
1305
  }
900
1306
 
901
- this._sock.send([this._rfb_auth_scheme]);
1307
+ this._sock.send([this._rfbAuthScheme]);
902
1308
  } else {
903
1309
  // Server decides
904
1310
  if (this._sock.rQwait("security scheme", 4)) { return false; }
905
- this._rfb_auth_scheme = this._sock.rQshift32();
906
- }
1311
+ this._rfbAuthScheme = this._sock.rQshift32();
907
1312
 
908
- this._rfb_init_state = 'Authentication';
909
- Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
1313
+ if (this._rfbAuthScheme == 0) {
1314
+ this._rfbInitState = "SecurityReason";
1315
+ this._securityContext = "authentication scheme";
1316
+ this._securityStatus = 1;
1317
+ return this._initMsg();
1318
+ }
1319
+ }
910
1320
 
911
- return this._init_msg(); // jump to authentication
912
- },
1321
+ this._rfbInitState = 'Authentication';
1322
+ Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
913
1323
 
914
- /*
915
- * Get the security failure reason if sent from the server and
916
- * send the 'securityfailure' event.
917
- *
918
- * - The optional parameter context can be used to add some extra
919
- * context to the log output.
920
- *
921
- * - The optional parameter security_result_status can be used to
922
- * add a custom status code to the event.
923
- */
924
- _handle_security_failure: function (context, security_result_status) {
925
-
926
- if (typeof context === 'undefined') {
927
- context = "";
928
- } else {
929
- context = " on " + context;
930
- }
931
-
932
- if (typeof security_result_status === 'undefined') {
933
- security_result_status = 1; // fail
934
- }
1324
+ return this._initMsg(); // jump to authentication
1325
+ }
935
1326
 
1327
+ _handleSecurityReason() {
936
1328
  if (this._sock.rQwait("reason length", 4)) {
937
1329
  return false;
938
1330
  }
939
- let strlen = this._sock.rQshift32();
1331
+ const strlen = this._sock.rQshift32();
940
1332
  let reason = "";
941
1333
 
942
1334
  if (strlen > 0) {
943
- if (this._sock.rQwait("reason", strlen, 8)) { return false; }
1335
+ if (this._sock.rQwait("reason", strlen, 4)) { return false; }
944
1336
  reason = this._sock.rQshiftStr(strlen);
945
1337
  }
946
1338
 
947
1339
  if (reason !== "") {
948
-
949
- let event = new CustomEvent(
1340
+ this.dispatchEvent(new CustomEvent(
950
1341
  "securityfailure",
951
- { detail: { status: security_result_status, reason: reason } });
952
- this.dispatchEvent(event);
1342
+ { detail: { status: this._securityStatus,
1343
+ reason: reason } }));
953
1344
 
954
- return this._fail("Security negotiation failed" + context +
1345
+ return this._fail("Security negotiation failed on " +
1346
+ this._securityContext +
955
1347
  " (reason: " + reason + ")");
956
1348
  } else {
957
-
958
- let event = new CustomEvent(
1349
+ this.dispatchEvent(new CustomEvent(
959
1350
  "securityfailure",
960
- { detail: { status: security_result_status } });
961
- this.dispatchEvent(event);
1351
+ { detail: { status: this._securityStatus } }));
962
1352
 
963
- return this._fail("Security negotiation failed" + context);
1353
+ return this._fail("Security negotiation failed on " +
1354
+ this._securityContext);
964
1355
  }
965
- },
1356
+ }
966
1357
 
967
1358
  // authentication
968
- _negotiate_xvp_auth: function () {
969
- if (!this._rfb_credentials.username ||
970
- !this._rfb_credentials.password ||
971
- !this._rfb_credentials.target) {
972
- var event = new CustomEvent("credentialsrequired",
973
- { detail: { types: ["username", "password", "target"] } });
974
- this.dispatchEvent(event);
1359
+ _negotiateXvpAuth() {
1360
+ if (this._rfbCredentials.username === undefined ||
1361
+ this._rfbCredentials.password === undefined ||
1362
+ this._rfbCredentials.target === undefined) {
1363
+ this.dispatchEvent(new CustomEvent(
1364
+ "credentialsrequired",
1365
+ { detail: { types: ["username", "password", "target"] } }));
975
1366
  return false;
976
1367
  }
977
1368
 
978
- var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
979
- String.fromCharCode(this._rfb_credentials.target.length) +
980
- this._rfb_credentials.username +
981
- this._rfb_credentials.target;
982
- this._sock.send_string(xvp_auth_str);
983
- this._rfb_auth_scheme = 2;
984
- return this._negotiate_authentication();
985
- },
1369
+ const xvpAuthStr = String.fromCharCode(this._rfbCredentials.username.length) +
1370
+ String.fromCharCode(this._rfbCredentials.target.length) +
1371
+ this._rfbCredentials.username +
1372
+ this._rfbCredentials.target;
1373
+ this._sock.sendString(xvpAuthStr);
1374
+ this._rfbAuthScheme = 2;
1375
+ return this._negotiateAuthentication();
1376
+ }
1377
+
1378
+ // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
1379
+ _negotiateVeNCryptAuth() {
1380
+
1381
+ // waiting for VeNCrypt version
1382
+ if (this._rfbVeNCryptState == 0) {
1383
+ if (this._sock.rQwait("vencrypt version", 2)) { return false; }
1384
+
1385
+ const major = this._sock.rQshift8();
1386
+ const minor = this._sock.rQshift8();
1387
+
1388
+ if (!(major == 0 && minor == 2)) {
1389
+ return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
1390
+ }
1391
+
1392
+ this._sock.send([0, 2]);
1393
+ this._rfbVeNCryptState = 1;
1394
+ }
1395
+
1396
+ // waiting for ACK
1397
+ if (this._rfbVeNCryptState == 1) {
1398
+ if (this._sock.rQwait("vencrypt ack", 1)) { return false; }
1399
+
1400
+ const res = this._sock.rQshift8();
1401
+
1402
+ if (res != 0) {
1403
+ return this._fail("VeNCrypt failure " + res);
1404
+ }
1405
+
1406
+ this._rfbVeNCryptState = 2;
1407
+ }
1408
+ // must fall through here (i.e. no "else if"), beacause we may have already received
1409
+ // the subtypes length and won't be called again
1410
+
1411
+ if (this._rfbVeNCryptState == 2) { // waiting for subtypes length
1412
+ if (this._sock.rQwait("vencrypt subtypes length", 1)) { return false; }
1413
+
1414
+ const subtypesLength = this._sock.rQshift8();
1415
+ if (subtypesLength < 1) {
1416
+ return this._fail("VeNCrypt subtypes empty");
1417
+ }
1418
+
1419
+ this._rfbVeNCryptSubtypesLength = subtypesLength;
1420
+ this._rfbVeNCryptState = 3;
1421
+ }
1422
+
1423
+ // waiting for subtypes list
1424
+ if (this._rfbVeNCryptState == 3) {
1425
+ if (this._sock.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength)) { return false; }
1426
+
1427
+ const subtypes = [];
1428
+ for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) {
1429
+ subtypes.push(this._sock.rQshift32());
1430
+ }
1431
+
1432
+ // 256 = Plain subtype
1433
+ if (subtypes.indexOf(256) != -1) {
1434
+ // 0x100 = 256
1435
+ this._sock.send([0, 0, 1, 0]);
1436
+ this._rfbVeNCryptState = 4;
1437
+ } else {
1438
+ return this._fail("VeNCrypt Plain subtype not offered by server");
1439
+ }
1440
+ }
1441
+
1442
+ // negotiated Plain subtype, server waits for password
1443
+ if (this._rfbVeNCryptState == 4) {
1444
+ if (!this._rfbCredentials.username ||
1445
+ !this._rfbCredentials.password) {
1446
+ this.dispatchEvent(new CustomEvent(
1447
+ "credentialsrequired",
1448
+ { detail: { types: ["username", "password"] } }));
1449
+ return false;
1450
+ }
1451
+
1452
+ const user = encodeUTF8(this._rfbCredentials.username);
1453
+ const pass = encodeUTF8(this._rfbCredentials.password);
1454
+
1455
+ // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1456
+ this._sock.send([0, 0, 0, user.length]);
1457
+ this._sock.send([0, 0, 0, pass.length]);
1458
+ this._sock.sendString(user);
1459
+ this._sock.sendString(pass);
986
1460
 
987
- _negotiate_std_vnc_auth: function () {
1461
+ this._rfbInitState = "SecurityResult";
1462
+ return true;
1463
+ }
1464
+ }
1465
+
1466
+ _negotiateStdVNCAuth() {
988
1467
  if (this._sock.rQwait("auth challenge", 16)) { return false; }
989
1468
 
990
- if (!this._rfb_credentials.password) {
991
- var event = new CustomEvent("credentialsrequired",
992
- { detail: { types: ["password"] } });
993
- this.dispatchEvent(event);
1469
+ if (this._rfbCredentials.password === undefined) {
1470
+ this.dispatchEvent(new CustomEvent(
1471
+ "credentialsrequired",
1472
+ { detail: { types: ["password"] } }));
994
1473
  return false;
995
1474
  }
996
1475
 
997
1476
  // TODO(directxman12): make genDES not require an Array
998
- var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
999
- var response = RFB.genDES(this._rfb_credentials.password, challenge);
1477
+ const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
1478
+ const response = RFB.genDES(this._rfbCredentials.password, challenge);
1000
1479
  this._sock.send(response);
1001
- this._rfb_init_state = "SecurityResult";
1480
+ this._rfbInitState = "SecurityResult";
1002
1481
  return true;
1003
- },
1482
+ }
1483
+
1484
+ _negotiateTightUnixAuth() {
1485
+ if (this._rfbCredentials.username === undefined ||
1486
+ this._rfbCredentials.password === undefined) {
1487
+ this.dispatchEvent(new CustomEvent(
1488
+ "credentialsrequired",
1489
+ { detail: { types: ["username", "password"] } }));
1490
+ return false;
1491
+ }
1492
+
1493
+ this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
1494
+ this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
1495
+ this._sock.sendString(this._rfbCredentials.username);
1496
+ this._sock.sendString(this._rfbCredentials.password);
1497
+ this._rfbInitState = "SecurityResult";
1498
+ return true;
1499
+ }
1004
1500
 
1005
- _negotiate_tight_tunnels: function (numTunnels) {
1006
- var clientSupportedTunnelTypes = {
1501
+ _negotiateTightTunnels(numTunnels) {
1502
+ const clientSupportedTunnelTypes = {
1007
1503
  0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
1008
1504
  };
1009
- var serverSupportedTunnelTypes = {};
1505
+ const serverSupportedTunnelTypes = {};
1010
1506
  // receive tunnel capabilities
1011
- for (var i = 0; i < numTunnels; i++) {
1012
- var cap_code = this._sock.rQshift32();
1013
- var cap_vendor = this._sock.rQshiftStr(4);
1014
- var cap_signature = this._sock.rQshiftStr(8);
1015
- serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
1507
+ for (let i = 0; i < numTunnels; i++) {
1508
+ const capCode = this._sock.rQshift32();
1509
+ const capVendor = this._sock.rQshiftStr(4);
1510
+ const capSignature = this._sock.rQshiftStr(8);
1511
+ serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };
1512
+ }
1513
+
1514
+ Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
1515
+
1516
+ // Siemens touch panels have a VNC server that supports NOTUNNEL,
1517
+ // but forgets to advertise it. Try to detect such servers by
1518
+ // looking for their custom tunnel type.
1519
+ if (serverSupportedTunnelTypes[1] &&
1520
+ (serverSupportedTunnelTypes[1].vendor === "SICR") &&
1521
+ (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
1522
+ Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1523
+ serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
1016
1524
  }
1017
1525
 
1018
1526
  // choose the notunnel type
@@ -1022,62 +1530,70 @@ RFB.prototype = {
1022
1530
  return this._fail("Client's tunnel type had the incorrect " +
1023
1531
  "vendor or signature");
1024
1532
  }
1533
+ Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
1025
1534
  this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
1026
1535
  return false; // wait until we receive the sub auth count to continue
1027
1536
  } else {
1028
1537
  return this._fail("Server wanted tunnels, but doesn't support " +
1029
1538
  "the notunnel type");
1030
1539
  }
1031
- },
1540
+ }
1032
1541
 
1033
- _negotiate_tight_auth: function () {
1034
- if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
1542
+ _negotiateTightAuth() {
1543
+ if (!this._rfbTightVNC) { // first pass, do the tunnel negotiation
1035
1544
  if (this._sock.rQwait("num tunnels", 4)) { return false; }
1036
- var numTunnels = this._sock.rQshift32();
1545
+ const numTunnels = this._sock.rQshift32();
1037
1546
  if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
1038
1547
 
1039
- this._rfb_tightvnc = true;
1548
+ this._rfbTightVNC = true;
1040
1549
 
1041
1550
  if (numTunnels > 0) {
1042
- this._negotiate_tight_tunnels(numTunnels);
1551
+ this._negotiateTightTunnels(numTunnels);
1043
1552
  return false; // wait until we receive the sub auth to continue
1044
1553
  }
1045
1554
  }
1046
1555
 
1047
1556
  // second pass, do the sub-auth negotiation
1048
1557
  if (this._sock.rQwait("sub auth count", 4)) { return false; }
1049
- var subAuthCount = this._sock.rQshift32();
1558
+ const subAuthCount = this._sock.rQshift32();
1050
1559
  if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
1051
- this._rfb_init_state = 'SecurityResult';
1560
+ this._rfbInitState = 'SecurityResult';
1052
1561
  return true;
1053
1562
  }
1054
1563
 
1055
1564
  if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
1056
1565
 
1057
- var clientSupportedTypes = {
1566
+ const clientSupportedTypes = {
1058
1567
  'STDVNOAUTH__': 1,
1059
- 'STDVVNCAUTH_': 2
1568
+ 'STDVVNCAUTH_': 2,
1569
+ 'TGHTULGNAUTH': 129
1060
1570
  };
1061
1571
 
1062
- var serverSupportedTypes = [];
1572
+ const serverSupportedTypes = [];
1063
1573
 
1064
- for (var i = 0; i < subAuthCount; i++) {
1065
- var capNum = this._sock.rQshift32();
1066
- var capabilities = this._sock.rQshiftStr(12);
1574
+ for (let i = 0; i < subAuthCount; i++) {
1575
+ this._sock.rQshift32(); // capNum
1576
+ const capabilities = this._sock.rQshiftStr(12);
1067
1577
  serverSupportedTypes.push(capabilities);
1068
1578
  }
1069
1579
 
1070
- for (var authType in clientSupportedTypes) {
1580
+ Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
1581
+
1582
+ for (let authType in clientSupportedTypes) {
1071
1583
  if (serverSupportedTypes.indexOf(authType) != -1) {
1072
1584
  this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
1585
+ Log.Debug("Selected authentication type: " + authType);
1073
1586
 
1074
1587
  switch (authType) {
1075
1588
  case 'STDVNOAUTH__': // no auth
1076
- this._rfb_init_state = 'SecurityResult';
1589
+ this._rfbInitState = 'SecurityResult';
1077
1590
  return true;
1078
1591
  case 'STDVVNCAUTH_': // VNC auth
1079
- this._rfb_auth_scheme = 2;
1080
- return this._init_msg();
1592
+ this._rfbAuthScheme = 2;
1593
+ return this._initMsg();
1594
+ case 'TGHTULGNAUTH': // UNIX auth
1595
+ this._rfbAuthScheme = 129;
1596
+ return this._initMsg();
1081
1597
  default:
1082
1598
  return this._fail("Unsupported tiny auth scheme " +
1083
1599
  "(scheme: " + authType + ")");
@@ -1086,97 +1602,104 @@ RFB.prototype = {
1086
1602
  }
1087
1603
 
1088
1604
  return this._fail("No supported sub-auth types!");
1089
- },
1090
-
1091
- _negotiate_authentication: function () {
1092
- switch (this._rfb_auth_scheme) {
1093
- case 0: // connection failed
1094
- return this._handle_security_failure("authentication scheme");
1605
+ }
1095
1606
 
1607
+ _negotiateAuthentication() {
1608
+ switch (this._rfbAuthScheme) {
1096
1609
  case 1: // no auth
1097
- if (this._rfb_version >= 3.8) {
1098
- this._rfb_init_state = 'SecurityResult';
1610
+ if (this._rfbVersion >= 3.8) {
1611
+ this._rfbInitState = 'SecurityResult';
1099
1612
  return true;
1100
1613
  }
1101
- this._rfb_init_state = 'ClientInitialisation';
1102
- return this._init_msg();
1614
+ this._rfbInitState = 'ClientInitialisation';
1615
+ return this._initMsg();
1103
1616
 
1104
1617
  case 22: // XVP auth
1105
- return this._negotiate_xvp_auth();
1618
+ return this._negotiateXvpAuth();
1106
1619
 
1107
1620
  case 2: // VNC authentication
1108
- return this._negotiate_std_vnc_auth();
1621
+ return this._negotiateStdVNCAuth();
1109
1622
 
1110
1623
  case 16: // TightVNC Security Type
1111
- return this._negotiate_tight_auth();
1624
+ return this._negotiateTightAuth();
1625
+
1626
+ case 19: // VeNCrypt Security Type
1627
+ return this._negotiateVeNCryptAuth();
1628
+
1629
+ case 129: // TightVNC UNIX Security Type
1630
+ return this._negotiateTightUnixAuth();
1112
1631
 
1113
1632
  default:
1114
1633
  return this._fail("Unsupported auth scheme (scheme: " +
1115
- this._rfb_auth_scheme + ")");
1634
+ this._rfbAuthScheme + ")");
1116
1635
  }
1117
- },
1636
+ }
1118
1637
 
1119
- _handle_security_result: function () {
1638
+ _handleSecurityResult() {
1120
1639
  if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
1121
1640
 
1122
- let status = this._sock.rQshift32();
1641
+ const status = this._sock.rQshift32();
1123
1642
 
1124
1643
  if (status === 0) { // OK
1125
- this._rfb_init_state = 'ClientInitialisation';
1644
+ this._rfbInitState = 'ClientInitialisation';
1126
1645
  Log.Debug('Authentication OK');
1127
- return this._init_msg();
1646
+ return this._initMsg();
1128
1647
  } else {
1129
- if (this._rfb_version >= 3.8) {
1130
- return this._handle_security_failure("security result", status);
1648
+ if (this._rfbVersion >= 3.8) {
1649
+ this._rfbInitState = "SecurityReason";
1650
+ this._securityContext = "security result";
1651
+ this._securityStatus = status;
1652
+ return this._initMsg();
1131
1653
  } else {
1132
- let event = new CustomEvent("securityfailure",
1133
- { detail: { status: status } });
1134
- this.dispatchEvent(event);
1654
+ this.dispatchEvent(new CustomEvent(
1655
+ "securityfailure",
1656
+ { detail: { status: status } }));
1135
1657
 
1136
1658
  return this._fail("Security handshake failed");
1137
1659
  }
1138
1660
  }
1139
- },
1661
+ }
1140
1662
 
1141
- _negotiate_server_init: function () {
1663
+ _negotiateServerInit() {
1142
1664
  if (this._sock.rQwait("server initialization", 24)) { return false; }
1143
1665
 
1144
1666
  /* Screen size */
1145
- var width = this._sock.rQshift16();
1146
- var height = this._sock.rQshift16();
1667
+ const width = this._sock.rQshift16();
1668
+ const height = this._sock.rQshift16();
1147
1669
 
1148
1670
  /* PIXEL_FORMAT */
1149
- var bpp = this._sock.rQshift8();
1150
- var depth = this._sock.rQshift8();
1151
- var big_endian = this._sock.rQshift8();
1152
- var true_color = this._sock.rQshift8();
1153
-
1154
- var red_max = this._sock.rQshift16();
1155
- var green_max = this._sock.rQshift16();
1156
- var blue_max = this._sock.rQshift16();
1157
- var red_shift = this._sock.rQshift8();
1158
- var green_shift = this._sock.rQshift8();
1159
- var blue_shift = this._sock.rQshift8();
1671
+ const bpp = this._sock.rQshift8();
1672
+ const depth = this._sock.rQshift8();
1673
+ const bigEndian = this._sock.rQshift8();
1674
+ const trueColor = this._sock.rQshift8();
1675
+
1676
+ const redMax = this._sock.rQshift16();
1677
+ const greenMax = this._sock.rQshift16();
1678
+ const blueMax = this._sock.rQshift16();
1679
+ const redShift = this._sock.rQshift8();
1680
+ const greenShift = this._sock.rQshift8();
1681
+ const blueShift = this._sock.rQshift8();
1160
1682
  this._sock.rQskipBytes(3); // padding
1161
1683
 
1162
1684
  // NB(directxman12): we don't want to call any callbacks or print messages until
1163
1685
  // *after* we're past the point where we could backtrack
1164
1686
 
1165
1687
  /* Connection name/title */
1166
- var name_length = this._sock.rQshift32();
1167
- if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1168
- this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
1688
+ const nameLength = this._sock.rQshift32();
1689
+ if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
1690
+ let name = this._sock.rQshiftStr(nameLength);
1691
+ name = decodeUTF8(name, true);
1169
1692
 
1170
- if (this._rfb_tightvnc) {
1171
- if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1693
+ if (this._rfbTightVNC) {
1694
+ if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
1172
1695
  // In TightVNC mode, ServerInit message is extended
1173
- var numServerMessages = this._sock.rQshift16();
1174
- var numClientMessages = this._sock.rQshift16();
1175
- var numEncodings = this._sock.rQshift16();
1696
+ const numServerMessages = this._sock.rQshift16();
1697
+ const numClientMessages = this._sock.rQshift16();
1698
+ const numEncodings = this._sock.rQshift16();
1176
1699
  this._sock.rQskipBytes(2); // padding
1177
1700
 
1178
- var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1179
- if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1701
+ const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1702
+ if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
1180
1703
 
1181
1704
  // we don't actually do anything with the capability information that TIGHT sends,
1182
1705
  // so we just skip the all of this.
@@ -1195,76 +1718,53 @@ RFB.prototype = {
1195
1718
  // if we backtrack
1196
1719
  Log.Info("Screen: " + width + "x" + height +
1197
1720
  ", bpp: " + bpp + ", depth: " + depth +
1198
- ", big_endian: " + big_endian +
1199
- ", true_color: " + true_color +
1200
- ", red_max: " + red_max +
1201
- ", green_max: " + green_max +
1202
- ", blue_max: " + blue_max +
1203
- ", red_shift: " + red_shift +
1204
- ", green_shift: " + green_shift +
1205
- ", blue_shift: " + blue_shift);
1206
-
1207
- if (big_endian !== 0) {
1208
- Log.Warn("Server native endian is not little endian");
1209
- }
1210
-
1211
- if (red_shift !== 16) {
1212
- Log.Warn("Server native red-shift is not 16");
1213
- }
1214
-
1215
- if (blue_shift !== 0) {
1216
- Log.Warn("Server native blue-shift is not 0");
1217
- }
1721
+ ", bigEndian: " + bigEndian +
1722
+ ", trueColor: " + trueColor +
1723
+ ", redMax: " + redMax +
1724
+ ", greenMax: " + greenMax +
1725
+ ", blueMax: " + blueMax +
1726
+ ", redShift: " + redShift +
1727
+ ", greenShift: " + greenShift +
1728
+ ", blueShift: " + blueShift);
1218
1729
 
1219
1730
  // we're past the point where we could backtrack, so it's safe to call this
1220
- var event = new CustomEvent("desktopname",
1221
- { detail: { name: this._fb_name } });
1222
- this.dispatchEvent(event);
1223
-
1731
+ this._setDesktopName(name);
1224
1732
  this._resize(width, height);
1225
1733
 
1226
1734
  if (!this._viewOnly) { this._keyboard.grab(); }
1227
- if (!this._viewOnly) { this._mouse.grab(); }
1228
1735
 
1229
- this._fb_depth = 24;
1736
+ this._fbDepth = 24;
1230
1737
 
1231
- if (this._fb_name === "Intel(r) AMT KVM") {
1738
+ if (this._fbName === "Intel(r) AMT KVM") {
1232
1739
  Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1233
- this._fb_depth = 8;
1740
+ this._fbDepth = 8;
1234
1741
  }
1235
1742
 
1236
- RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
1743
+ RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
1237
1744
  this._sendEncodings();
1238
- RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1239
-
1240
- this._timing.fbu_rt_start = (new Date()).getTime();
1241
- this._timing.pixels = 0;
1242
-
1243
- // Cursor will be server side until the server decides to honor
1244
- // our request and send over the cursor image
1245
- this._display.disableLocalCursor();
1745
+ RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
1246
1746
 
1247
1747
  this._updateConnectionState('connected');
1248
1748
  return true;
1249
- },
1749
+ }
1250
1750
 
1251
- _sendEncodings: function () {
1252
- var encs = [];
1751
+ _sendEncodings() {
1752
+ const encs = [];
1253
1753
 
1254
1754
  // In preference order
1255
1755
  encs.push(encodings.encodingCopyRect);
1256
1756
  // Only supported with full depth support
1257
- if (this._fb_depth == 24) {
1757
+ if (this._fbDepth == 24) {
1258
1758
  encs.push(encodings.encodingTight);
1759
+ encs.push(encodings.encodingTightPNG);
1259
1760
  encs.push(encodings.encodingHextile);
1260
1761
  encs.push(encodings.encodingRRE);
1261
1762
  }
1262
1763
  encs.push(encodings.encodingRaw);
1263
1764
 
1264
1765
  // Psuedo-encoding settings
1265
- encs.push(encodings.pseudoEncodingTightPNG);
1266
- encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1267
- encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1766
+ encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
1767
+ encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
1268
1768
 
1269
1769
  encs.push(encodings.pseudoEncodingDesktopSize);
1270
1770
  encs.push(encodings.pseudoEncodingLastRect);
@@ -1273,14 +1773,16 @@ RFB.prototype = {
1273
1773
  encs.push(encodings.pseudoEncodingXvp);
1274
1774
  encs.push(encodings.pseudoEncodingFence);
1275
1775
  encs.push(encodings.pseudoEncodingContinuousUpdates);
1776
+ encs.push(encodings.pseudoEncodingDesktopName);
1777
+ encs.push(encodings.pseudoEncodingExtendedClipboard);
1276
1778
 
1277
- if (supportsCursorURIs() &&
1278
- !isTouchDevice && this._fb_depth == 24) {
1779
+ if (this._fbDepth == 24) {
1780
+ encs.push(encodings.pseudoEncodingVMwareCursor);
1279
1781
  encs.push(encodings.pseudoEncodingCursor);
1280
1782
  }
1281
1783
 
1282
1784
  RFB.messages.clientEncodings(this._sock, encs);
1283
- },
1785
+ }
1284
1786
 
1285
1787
  /* RFB protocol initialization states:
1286
1788
  * ProtocolVersion
@@ -1290,64 +1792,216 @@ RFB.prototype = {
1290
1792
  * ClientInitialization - not triggered by server message
1291
1793
  * ServerInitialization
1292
1794
  */
1293
- _init_msg: function () {
1294
- switch (this._rfb_init_state) {
1795
+ _initMsg() {
1796
+ switch (this._rfbInitState) {
1295
1797
  case 'ProtocolVersion':
1296
- return this._negotiate_protocol_version();
1798
+ return this._negotiateProtocolVersion();
1297
1799
 
1298
1800
  case 'Security':
1299
- return this._negotiate_security();
1801
+ return this._negotiateSecurity();
1300
1802
 
1301
1803
  case 'Authentication':
1302
- return this._negotiate_authentication();
1804
+ return this._negotiateAuthentication();
1303
1805
 
1304
1806
  case 'SecurityResult':
1305
- return this._handle_security_result();
1807
+ return this._handleSecurityResult();
1808
+
1809
+ case 'SecurityReason':
1810
+ return this._handleSecurityReason();
1306
1811
 
1307
1812
  case 'ClientInitialisation':
1308
1813
  this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
1309
- this._rfb_init_state = 'ServerInitialisation';
1814
+ this._rfbInitState = 'ServerInitialisation';
1310
1815
  return true;
1311
1816
 
1312
1817
  case 'ServerInitialisation':
1313
- return this._negotiate_server_init();
1818
+ return this._negotiateServerInit();
1314
1819
 
1315
1820
  default:
1316
1821
  return this._fail("Unknown init state (state: " +
1317
- this._rfb_init_state + ")");
1822
+ this._rfbInitState + ")");
1318
1823
  }
1319
- },
1824
+ }
1320
1825
 
1321
- _handle_set_colour_map_msg: function () {
1826
+ _handleSetColourMapMsg() {
1322
1827
  Log.Debug("SetColorMapEntries");
1323
1828
 
1324
1829
  return this._fail("Unexpected SetColorMapEntries message");
1325
- },
1830
+ }
1326
1831
 
1327
- _handle_server_cut_text: function () {
1832
+ _handleServerCutText() {
1328
1833
  Log.Debug("ServerCutText");
1329
1834
 
1330
1835
  if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1836
+
1331
1837
  this._sock.rQskipBytes(3); // Padding
1332
- var length = this._sock.rQshift32();
1333
- if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
1334
1838
 
1335
- var text = this._sock.rQshiftStr(length);
1839
+ let length = this._sock.rQshift32();
1840
+ length = toSigned32bit(length);
1841
+
1842
+ if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }
1843
+
1844
+ if (length >= 0) {
1845
+ //Standard msg
1846
+ const text = this._sock.rQshiftStr(length);
1847
+ if (this._viewOnly) {
1848
+ return true;
1849
+ }
1850
+
1851
+ this.dispatchEvent(new CustomEvent(
1852
+ "clipboard",
1853
+ { detail: { text: text } }));
1854
+
1855
+ } else {
1856
+ //Extended msg.
1857
+ length = Math.abs(length);
1858
+ const flags = this._sock.rQshift32();
1859
+ let formats = flags & 0x0000FFFF;
1860
+ let actions = flags & 0xFF000000;
1861
+
1862
+ let isCaps = (!!(actions & extendedClipboardActionCaps));
1863
+ if (isCaps) {
1864
+ this._clipboardServerCapabilitiesFormats = {};
1865
+ this._clipboardServerCapabilitiesActions = {};
1866
+
1867
+ // Update our server capabilities for Formats
1868
+ for (let i = 0; i <= 15; i++) {
1869
+ let index = 1 << i;
1870
+
1871
+ // Check if format flag is set.
1872
+ if ((formats & index)) {
1873
+ this._clipboardServerCapabilitiesFormats[index] = true;
1874
+ // We don't send unsolicited clipboard, so we
1875
+ // ignore the size
1876
+ this._sock.rQshift32();
1877
+ }
1878
+ }
1879
+
1880
+ // Update our server capabilities for Actions
1881
+ for (let i = 24; i <= 31; i++) {
1882
+ let index = 1 << i;
1883
+ this._clipboardServerCapabilitiesActions[index] = !!(actions & index);
1884
+ }
1885
+
1886
+ /* Caps handling done, send caps with the clients
1887
+ capabilities set as a response */
1888
+ let clientActions = [
1889
+ extendedClipboardActionCaps,
1890
+ extendedClipboardActionRequest,
1891
+ extendedClipboardActionPeek,
1892
+ extendedClipboardActionNotify,
1893
+ extendedClipboardActionProvide
1894
+ ];
1895
+ RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});
1896
+
1897
+ } else if (actions === extendedClipboardActionRequest) {
1898
+ if (this._viewOnly) {
1899
+ return true;
1900
+ }
1901
+
1902
+ // Check if server has told us it can handle Provide and there is clipboard data to send.
1903
+ if (this._clipboardText != null &&
1904
+ this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {
1905
+
1906
+ if (formats & extendedClipboardFormatText) {
1907
+ RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);
1908
+ }
1909
+ }
1910
+
1911
+ } else if (actions === extendedClipboardActionPeek) {
1912
+ if (this._viewOnly) {
1913
+ return true;
1914
+ }
1915
+
1916
+ if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
1917
+
1918
+ if (this._clipboardText != null) {
1919
+ RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
1920
+ } else {
1921
+ RFB.messages.extendedClipboardNotify(this._sock, []);
1922
+ }
1923
+ }
1924
+
1925
+ } else if (actions === extendedClipboardActionNotify) {
1926
+ if (this._viewOnly) {
1927
+ return true;
1928
+ }
1929
+
1930
+ if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {
1931
+
1932
+ if (formats & extendedClipboardFormatText) {
1933
+ RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);
1934
+ }
1935
+ }
1936
+
1937
+ } else if (actions === extendedClipboardActionProvide) {
1938
+ if (this._viewOnly) {
1939
+ return true;
1940
+ }
1941
+
1942
+ if (!(formats & extendedClipboardFormatText)) {
1943
+ return true;
1944
+ }
1945
+ // Ignore what we had in our clipboard client side.
1946
+ this._clipboardText = null;
1947
+
1948
+ // FIXME: Should probably verify that this data was actually requested
1949
+ let zlibStream = this._sock.rQshiftBytes(length - 4);
1950
+ let streamInflator = new Inflator();
1951
+ let textData = null;
1952
+
1953
+ streamInflator.setInput(zlibStream);
1954
+ for (let i = 0; i <= 15; i++) {
1955
+ let format = 1 << i;
1956
+
1957
+ if (formats & format) {
1336
1958
 
1337
- if (this._viewOnly) { return true; }
1959
+ let size = 0x00;
1960
+ let sizeArray = streamInflator.inflate(4);
1338
1961
 
1339
- var event = new CustomEvent("clipboard",
1340
- { detail: { text: text } });
1341
- this.dispatchEvent(event);
1962
+ size |= (sizeArray[0] << 24);
1963
+ size |= (sizeArray[1] << 16);
1964
+ size |= (sizeArray[2] << 8);
1965
+ size |= (sizeArray[3]);
1966
+ let chunk = streamInflator.inflate(size);
1342
1967
 
1968
+ if (format === extendedClipboardFormatText) {
1969
+ textData = chunk;
1970
+ }
1971
+ }
1972
+ }
1973
+ streamInflator.setInput(null);
1974
+
1975
+ if (textData !== null) {
1976
+ let tmpText = "";
1977
+ for (let i = 0; i < textData.length; i++) {
1978
+ tmpText += String.fromCharCode(textData[i]);
1979
+ }
1980
+ textData = tmpText;
1981
+
1982
+ textData = decodeUTF8(textData);
1983
+ if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {
1984
+ textData = textData.slice(0, -1);
1985
+ }
1986
+
1987
+ textData = textData.replace("\r\n", "\n");
1988
+
1989
+ this.dispatchEvent(new CustomEvent(
1990
+ "clipboard",
1991
+ { detail: { text: textData } }));
1992
+ }
1993
+ } else {
1994
+ return this._fail("Unexpected action in extended clipboard message: " + actions);
1995
+ }
1996
+ }
1343
1997
  return true;
1344
- },
1998
+ }
1345
1999
 
1346
- _handle_server_fence_msg: function() {
2000
+ _handleServerFenceMsg() {
1347
2001
  if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
1348
2002
  this._sock.rQskipBytes(3); // Padding
1349
- var flags = this._sock.rQshift32();
1350
- var length = this._sock.rQshift8();
2003
+ let flags = this._sock.rQshift32();
2004
+ let length = this._sock.rQshift8();
1351
2005
 
1352
2006
  if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
1353
2007
 
@@ -1356,7 +2010,7 @@ RFB.prototype = {
1356
2010
  length = 64;
1357
2011
  }
1358
2012
 
1359
- var payload = this._sock.rQshiftStr(length);
2013
+ const payload = this._sock.rQshiftStr(length);
1360
2014
 
1361
2015
  this._supportsFence = true;
1362
2016
 
@@ -1383,63 +2037,64 @@ RFB.prototype = {
1383
2037
  RFB.messages.clientFence(this._sock, flags, payload);
1384
2038
 
1385
2039
  return true;
1386
- },
2040
+ }
1387
2041
 
1388
- _handle_xvp_msg: function () {
2042
+ _handleXvpMsg() {
1389
2043
  if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
1390
- this._sock.rQskip8(); // Padding
1391
- var xvp_ver = this._sock.rQshift8();
1392
- var xvp_msg = this._sock.rQshift8();
2044
+ this._sock.rQskipBytes(1); // Padding
2045
+ const xvpVer = this._sock.rQshift8();
2046
+ const xvpMsg = this._sock.rQshift8();
1393
2047
 
1394
- switch (xvp_msg) {
2048
+ switch (xvpMsg) {
1395
2049
  case 0: // XVP_FAIL
1396
2050
  Log.Error("XVP Operation Failed");
1397
2051
  break;
1398
2052
  case 1: // XVP_INIT
1399
- this._rfb_xvp_ver = xvp_ver;
1400
- Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
2053
+ this._rfbXvpVer = xvpVer;
2054
+ Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")");
1401
2055
  this._setCapability("power", true);
1402
2056
  break;
1403
2057
  default:
1404
- this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
2058
+ this._fail("Illegal server XVP message (msg: " + xvpMsg + ")");
1405
2059
  break;
1406
2060
  }
1407
2061
 
1408
2062
  return true;
1409
- },
1410
-
1411
- _normal_msg: function () {
1412
- var msg_type;
2063
+ }
1413
2064
 
2065
+ _normalMsg() {
2066
+ let msgType;
1414
2067
  if (this._FBU.rects > 0) {
1415
- msg_type = 0;
2068
+ msgType = 0;
1416
2069
  } else {
1417
- msg_type = this._sock.rQshift8();
2070
+ msgType = this._sock.rQshift8();
1418
2071
  }
1419
2072
 
1420
- switch (msg_type) {
2073
+ let first, ret;
2074
+ switch (msgType) {
1421
2075
  case 0: // FramebufferUpdate
1422
- var ret = this._framebufferUpdate();
2076
+ ret = this._framebufferUpdate();
1423
2077
  if (ret && !this._enabledContinuousUpdates) {
1424
2078
  RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
1425
- this._fb_width, this._fb_height);
2079
+ this._fbWidth, this._fbHeight);
1426
2080
  }
1427
2081
  return ret;
1428
2082
 
1429
2083
  case 1: // SetColorMapEntries
1430
- return this._handle_set_colour_map_msg();
2084
+ return this._handleSetColourMapMsg();
1431
2085
 
1432
2086
  case 2: // Bell
1433
2087
  Log.Debug("Bell");
1434
- var event = new CustomEvent("bell", { detail: {} });
1435
- this.dispatchEvent(event);
2088
+ this.dispatchEvent(new CustomEvent(
2089
+ "bell",
2090
+ { detail: {} }));
1436
2091
  return true;
1437
2092
 
1438
2093
  case 3: // ServerCutText
1439
- return this._handle_server_cut_text();
2094
+ return this._handleServerCutText();
1440
2095
 
1441
2096
  case 150: // EndOfContinuousUpdates
1442
- var first = !(this._supportsContinuousUpdates);
2097
+ first = !this._supportsContinuousUpdates;
1443
2098
  this._supportsContinuousUpdates = true;
1444
2099
  this._enabledContinuousUpdates = false;
1445
2100
  if (first) {
@@ -1453,40 +2108,31 @@ RFB.prototype = {
1453
2108
  return true;
1454
2109
 
1455
2110
  case 248: // ServerFence
1456
- return this._handle_server_fence_msg();
2111
+ return this._handleServerFenceMsg();
1457
2112
 
1458
2113
  case 250: // XVP
1459
- return this._handle_xvp_msg();
2114
+ return this._handleXvpMsg();
1460
2115
 
1461
2116
  default:
1462
- this._fail("Unexpected server message (type " + msg_type + ")");
2117
+ this._fail("Unexpected server message (type " + msgType + ")");
1463
2118
  Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1464
2119
  return true;
1465
2120
  }
1466
- },
2121
+ }
1467
2122
 
1468
- _onFlush: function() {
2123
+ _onFlush() {
1469
2124
  this._flushing = false;
1470
2125
  // Resume processing
1471
- if (this._sock.rQlen() > 0) {
1472
- this._handle_message();
2126
+ if (this._sock.rQlen > 0) {
2127
+ this._handleMessage();
1473
2128
  }
1474
- },
1475
-
1476
- _framebufferUpdate: function () {
1477
- var ret = true;
1478
- var now;
2129
+ }
1479
2130
 
2131
+ _framebufferUpdate() {
1480
2132
  if (this._FBU.rects === 0) {
1481
2133
  if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
1482
- this._sock.rQskip8(); // Padding
2134
+ this._sock.rQskipBytes(1); // Padding
1483
2135
  this._FBU.rects = this._sock.rQshift16();
1484
- this._FBU.bytes = 0;
1485
- this._timing.cur_fbu = 0;
1486
- if (this._timing.fbu_rt_start > 0) {
1487
- now = (new Date()).getTime();
1488
- Log.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
1489
- }
1490
2136
 
1491
2137
  // Make sure the previous frame is fully rendered first
1492
2138
  // to avoid building up an excessive queue
@@ -1498,213 +2144,628 @@ RFB.prototype = {
1498
2144
  }
1499
2145
 
1500
2146
  while (this._FBU.rects > 0) {
1501
- if (this._rfb_connection_state !== 'connected') { return false; }
1502
-
1503
- if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
1504
- if (this._FBU.bytes === 0) {
2147
+ if (this._FBU.encoding === null) {
1505
2148
  if (this._sock.rQwait("rect header", 12)) { return false; }
1506
2149
  /* New FramebufferUpdate */
1507
2150
 
1508
- var hdr = this._sock.rQshiftBytes(12);
2151
+ const hdr = this._sock.rQshiftBytes(12);
1509
2152
  this._FBU.x = (hdr[0] << 8) + hdr[1];
1510
2153
  this._FBU.y = (hdr[2] << 8) + hdr[3];
1511
2154
  this._FBU.width = (hdr[4] << 8) + hdr[5];
1512
2155
  this._FBU.height = (hdr[6] << 8) + hdr[7];
1513
2156
  this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1514
2157
  (hdr[10] << 8) + hdr[11], 10);
1515
-
1516
- if (!this._encHandlers[this._FBU.encoding]) {
1517
- this._fail("Unsupported encoding (encoding: " +
1518
- this._FBU.encoding + ")");
1519
- return false;
1520
- }
1521
- }
1522
-
1523
- this._timing.last_fbu = (new Date()).getTime();
1524
-
1525
- ret = this._encHandlers[this._FBU.encoding]();
1526
-
1527
- now = (new Date()).getTime();
1528
- this._timing.cur_fbu += (now - this._timing.last_fbu);
1529
-
1530
- if (ret) {
1531
- if (!(this._FBU.encoding in this._encStats)) {
1532
- this._encStats[this._FBU.encoding] = [0, 0];
1533
- }
1534
- this._encStats[this._FBU.encoding][0]++;
1535
- this._encStats[this._FBU.encoding][1]++;
1536
- this._timing.pixels += this._FBU.width * this._FBU.height;
1537
2158
  }
1538
2159
 
1539
- if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
1540
- if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
1541
- this._timing.fbu_rt_start > 0) {
1542
- this._timing.full_fbu_total += this._timing.cur_fbu;
1543
- this._timing.full_fbu_cnt++;
1544
- Log.Info("Timing of full FBU, curr: " +
1545
- this._timing.cur_fbu + ", total: " +
1546
- this._timing.full_fbu_total + ", cnt: " +
1547
- this._timing.full_fbu_cnt + ", avg: " +
1548
- (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
1549
- }
1550
-
1551
- if (this._timing.fbu_rt_start > 0) {
1552
- var fbu_rt_diff = now - this._timing.fbu_rt_start;
1553
- this._timing.fbu_rt_total += fbu_rt_diff;
1554
- this._timing.fbu_rt_cnt++;
1555
- Log.Info("full FBU round-trip, cur: " +
1556
- fbu_rt_diff + ", total: " +
1557
- this._timing.fbu_rt_total + ", cnt: " +
1558
- this._timing.fbu_rt_cnt + ", avg: " +
1559
- (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
1560
- this._timing.fbu_rt_start = 0;
1561
- }
2160
+ if (!this._handleRect()) {
2161
+ return false;
1562
2162
  }
1563
2163
 
1564
- if (!ret) { return ret; } // need more data
2164
+ this._FBU.rects--;
2165
+ this._FBU.encoding = null;
1565
2166
  }
1566
2167
 
1567
2168
  this._display.flip();
1568
2169
 
1569
2170
  return true; // We finished this FBU
1570
- },
1571
-
1572
- _updateContinuousUpdates: function() {
1573
- if (!this._enabledContinuousUpdates) { return; }
2171
+ }
1574
2172
 
1575
- RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
1576
- this._fb_width, this._fb_height);
1577
- },
2173
+ _handleRect() {
2174
+ switch (this._FBU.encoding) {
2175
+ case encodings.pseudoEncodingLastRect:
2176
+ this._FBU.rects = 1; // Will be decreased when we return
2177
+ return true;
1578
2178
 
1579
- _resize: function(width, height) {
1580
- this._fb_width = width;
1581
- this._fb_height = height;
2179
+ case encodings.pseudoEncodingVMwareCursor:
2180
+ return this._handleVMwareCursor();
1582
2181
 
1583
- this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
2182
+ case encodings.pseudoEncodingCursor:
2183
+ return this._handleCursor();
1584
2184
 
1585
- this._display.resize(this._fb_width, this._fb_height);
2185
+ case encodings.pseudoEncodingQEMUExtendedKeyEvent:
2186
+ // Old Safari doesn't support creating keyboard events
2187
+ try {
2188
+ const keyboardEvent = document.createEvent("keyboardEvent");
2189
+ if (keyboardEvent.code !== undefined) {
2190
+ this._qemuExtKeyEventSupported = true;
2191
+ }
2192
+ } catch (err) {
2193
+ // Do nothing
2194
+ }
2195
+ return true;
1586
2196
 
1587
- // Adjust the visible viewport based on the new dimensions
1588
- this._updateClip();
1589
- this._updateScale();
2197
+ case encodings.pseudoEncodingDesktopName:
2198
+ return this._handleDesktopName();
1590
2199
 
1591
- this._timing.fbu_rt_start = (new Date()).getTime();
1592
- this._updateContinuousUpdates();
1593
- },
2200
+ case encodings.pseudoEncodingDesktopSize:
2201
+ this._resize(this._FBU.width, this._FBU.height);
2202
+ return true;
1594
2203
 
1595
- _xvpOp: function (ver, op) {
1596
- if (this._rfb_xvp_ver < ver) { return; }
1597
- Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
1598
- RFB.messages.xvpOp(this._sock, ver, op);
1599
- },
1600
- };
2204
+ case encodings.pseudoEncodingExtendedDesktopSize:
2205
+ return this._handleExtendedDesktopSize();
1601
2206
 
1602
- Object.assign(RFB.prototype, EventTargetMixin);
2207
+ default:
2208
+ return this._handleDataRect();
2209
+ }
2210
+ }
1603
2211
 
1604
- // Class Methods
1605
- RFB.messages = {
1606
- keyEvent: function (sock, keysym, down) {
1607
- var buff = sock._sQ;
1608
- var offset = sock._sQlen;
2212
+ _handleVMwareCursor() {
2213
+ const hotx = this._FBU.x; // hotspot-x
2214
+ const hoty = this._FBU.y; // hotspot-y
2215
+ const w = this._FBU.width;
2216
+ const h = this._FBU.height;
2217
+ if (this._sock.rQwait("VMware cursor encoding", 1)) {
2218
+ return false;
2219
+ }
1609
2220
 
1610
- buff[offset] = 4; // msg-type
1611
- buff[offset + 1] = down;
2221
+ const cursorType = this._sock.rQshift8();
1612
2222
 
1613
- buff[offset + 2] = 0;
1614
- buff[offset + 3] = 0;
2223
+ this._sock.rQshift8(); //Padding
1615
2224
 
1616
- buff[offset + 4] = (keysym >> 24);
1617
- buff[offset + 5] = (keysym >> 16);
1618
- buff[offset + 6] = (keysym >> 8);
1619
- buff[offset + 7] = keysym;
2225
+ let rgba;
2226
+ const bytesPerPixel = 4;
1620
2227
 
1621
- sock._sQlen += 8;
1622
- sock.flush();
1623
- },
2228
+ //Classic cursor
2229
+ if (cursorType == 0) {
2230
+ //Used to filter away unimportant bits.
2231
+ //OR is used for correct conversion in js.
2232
+ const PIXEL_MASK = 0xffffff00 | 0;
2233
+ rgba = new Array(w * h * bytesPerPixel);
1624
2234
 
1625
- QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) {
1626
- function getRFBkeycode(xt_scancode) {
1627
- var upperByte = (keycode >> 8);
1628
- var lowerByte = (keycode & 0x00ff);
1629
- if (upperByte === 0xe0 && lowerByte < 0x7f) {
1630
- lowerByte = lowerByte | 0x80;
1631
- return lowerByte;
2235
+ if (this._sock.rQwait("VMware cursor classic encoding",
2236
+ (w * h * bytesPerPixel) * 2, 2)) {
2237
+ return false;
1632
2238
  }
1633
- return xt_scancode;
1634
- }
1635
2239
 
1636
- var buff = sock._sQ;
1637
- var offset = sock._sQlen;
2240
+ let andMask = new Array(w * h);
2241
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2242
+ andMask[pixel] = this._sock.rQshift32();
2243
+ }
1638
2244
 
1639
- buff[offset] = 255; // msg-type
1640
- buff[offset + 1] = 0; // sub msg-type
2245
+ let xorMask = new Array(w * h);
2246
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2247
+ xorMask[pixel] = this._sock.rQshift32();
2248
+ }
1641
2249
 
1642
- buff[offset + 2] = (down >> 8);
1643
- buff[offset + 3] = down;
2250
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2251
+ if (andMask[pixel] == 0) {
2252
+ //Fully opaque pixel
2253
+ let bgr = xorMask[pixel];
2254
+ let r = bgr >> 8 & 0xff;
2255
+ let g = bgr >> 16 & 0xff;
2256
+ let b = bgr >> 24 & 0xff;
2257
+
2258
+ rgba[(pixel * bytesPerPixel) ] = r; //r
2259
+ rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
2260
+ rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
2261
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
2262
+
2263
+ } else if ((andMask[pixel] & PIXEL_MASK) ==
2264
+ PIXEL_MASK) {
2265
+ //Only screen value matters, no mouse colouring
2266
+ if (xorMask[pixel] == 0) {
2267
+ //Transparent pixel
2268
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2269
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2270
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2271
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
2272
+
2273
+ } else if ((xorMask[pixel] & PIXEL_MASK) ==
2274
+ PIXEL_MASK) {
2275
+ //Inverted pixel, not supported in browsers.
2276
+ //Fully opaque instead.
2277
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2278
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2279
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2280
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1644
2281
 
1645
- buff[offset + 4] = (keysym >> 24);
1646
- buff[offset + 5] = (keysym >> 16);
1647
- buff[offset + 6] = (keysym >> 8);
1648
- buff[offset + 7] = keysym;
2282
+ } else {
2283
+ //Unhandled xorMask
2284
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2285
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2286
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2287
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2288
+ }
1649
2289
 
1650
- var RFBkeycode = getRFBkeycode(keycode);
2290
+ } else {
2291
+ //Unhandled andMask
2292
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2293
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2294
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2295
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2296
+ }
2297
+ }
1651
2298
 
1652
- buff[offset + 8] = (RFBkeycode >> 24);
1653
- buff[offset + 9] = (RFBkeycode >> 16);
1654
- buff[offset + 10] = (RFBkeycode >> 8);
1655
- buff[offset + 11] = RFBkeycode;
2299
+ //Alpha cursor.
2300
+ } else if (cursorType == 1) {
2301
+ if (this._sock.rQwait("VMware cursor alpha encoding",
2302
+ (w * h * 4), 2)) {
2303
+ return false;
2304
+ }
1656
2305
 
1657
- sock._sQlen += 12;
1658
- sock.flush();
1659
- },
2306
+ rgba = new Array(w * h * bytesPerPixel);
1660
2307
 
1661
- pointerEvent: function (sock, x, y, mask) {
1662
- var buff = sock._sQ;
1663
- var offset = sock._sQlen;
2308
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2309
+ let data = this._sock.rQshift32();
1664
2310
 
1665
- buff[offset] = 5; // msg-type
2311
+ rgba[(pixel * 4) ] = data >> 24 & 0xff; //r
2312
+ rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
2313
+ rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff; //b
2314
+ rgba[(pixel * 4) + 3 ] = data & 0xff; //a
2315
+ }
1666
2316
 
1667
- buff[offset + 1] = mask;
2317
+ } else {
2318
+ Log.Warn("The given cursor type is not supported: "
2319
+ + cursorType + " given.");
2320
+ return false;
2321
+ }
1668
2322
 
1669
- buff[offset + 2] = x >> 8;
1670
- buff[offset + 3] = x;
2323
+ this._updateCursor(rgba, hotx, hoty, w, h);
1671
2324
 
1672
- buff[offset + 4] = y >> 8;
1673
- buff[offset + 5] = y;
2325
+ return true;
2326
+ }
1674
2327
 
1675
- sock._sQlen += 6;
1676
- sock.flush();
1677
- },
2328
+ _handleCursor() {
2329
+ const hotx = this._FBU.x; // hotspot-x
2330
+ const hoty = this._FBU.y; // hotspot-y
2331
+ const w = this._FBU.width;
2332
+ const h = this._FBU.height;
1678
2333
 
1679
- // TODO(directxman12): make this unicode compatible?
1680
- clientCutText: function (sock, text) {
1681
- var buff = sock._sQ;
1682
- var offset = sock._sQlen;
2334
+ const pixelslength = w * h * 4;
2335
+ const masklength = Math.ceil(w / 8) * h;
1683
2336
 
1684
- buff[offset] = 6; // msg-type
2337
+ let bytes = pixelslength + masklength;
2338
+ if (this._sock.rQwait("cursor encoding", bytes)) {
2339
+ return false;
2340
+ }
1685
2341
 
1686
- buff[offset + 1] = 0; // padding
1687
- buff[offset + 2] = 0; // padding
1688
- buff[offset + 3] = 0; // padding
2342
+ // Decode from BGRX pixels + bit mask to RGBA
2343
+ const pixels = this._sock.rQshiftBytes(pixelslength);
2344
+ const mask = this._sock.rQshiftBytes(masklength);
2345
+ let rgba = new Uint8Array(w * h * 4);
2346
+
2347
+ let pixIdx = 0;
2348
+ for (let y = 0; y < h; y++) {
2349
+ for (let x = 0; x < w; x++) {
2350
+ let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);
2351
+ let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;
2352
+ rgba[pixIdx ] = pixels[pixIdx + 2];
2353
+ rgba[pixIdx + 1] = pixels[pixIdx + 1];
2354
+ rgba[pixIdx + 2] = pixels[pixIdx];
2355
+ rgba[pixIdx + 3] = alpha;
2356
+ pixIdx += 4;
2357
+ }
2358
+ }
1689
2359
 
1690
- var n = text.length;
2360
+ this._updateCursor(rgba, hotx, hoty, w, h);
1691
2361
 
1692
- buff[offset + 4] = n >> 24;
1693
- buff[offset + 5] = n >> 16;
1694
- buff[offset + 6] = n >> 8;
1695
- buff[offset + 7] = n;
2362
+ return true;
2363
+ }
2364
+
2365
+ _handleDesktopName() {
2366
+ if (this._sock.rQwait("DesktopName", 4)) {
2367
+ return false;
2368
+ }
2369
+
2370
+ let length = this._sock.rQshift32();
2371
+
2372
+ if (this._sock.rQwait("DesktopName", length, 4)) {
2373
+ return false;
2374
+ }
2375
+
2376
+ let name = this._sock.rQshiftStr(length);
2377
+ name = decodeUTF8(name, true);
2378
+
2379
+ this._setDesktopName(name);
2380
+
2381
+ return true;
2382
+ }
2383
+
2384
+ _handleExtendedDesktopSize() {
2385
+ if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
2386
+ return false;
2387
+ }
2388
+
2389
+ const numberOfScreens = this._sock.rQpeek8();
2390
+
2391
+ let bytes = 4 + (numberOfScreens * 16);
2392
+ if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
2393
+ return false;
2394
+ }
2395
+
2396
+ const firstUpdate = !this._supportsSetDesktopSize;
2397
+ this._supportsSetDesktopSize = true;
2398
+
2399
+ // Normally we only apply the current resize mode after a
2400
+ // window resize event. However there is no such trigger on the
2401
+ // initial connect. And we don't know if the server supports
2402
+ // resizing until we've gotten here.
2403
+ if (firstUpdate) {
2404
+ this._requestRemoteResize();
2405
+ }
2406
+
2407
+ this._sock.rQskipBytes(1); // number-of-screens
2408
+ this._sock.rQskipBytes(3); // padding
2409
+
2410
+ for (let i = 0; i < numberOfScreens; i += 1) {
2411
+ // Save the id and flags of the first screen
2412
+ if (i === 0) {
2413
+ this._screenID = this._sock.rQshiftBytes(4); // id
2414
+ this._sock.rQskipBytes(2); // x-position
2415
+ this._sock.rQskipBytes(2); // y-position
2416
+ this._sock.rQskipBytes(2); // width
2417
+ this._sock.rQskipBytes(2); // height
2418
+ this._screenFlags = this._sock.rQshiftBytes(4); // flags
2419
+ } else {
2420
+ this._sock.rQskipBytes(16);
2421
+ }
2422
+ }
2423
+
2424
+ /*
2425
+ * The x-position indicates the reason for the change:
2426
+ *
2427
+ * 0 - server resized on its own
2428
+ * 1 - this client requested the resize
2429
+ * 2 - another client requested the resize
2430
+ */
2431
+
2432
+ // We need to handle errors when we requested the resize.
2433
+ if (this._FBU.x === 1 && this._FBU.y !== 0) {
2434
+ let msg = "";
2435
+ // The y-position indicates the status code from the server
2436
+ switch (this._FBU.y) {
2437
+ case 1:
2438
+ msg = "Resize is administratively prohibited";
2439
+ break;
2440
+ case 2:
2441
+ msg = "Out of resources";
2442
+ break;
2443
+ case 3:
2444
+ msg = "Invalid screen layout";
2445
+ break;
2446
+ default:
2447
+ msg = "Unknown reason";
2448
+ break;
2449
+ }
2450
+ Log.Warn("Server did not accept the resize request: "
2451
+ + msg);
2452
+ } else {
2453
+ this._resize(this._FBU.width, this._FBU.height);
2454
+ }
2455
+
2456
+ return true;
2457
+ }
2458
+
2459
+ _handleDataRect() {
2460
+ let decoder = this._decoders[this._FBU.encoding];
2461
+ if (!decoder) {
2462
+ this._fail("Unsupported encoding (encoding: " +
2463
+ this._FBU.encoding + ")");
2464
+ return false;
2465
+ }
2466
+
2467
+ try {
2468
+ return decoder.decodeRect(this._FBU.x, this._FBU.y,
2469
+ this._FBU.width, this._FBU.height,
2470
+ this._sock, this._display,
2471
+ this._fbDepth);
2472
+ } catch (err) {
2473
+ this._fail("Error decoding rect: " + err);
2474
+ return false;
2475
+ }
2476
+ }
2477
+
2478
+ _updateContinuousUpdates() {
2479
+ if (!this._enabledContinuousUpdates) { return; }
2480
+
2481
+ RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
2482
+ this._fbWidth, this._fbHeight);
2483
+ }
1696
2484
 
1697
- for (var i = 0; i < n; i++) {
1698
- buff[offset + 8 + i] = text.charCodeAt(i);
2485
+ _resize(width, height) {
2486
+ this._fbWidth = width;
2487
+ this._fbHeight = height;
2488
+
2489
+ this._display.resize(this._fbWidth, this._fbHeight);
2490
+
2491
+ // Adjust the visible viewport based on the new dimensions
2492
+ this._updateClip();
2493
+ this._updateScale();
2494
+
2495
+ this._updateContinuousUpdates();
2496
+ }
2497
+
2498
+ _xvpOp(ver, op) {
2499
+ if (this._rfbXvpVer < ver) { return; }
2500
+ Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
2501
+ RFB.messages.xvpOp(this._sock, ver, op);
2502
+ }
2503
+
2504
+ _updateCursor(rgba, hotx, hoty, w, h) {
2505
+ this._cursorImage = {
2506
+ rgbaPixels: rgba,
2507
+ hotx: hotx, hoty: hoty, w: w, h: h,
2508
+ };
2509
+ this._refreshCursor();
2510
+ }
2511
+
2512
+ _shouldShowDotCursor() {
2513
+ // Called when this._cursorImage is updated
2514
+ if (!this._showDotCursor) {
2515
+ // User does not want to see the dot, so...
2516
+ return false;
2517
+ }
2518
+
2519
+ // The dot should not be shown if the cursor is already visible,
2520
+ // i.e. contains at least one not-fully-transparent pixel.
2521
+ // So iterate through all alpha bytes in rgba and stop at the
2522
+ // first non-zero.
2523
+ for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
2524
+ if (this._cursorImage.rgbaPixels[i]) {
2525
+ return false;
2526
+ }
1699
2527
  }
1700
2528
 
1701
- sock._sQlen += 8 + n;
2529
+ // At this point, we know that the cursor is fully transparent, and
2530
+ // the user wants to see the dot instead of this.
2531
+ return true;
2532
+ }
2533
+
2534
+ _refreshCursor() {
2535
+ if (this._rfbConnectionState !== "connecting" &&
2536
+ this._rfbConnectionState !== "connected") {
2537
+ return;
2538
+ }
2539
+ const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
2540
+ this._cursor.change(image.rgbaPixels,
2541
+ image.hotx, image.hoty,
2542
+ image.w, image.h
2543
+ );
2544
+ }
2545
+
2546
+ static genDES(password, challenge) {
2547
+ const passwordChars = password.split('').map(c => c.charCodeAt(0));
2548
+ return (new DES(passwordChars)).encrypt(challenge);
2549
+ }
2550
+ }
2551
+
2552
+ // Class Methods
2553
+ RFB.messages = {
2554
+ keyEvent(sock, keysym, down) {
2555
+ const buff = sock._sQ;
2556
+ const offset = sock._sQlen;
2557
+
2558
+ buff[offset] = 4; // msg-type
2559
+ buff[offset + 1] = down;
2560
+
2561
+ buff[offset + 2] = 0;
2562
+ buff[offset + 3] = 0;
2563
+
2564
+ buff[offset + 4] = (keysym >> 24);
2565
+ buff[offset + 5] = (keysym >> 16);
2566
+ buff[offset + 6] = (keysym >> 8);
2567
+ buff[offset + 7] = keysym;
2568
+
2569
+ sock._sQlen += 8;
2570
+ sock.flush();
2571
+ },
2572
+
2573
+ QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
2574
+ function getRFBkeycode(xtScanCode) {
2575
+ const upperByte = (keycode >> 8);
2576
+ const lowerByte = (keycode & 0x00ff);
2577
+ if (upperByte === 0xe0 && lowerByte < 0x7f) {
2578
+ return lowerByte | 0x80;
2579
+ }
2580
+ return xtScanCode;
2581
+ }
2582
+
2583
+ const buff = sock._sQ;
2584
+ const offset = sock._sQlen;
2585
+
2586
+ buff[offset] = 255; // msg-type
2587
+ buff[offset + 1] = 0; // sub msg-type
2588
+
2589
+ buff[offset + 2] = (down >> 8);
2590
+ buff[offset + 3] = down;
2591
+
2592
+ buff[offset + 4] = (keysym >> 24);
2593
+ buff[offset + 5] = (keysym >> 16);
2594
+ buff[offset + 6] = (keysym >> 8);
2595
+ buff[offset + 7] = keysym;
2596
+
2597
+ const RFBkeycode = getRFBkeycode(keycode);
2598
+
2599
+ buff[offset + 8] = (RFBkeycode >> 24);
2600
+ buff[offset + 9] = (RFBkeycode >> 16);
2601
+ buff[offset + 10] = (RFBkeycode >> 8);
2602
+ buff[offset + 11] = RFBkeycode;
2603
+
2604
+ sock._sQlen += 12;
1702
2605
  sock.flush();
1703
2606
  },
1704
2607
 
1705
- setDesktopSize: function (sock, width, height, id, flags) {
1706
- var buff = sock._sQ;
1707
- var offset = sock._sQlen;
2608
+ pointerEvent(sock, x, y, mask) {
2609
+ const buff = sock._sQ;
2610
+ const offset = sock._sQlen;
2611
+
2612
+ buff[offset] = 5; // msg-type
2613
+
2614
+ buff[offset + 1] = mask;
2615
+
2616
+ buff[offset + 2] = x >> 8;
2617
+ buff[offset + 3] = x;
2618
+
2619
+ buff[offset + 4] = y >> 8;
2620
+ buff[offset + 5] = y;
2621
+
2622
+ sock._sQlen += 6;
2623
+ sock.flush();
2624
+ },
2625
+
2626
+ // Used to build Notify and Request data.
2627
+ _buildExtendedClipboardFlags(actions, formats) {
2628
+ let data = new Uint8Array(4);
2629
+ let formatFlag = 0x00000000;
2630
+ let actionFlag = 0x00000000;
2631
+
2632
+ for (let i = 0; i < actions.length; i++) {
2633
+ actionFlag |= actions[i];
2634
+ }
2635
+
2636
+ for (let i = 0; i < formats.length; i++) {
2637
+ formatFlag |= formats[i];
2638
+ }
2639
+
2640
+ data[0] = actionFlag >> 24; // Actions
2641
+ data[1] = 0x00; // Reserved
2642
+ data[2] = 0x00; // Reserved
2643
+ data[3] = formatFlag; // Formats
2644
+
2645
+ return data;
2646
+ },
2647
+
2648
+ extendedClipboardProvide(sock, formats, inData) {
2649
+ // Deflate incomming data and their sizes
2650
+ let deflator = new Deflator();
2651
+ let dataToDeflate = [];
2652
+
2653
+ for (let i = 0; i < formats.length; i++) {
2654
+ // We only support the format Text at this time
2655
+ if (formats[i] != extendedClipboardFormatText) {
2656
+ throw new Error("Unsupported extended clipboard format for Provide message.");
2657
+ }
2658
+
2659
+ // Change lone \r or \n into \r\n as defined in rfbproto
2660
+ inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");
2661
+
2662
+ // Check if it already has \0
2663
+ let text = encodeUTF8(inData[i] + "\0");
2664
+
2665
+ dataToDeflate.push( (text.length >> 24) & 0xFF,
2666
+ (text.length >> 16) & 0xFF,
2667
+ (text.length >> 8) & 0xFF,
2668
+ (text.length & 0xFF));
2669
+
2670
+ for (let j = 0; j < text.length; j++) {
2671
+ dataToDeflate.push(text.charCodeAt(j));
2672
+ }
2673
+ }
2674
+
2675
+ let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));
2676
+
2677
+ // Build data to send
2678
+ let data = new Uint8Array(4 + deflatedData.length);
2679
+ data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],
2680
+ formats));
2681
+ data.set(deflatedData, 4);
2682
+
2683
+ RFB.messages.clientCutText(sock, data, true);
2684
+ },
2685
+
2686
+ extendedClipboardNotify(sock, formats) {
2687
+ let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],
2688
+ formats);
2689
+ RFB.messages.clientCutText(sock, flags, true);
2690
+ },
2691
+
2692
+ extendedClipboardRequest(sock, formats) {
2693
+ let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],
2694
+ formats);
2695
+ RFB.messages.clientCutText(sock, flags, true);
2696
+ },
2697
+
2698
+ extendedClipboardCaps(sock, actions, formats) {
2699
+ let formatKeys = Object.keys(formats);
2700
+ let data = new Uint8Array(4 + (4 * formatKeys.length));
2701
+
2702
+ formatKeys.map(x => parseInt(x));
2703
+ formatKeys.sort((a, b) => a - b);
2704
+
2705
+ data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));
2706
+
2707
+ let loopOffset = 4;
2708
+ for (let i = 0; i < formatKeys.length; i++) {
2709
+ data[loopOffset] = formats[formatKeys[i]] >> 24;
2710
+ data[loopOffset + 1] = formats[formatKeys[i]] >> 16;
2711
+ data[loopOffset + 2] = formats[formatKeys[i]] >> 8;
2712
+ data[loopOffset + 3] = formats[formatKeys[i]] >> 0;
2713
+
2714
+ loopOffset += 4;
2715
+ data[3] |= (1 << formatKeys[i]); // Update our format flags
2716
+ }
2717
+
2718
+ RFB.messages.clientCutText(sock, data, true);
2719
+ },
2720
+
2721
+ clientCutText(sock, data, extended = false) {
2722
+ const buff = sock._sQ;
2723
+ const offset = sock._sQlen;
2724
+
2725
+ buff[offset] = 6; // msg-type
2726
+
2727
+ buff[offset + 1] = 0; // padding
2728
+ buff[offset + 2] = 0; // padding
2729
+ buff[offset + 3] = 0; // padding
2730
+
2731
+ let length;
2732
+ if (extended) {
2733
+ length = toUnsigned32bit(-data.length);
2734
+ } else {
2735
+ length = data.length;
2736
+ }
2737
+
2738
+ buff[offset + 4] = length >> 24;
2739
+ buff[offset + 5] = length >> 16;
2740
+ buff[offset + 6] = length >> 8;
2741
+ buff[offset + 7] = length;
2742
+
2743
+ sock._sQlen += 8;
2744
+
2745
+ // We have to keep track of from where in the data we begin creating the
2746
+ // buffer for the flush in the next iteration.
2747
+ let dataOffset = 0;
2748
+
2749
+ let remaining = data.length;
2750
+ while (remaining > 0) {
2751
+
2752
+ let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
2753
+ for (let i = 0; i < flushSize; i++) {
2754
+ buff[sock._sQlen + i] = data[dataOffset + i];
2755
+ }
2756
+
2757
+ sock._sQlen += flushSize;
2758
+ sock.flush();
2759
+
2760
+ remaining -= flushSize;
2761
+ dataOffset += flushSize;
2762
+ }
2763
+
2764
+ },
2765
+
2766
+ setDesktopSize(sock, width, height, id, flags) {
2767
+ const buff = sock._sQ;
2768
+ const offset = sock._sQlen;
1708
2769
 
1709
2770
  buff[offset] = 251; // msg-type
1710
2771
  buff[offset + 1] = 0; // padding
@@ -1738,9 +2799,9 @@ RFB.messages = {
1738
2799
  sock.flush();
1739
2800
  },
1740
2801
 
1741
- clientFence: function (sock, flags, payload) {
1742
- var buff = sock._sQ;
1743
- var offset = sock._sQlen;
2802
+ clientFence(sock, flags, payload) {
2803
+ const buff = sock._sQ;
2804
+ const offset = sock._sQlen;
1744
2805
 
1745
2806
  buff[offset] = 248; // msg-type
1746
2807
 
@@ -1753,11 +2814,11 @@ RFB.messages = {
1753
2814
  buff[offset + 6] = flags >> 8;
1754
2815
  buff[offset + 7] = flags;
1755
2816
 
1756
- var n = payload.length;
2817
+ const n = payload.length;
1757
2818
 
1758
2819
  buff[offset + 8] = n; // length
1759
2820
 
1760
- for (var i = 0; i < n; i++) {
2821
+ for (let i = 0; i < n; i++) {
1761
2822
  buff[offset + 9 + i] = payload.charCodeAt(i);
1762
2823
  }
1763
2824
 
@@ -1765,9 +2826,9 @@ RFB.messages = {
1765
2826
  sock.flush();
1766
2827
  },
1767
2828
 
1768
- enableContinuousUpdates: function (sock, enable, x, y, width, height) {
1769
- var buff = sock._sQ;
1770
- var offset = sock._sQlen;
2829
+ enableContinuousUpdates(sock, enable, x, y, width, height) {
2830
+ const buff = sock._sQ;
2831
+ const offset = sock._sQlen;
1771
2832
 
1772
2833
  buff[offset] = 150; // msg-type
1773
2834
  buff[offset + 1] = enable; // enable-flag
@@ -1785,11 +2846,11 @@ RFB.messages = {
1785
2846
  sock.flush();
1786
2847
  },
1787
2848
 
1788
- pixelFormat: function (sock, depth, true_color) {
1789
- var buff = sock._sQ;
1790
- var offset = sock._sQlen;
2849
+ pixelFormat(sock, depth, trueColor) {
2850
+ const buff = sock._sQ;
2851
+ const offset = sock._sQlen;
1791
2852
 
1792
- var bpp, bits;
2853
+ let bpp;
1793
2854
 
1794
2855
  if (depth > 16) {
1795
2856
  bpp = 32;
@@ -1799,7 +2860,7 @@ RFB.messages = {
1799
2860
  bpp = 8;
1800
2861
  }
1801
2862
 
1802
- bits = Math.floor(depth/3);
2863
+ const bits = Math.floor(depth/3);
1803
2864
 
1804
2865
  buff[offset] = 0; // msg-type
1805
2866
 
@@ -1810,7 +2871,7 @@ RFB.messages = {
1810
2871
  buff[offset + 4] = bpp; // bits-per-pixel
1811
2872
  buff[offset + 5] = depth; // depth
1812
2873
  buff[offset + 6] = 0; // little-endian
1813
- buff[offset + 7] = true_color ? 1 : 0; // true-color
2874
+ buff[offset + 7] = trueColor ? 1 : 0; // true-color
1814
2875
 
1815
2876
  buff[offset + 8] = 0; // red-max
1816
2877
  buff[offset + 9] = (1 << bits) - 1; // red-max
@@ -1833,9 +2894,9 @@ RFB.messages = {
1833
2894
  sock.flush();
1834
2895
  },
1835
2896
 
1836
- clientEncodings: function (sock, encodings) {
1837
- var buff = sock._sQ;
1838
- var offset = sock._sQlen;
2897
+ clientEncodings(sock, encodings) {
2898
+ const buff = sock._sQ;
2899
+ const offset = sock._sQlen;
1839
2900
 
1840
2901
  buff[offset] = 2; // msg-type
1841
2902
  buff[offset + 1] = 0; // padding
@@ -1843,9 +2904,9 @@ RFB.messages = {
1843
2904
  buff[offset + 2] = encodings.length >> 8;
1844
2905
  buff[offset + 3] = encodings.length;
1845
2906
 
1846
- var i, j = offset + 4;
1847
- for (i = 0; i < encodings.length; i++) {
1848
- var enc = encodings[i];
2907
+ let j = offset + 4;
2908
+ for (let i = 0; i < encodings.length; i++) {
2909
+ const enc = encodings[i];
1849
2910
  buff[j] = enc >> 24;
1850
2911
  buff[j + 1] = enc >> 16;
1851
2912
  buff[j + 2] = enc >> 8;
@@ -1858,9 +2919,9 @@ RFB.messages = {
1858
2919
  sock.flush();
1859
2920
  },
1860
2921
 
1861
- fbUpdateRequest: function (sock, incremental, x, y, w, h) {
1862
- var buff = sock._sQ;
1863
- var offset = sock._sQlen;
2922
+ fbUpdateRequest(sock, incremental, x, y, w, h) {
2923
+ const buff = sock._sQ;
2924
+ const offset = sock._sQlen;
1864
2925
 
1865
2926
  if (typeof(x) === "undefined") { x = 0; }
1866
2927
  if (typeof(y) === "undefined") { y = 0; }
@@ -1884,9 +2945,9 @@ RFB.messages = {
1884
2945
  sock.flush();
1885
2946
  },
1886
2947
 
1887
- xvpOp: function (sock, ver, op) {
1888
- var buff = sock._sQ;
1889
- var offset = sock._sQlen;
2948
+ xvpOp(sock, ver, op) {
2949
+ const buff = sock._sQ;
2950
+ const offset = sock._sQlen;
1890
2951
 
1891
2952
  buff[offset] = 250; // msg-type
1892
2953
  buff[offset + 1] = 0; // padding
@@ -1896,645 +2957,25 @@ RFB.messages = {
1896
2957
 
1897
2958
  sock._sQlen += 4;
1898
2959
  sock.flush();
1899
- },
1900
- };
1901
-
1902
- RFB.genDES = function (password, challenge) {
1903
- var passwd = [];
1904
- for (var i = 0; i < password.length; i++) {
1905
- passwd.push(password.charCodeAt(i));
1906
2960
  }
1907
- return (new DES(passwd)).encrypt(challenge);
1908
2961
  };
1909
2962
 
1910
- RFB.encodingHandlers = {
1911
- RAW: function () {
1912
- if (this._FBU.lines === 0) {
1913
- this._FBU.lines = this._FBU.height;
1914
- }
1915
-
1916
- var pixelSize = this._fb_depth == 8 ? 1 : 4;
1917
- this._FBU.bytes = this._FBU.width * pixelSize; // at least a line
1918
- if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
1919
- var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
1920
- var curr_height = Math.min(this._FBU.lines,
1921
- Math.floor(this._sock.rQlen() / (this._FBU.width * pixelSize)));
1922
- var data = this._sock.get_rQ();
1923
- var index = this._sock.get_rQi();
1924
- if (this._fb_depth == 8) {
1925
- var pixels = this._FBU.width * curr_height
1926
- var newdata = new Uint8Array(pixels * 4);
1927
- var i;
1928
- for (i = 0;i < pixels;i++) {
1929
- newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
1930
- newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
1931
- newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
1932
- newdata[i * 4 + 4] = 0;
1933
- }
1934
- data = newdata;
1935
- index = 0;
1936
- }
1937
- this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
1938
- curr_height, data, index);
1939
- this._sock.rQskipBytes(this._FBU.width * curr_height * pixelSize);
1940
- this._FBU.lines -= curr_height;
1941
-
1942
- if (this._FBU.lines > 0) {
1943
- this._FBU.bytes = this._FBU.width * pixelSize; // At least another line
1944
- } else {
1945
- this._FBU.rects--;
1946
- this._FBU.bytes = 0;
1947
- }
1948
-
1949
- return true;
1950
- },
1951
-
1952
- COPYRECT: function () {
1953
- this._FBU.bytes = 4;
1954
- if (this._sock.rQwait("COPYRECT", 4)) { return false; }
1955
- this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
1956
- this._FBU.x, this._FBU.y, this._FBU.width,
1957
- this._FBU.height);
1958
-
1959
- this._FBU.rects--;
1960
- this._FBU.bytes = 0;
1961
- return true;
1962
- },
1963
-
1964
- RRE: function () {
1965
- var color;
1966
- if (this._FBU.subrects === 0) {
1967
- this._FBU.bytes = 4 + 4;
1968
- if (this._sock.rQwait("RRE", 4 + 4)) { return false; }
1969
- this._FBU.subrects = this._sock.rQshift32();
1970
- color = this._sock.rQshiftBytes(4); // Background
1971
- this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
1972
- }
1973
-
1974
- while (this._FBU.subrects > 0 && this._sock.rQlen() >= (4 + 8)) {
1975
- color = this._sock.rQshiftBytes(4);
1976
- var x = this._sock.rQshift16();
1977
- var y = this._sock.rQshift16();
1978
- var width = this._sock.rQshift16();
1979
- var height = this._sock.rQshift16();
1980
- this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
1981
- this._FBU.subrects--;
1982
- }
1983
-
1984
- if (this._FBU.subrects > 0) {
1985
- var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
1986
- this._FBU.bytes = (4 + 8) * chunk;
1987
- } else {
1988
- this._FBU.rects--;
1989
- this._FBU.bytes = 0;
1990
- }
1991
-
1992
- return true;
1993
- },
1994
-
1995
- HEXTILE: function () {
1996
- var rQ = this._sock.get_rQ();
1997
- var rQi = this._sock.get_rQi();
1998
-
1999
- if (this._FBU.tiles === 0) {
2000
- this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
2001
- this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
2002
- this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
2003
- this._FBU.tiles = this._FBU.total_tiles;
2004
- }
2005
-
2006
- while (this._FBU.tiles > 0) {
2007
- this._FBU.bytes = 1;
2008
- if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
2009
- var subencoding = rQ[rQi]; // Peek
2010
- if (subencoding > 30) { // Raw
2011
- this._fail("Illegal hextile subencoding (subencoding: " +
2012
- subencoding + ")");
2013
- return false;
2014
- }
2015
-
2016
- var subrects = 0;
2017
- var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
2018
- var tile_x = curr_tile % this._FBU.tiles_x;
2019
- var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
2020
- var x = this._FBU.x + tile_x * 16;
2021
- var y = this._FBU.y + tile_y * 16;
2022
- var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
2023
- var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
2024
-
2025
- // Figure out how much we are expecting
2026
- if (subencoding & 0x01) { // Raw
2027
- this._FBU.bytes += w * h * 4;
2028
- } else {
2029
- if (subencoding & 0x02) { // Background
2030
- this._FBU.bytes += 4;
2031
- }
2032
- if (subencoding & 0x04) { // Foreground
2033
- this._FBU.bytes += 4;
2034
- }
2035
- if (subencoding & 0x08) { // AnySubrects
2036
- this._FBU.bytes++; // Since we aren't shifting it off
2037
- if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
2038
- subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
2039
- if (subencoding & 0x10) { // SubrectsColoured
2040
- this._FBU.bytes += subrects * (4 + 2);
2041
- } else {
2042
- this._FBU.bytes += subrects * 2;
2043
- }
2044
- }
2045
- }
2046
-
2047
- if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
2048
-
2049
- // We know the encoding and have a whole tile
2050
- this._FBU.subencoding = rQ[rQi];
2051
- rQi++;
2052
- if (this._FBU.subencoding === 0) {
2053
- if (this._FBU.lastsubencoding & 0x01) {
2054
- // Weird: ignore blanks are RAW
2055
- Log.Debug(" Ignoring blank after RAW");
2056
- } else {
2057
- this._display.fillRect(x, y, w, h, this._FBU.background);
2058
- }
2059
- } else if (this._FBU.subencoding & 0x01) { // Raw
2060
- this._display.blitImage(x, y, w, h, rQ, rQi);
2061
- rQi += this._FBU.bytes - 1;
2062
- } else {
2063
- if (this._FBU.subencoding & 0x02) { // Background
2064
- this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
2065
- rQi += 4;
2066
- }
2067
- if (this._FBU.subencoding & 0x04) { // Foreground
2068
- this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
2069
- rQi += 4;
2070
- }
2071
-
2072
- this._display.startTile(x, y, w, h, this._FBU.background);
2073
- if (this._FBU.subencoding & 0x08) { // AnySubrects
2074
- subrects = rQ[rQi];
2075
- rQi++;
2076
-
2077
- for (var s = 0; s < subrects; s++) {
2078
- var color;
2079
- if (this._FBU.subencoding & 0x10) { // SubrectsColoured
2080
- color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
2081
- rQi += 4;
2082
- } else {
2083
- color = this._FBU.foreground;
2084
- }
2085
- var xy = rQ[rQi];
2086
- rQi++;
2087
- var sx = (xy >> 4);
2088
- var sy = (xy & 0x0f);
2089
-
2090
- var wh = rQ[rQi];
2091
- rQi++;
2092
- var sw = (wh >> 4) + 1;
2093
- var sh = (wh & 0x0f) + 1;
2094
-
2095
- this._display.subTile(sx, sy, sw, sh, color);
2096
- }
2097
- }
2098
- this._display.finishTile();
2099
- }
2100
- this._sock.set_rQi(rQi);
2101
- this._FBU.lastsubencoding = this._FBU.subencoding;
2102
- this._FBU.bytes = 0;
2103
- this._FBU.tiles--;
2104
- }
2105
-
2106
- if (this._FBU.tiles === 0) {
2107
- this._FBU.rects--;
2108
- }
2109
-
2110
- return true;
2111
- },
2112
-
2113
- TIGHT: function () {
2114
- this._FBU.bytes = 1; // compression-control byte
2115
- if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
2116
-
2117
- var checksum = function (data) {
2118
- var sum = 0;
2119
- for (var i = 0; i < data.length; i++) {
2120
- sum += data[i];
2121
- if (sum > 65536) sum -= 65536;
2122
- }
2123
- return sum;
2124
- };
2125
-
2126
- var resetStreams = 0;
2127
- var streamId = -1;
2128
- var decompress = function (data, expected) {
2129
- for (var i = 0; i < 4; i++) {
2130
- if ((resetStreams >> i) & 1) {
2131
- this._FBU.zlibs[i].reset();
2132
- Log.Info("Reset zlib stream " + i);
2133
- }
2134
- }
2135
-
2136
- //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
2137
- var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
2138
- /*if (uncompressed.status !== 0) {
2139
- Log.Error("Invalid data in zlib stream");
2140
- }*/
2141
-
2142
- //return uncompressed.data;
2143
- return uncompressed;
2144
- }.bind(this);
2145
-
2146
- var indexedToRGBX2Color = function (data, palette, width, height) {
2147
- // Convert indexed (palette based) image data to RGB
2148
- // TODO: reduce number of calculations inside loop
2149
- var dest = this._destBuff;
2150
- var w = Math.floor((width + 7) / 8);
2151
- var w1 = Math.floor(width / 8);
2152
-
2153
- /*for (var y = 0; y < height; y++) {
2154
- var b, x, dp, sp;
2155
- var yoffset = y * width;
2156
- var ybitoffset = y * w;
2157
- var xoffset, targetbyte;
2158
- for (x = 0; x < w1; x++) {
2159
- xoffset = yoffset + x * 8;
2160
- targetbyte = data[ybitoffset + x];
2161
- for (b = 7; b >= 0; b--) {
2162
- dp = (xoffset + 7 - b) * 3;
2163
- sp = (targetbyte >> b & 1) * 3;
2164
- dest[dp] = palette[sp];
2165
- dest[dp + 1] = palette[sp + 1];
2166
- dest[dp + 2] = palette[sp + 2];
2167
- }
2168
- }
2169
-
2170
- xoffset = yoffset + x * 8;
2171
- targetbyte = data[ybitoffset + x];
2172
- for (b = 7; b >= 8 - width % 8; b--) {
2173
- dp = (xoffset + 7 - b) * 3;
2174
- sp = (targetbyte >> b & 1) * 3;
2175
- dest[dp] = palette[sp];
2176
- dest[dp + 1] = palette[sp + 1];
2177
- dest[dp + 2] = palette[sp + 2];
2178
- }
2179
- }*/
2180
-
2181
- for (var y = 0; y < height; y++) {
2182
- var b, x, dp, sp;
2183
- for (x = 0; x < w1; x++) {
2184
- for (b = 7; b >= 0; b--) {
2185
- dp = (y * width + x * 8 + 7 - b) * 4;
2186
- sp = (data[y * w + x] >> b & 1) * 3;
2187
- dest[dp] = palette[sp];
2188
- dest[dp + 1] = palette[sp + 1];
2189
- dest[dp + 2] = palette[sp + 2];
2190
- dest[dp + 3] = 255;
2191
- }
2192
- }
2193
-
2194
- for (b = 7; b >= 8 - width % 8; b--) {
2195
- dp = (y * width + x * 8 + 7 - b) * 4;
2196
- sp = (data[y * w + x] >> b & 1) * 3;
2197
- dest[dp] = palette[sp];
2198
- dest[dp + 1] = palette[sp + 1];
2199
- dest[dp + 2] = palette[sp + 2];
2200
- dest[dp + 3] = 255;
2201
- }
2202
- }
2203
-
2204
- return dest;
2205
- }.bind(this);
2206
-
2207
- var indexedToRGBX = function (data, palette, width, height) {
2208
- // Convert indexed (palette based) image data to RGB
2209
- var dest = this._destBuff;
2210
- var total = width * height * 4;
2211
- for (var i = 0, j = 0; i < total; i += 4, j++) {
2212
- var sp = data[j] * 3;
2213
- dest[i] = palette[sp];
2214
- dest[i + 1] = palette[sp + 1];
2215
- dest[i + 2] = palette[sp + 2];
2216
- dest[i + 3] = 255;
2217
- }
2218
-
2219
- return dest;
2220
- }.bind(this);
2221
-
2222
- var rQi = this._sock.get_rQi();
2223
- var rQ = this._sock.rQwhole();
2224
- var cmode, data;
2225
- var cl_header, cl_data;
2226
-
2227
- var handlePalette = function () {
2228
- var numColors = rQ[rQi + 2] + 1;
2229
- var paletteSize = numColors * 3;
2230
- this._FBU.bytes += paletteSize;
2231
- if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
2232
-
2233
- var bpp = (numColors <= 2) ? 1 : 8;
2234
- var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
2235
- var raw = false;
2236
- if (rowSize * this._FBU.height < 12) {
2237
- raw = true;
2238
- cl_header = 0;
2239
- cl_data = rowSize * this._FBU.height;
2240
- //clength = [0, rowSize * this._FBU.height];
2241
- } else {
2242
- // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
2243
- var cl_offset = rQi + 3 + paletteSize;
2244
- cl_header = 1;
2245
- cl_data = 0;
2246
- cl_data += rQ[cl_offset] & 0x7f;
2247
- if (rQ[cl_offset] & 0x80) {
2248
- cl_header++;
2249
- cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2250
- if (rQ[cl_offset + 1] & 0x80) {
2251
- cl_header++;
2252
- cl_data += rQ[cl_offset + 2] << 14;
2253
- }
2254
- }
2255
- // end inline getTightCLength
2256
- }
2257
-
2258
- this._FBU.bytes += cl_header + cl_data;
2259
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2260
-
2261
- // Shift ctl, filter id, num colors, palette entries, and clength off
2262
- this._sock.rQskipBytes(3);
2263
- //var palette = this._sock.rQshiftBytes(paletteSize);
2264
- this._sock.rQshiftTo(this._paletteBuff, paletteSize);
2265
- this._sock.rQskipBytes(cl_header);
2266
-
2267
- if (raw) {
2268
- data = this._sock.rQshiftBytes(cl_data);
2269
- } else {
2270
- data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
2271
- }
2272
-
2273
- // Convert indexed (palette based) image data to RGB
2274
- var rgbx;
2275
- if (numColors == 2) {
2276
- rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
2277
- this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
2278
- } else {
2279
- rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
2280
- this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
2281
- }
2282
-
2283
-
2284
- return true;
2285
- }.bind(this);
2286
-
2287
- var handleCopy = function () {
2288
- var raw = false;
2289
- var uncompressedSize = this._FBU.width * this._FBU.height * 3;
2290
- if (uncompressedSize < 12) {
2291
- raw = true;
2292
- cl_header = 0;
2293
- cl_data = uncompressedSize;
2294
- } else {
2295
- // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
2296
- var cl_offset = rQi + 1;
2297
- cl_header = 1;
2298
- cl_data = 0;
2299
- cl_data += rQ[cl_offset] & 0x7f;
2300
- if (rQ[cl_offset] & 0x80) {
2301
- cl_header++;
2302
- cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2303
- if (rQ[cl_offset + 1] & 0x80) {
2304
- cl_header++;
2305
- cl_data += rQ[cl_offset + 2] << 14;
2306
- }
2307
- }
2308
- // end inline getTightCLength
2309
- }
2310
- this._FBU.bytes = 1 + cl_header + cl_data;
2311
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2312
-
2313
- // Shift ctl, clength off
2314
- this._sock.rQshiftBytes(1 + cl_header);
2315
-
2316
- if (raw) {
2317
- data = this._sock.rQshiftBytes(cl_data);
2318
- } else {
2319
- data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
2320
- }
2321
-
2322
- this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
2323
-
2324
- return true;
2325
- }.bind(this);
2326
-
2327
- var ctl = this._sock.rQpeek8();
2328
-
2329
- // Keep tight reset bits
2330
- resetStreams = ctl & 0xF;
2331
-
2332
- // Figure out filter
2333
- ctl = ctl >> 4;
2334
- streamId = ctl & 0x3;
2335
-
2336
- if (ctl === 0x08) cmode = "fill";
2337
- else if (ctl === 0x09) cmode = "jpeg";
2338
- else if (ctl === 0x0A) cmode = "png";
2339
- else if (ctl & 0x04) cmode = "filter";
2340
- else if (ctl < 0x04) cmode = "copy";
2341
- else return this._fail("Illegal tight compression received (ctl: " +
2342
- ctl + ")");
2343
-
2344
- switch (cmode) {
2345
- // fill use depth because TPIXELs drop the padding byte
2346
- case "fill": // TPIXEL
2347
- this._FBU.bytes += 3;
2348
- break;
2349
- case "jpeg": // max clength
2350
- this._FBU.bytes += 3;
2351
- break;
2352
- case "png": // max clength
2353
- this._FBU.bytes += 3;
2354
- break;
2355
- case "filter": // filter id + num colors if palette
2356
- this._FBU.bytes += 2;
2357
- break;
2358
- case "copy":
2359
- break;
2360
- }
2361
-
2362
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2363
-
2364
- // Determine FBU.bytes
2365
- switch (cmode) {
2366
- case "fill":
2367
- // skip ctl byte
2368
- this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false);
2369
- this._sock.rQskipBytes(4);
2370
- break;
2371
- case "png":
2372
- case "jpeg":
2373
- // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
2374
- var cl_offset = rQi + 1;
2375
- cl_header = 1;
2376
- cl_data = 0;
2377
- cl_data += rQ[cl_offset] & 0x7f;
2378
- if (rQ[cl_offset] & 0x80) {
2379
- cl_header++;
2380
- cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2381
- if (rQ[cl_offset + 1] & 0x80) {
2382
- cl_header++;
2383
- cl_data += rQ[cl_offset + 2] << 14;
2384
- }
2385
- }
2386
- // end inline getTightCLength
2387
- this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
2388
- if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2389
-
2390
- // We have everything, render it
2391
- this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
2392
- data = this._sock.rQshiftBytes(cl_data);
2393
- this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data);
2394
- break;
2395
- case "filter":
2396
- var filterId = rQ[rQi + 1];
2397
- if (filterId === 1) {
2398
- if (!handlePalette()) { return false; }
2399
- } else {
2400
- // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
2401
- // Filter 2, Gradient is valid but not use if jpeg is enabled
2402
- this._fail("Unsupported tight subencoding received " +
2403
- "(filter: " + filterId + ")");
2404
- }
2405
- break;
2406
- case "copy":
2407
- if (!handleCopy()) { return false; }
2408
- break;
2409
- }
2410
-
2411
-
2412
- this._FBU.bytes = 0;
2413
- this._FBU.rects--;
2414
-
2415
- return true;
2416
- },
2417
-
2418
- last_rect: function () {
2419
- this._FBU.rects = 0;
2420
- return true;
2421
- },
2422
-
2423
- ExtendedDesktopSize: function () {
2424
- this._FBU.bytes = 1;
2425
- if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
2426
-
2427
- var firstUpdate = !this._supportsSetDesktopSize;
2428
- this._supportsSetDesktopSize = true;
2429
-
2430
- // Normally we only apply the current resize mode after a
2431
- // window resize event. However there is no such trigger on the
2432
- // initial connect. And we don't know if the server supports
2433
- // resizing until we've gotten here.
2434
- if (firstUpdate) {
2435
- this._requestRemoteResize();
2436
- }
2437
-
2438
- var number_of_screens = this._sock.rQpeek8();
2439
-
2440
- this._FBU.bytes = 4 + (number_of_screens * 16);
2441
- if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
2442
-
2443
- this._sock.rQskipBytes(1); // number-of-screens
2444
- this._sock.rQskipBytes(3); // padding
2445
-
2446
- for (var i = 0; i < number_of_screens; i += 1) {
2447
- // Save the id and flags of the first screen
2448
- if (i === 0) {
2449
- this._screen_id = this._sock.rQshiftBytes(4); // id
2450
- this._sock.rQskipBytes(2); // x-position
2451
- this._sock.rQskipBytes(2); // y-position
2452
- this._sock.rQskipBytes(2); // width
2453
- this._sock.rQskipBytes(2); // height
2454
- this._screen_flags = this._sock.rQshiftBytes(4); // flags
2455
- } else {
2456
- this._sock.rQskipBytes(16);
2457
- }
2458
- }
2459
-
2460
- /*
2461
- * The x-position indicates the reason for the change:
2462
- *
2463
- * 0 - server resized on its own
2464
- * 1 - this client requested the resize
2465
- * 2 - another client requested the resize
2466
- */
2467
-
2468
- // We need to handle errors when we requested the resize.
2469
- if (this._FBU.x === 1 && this._FBU.y !== 0) {
2470
- var msg = "";
2471
- // The y-position indicates the status code from the server
2472
- switch (this._FBU.y) {
2473
- case 1:
2474
- msg = "Resize is administratively prohibited";
2475
- break;
2476
- case 2:
2477
- msg = "Out of resources";
2478
- break;
2479
- case 3:
2480
- msg = "Invalid screen layout";
2481
- break;
2482
- default:
2483
- msg = "Unknown reason";
2484
- break;
2485
- }
2486
- Log.Warn("Server did not accept the resize request: "
2487
- + msg);
2488
- } else {
2489
- this._resize(this._FBU.width, this._FBU.height);
2490
- }
2491
-
2492
- this._FBU.bytes = 0;
2493
- this._FBU.rects -= 1;
2494
- return true;
2495
- },
2496
-
2497
- DesktopSize: function () {
2498
- this._resize(this._FBU.width, this._FBU.height);
2499
- this._FBU.bytes = 0;
2500
- this._FBU.rects -= 1;
2501
- return true;
2502
- },
2503
-
2504
- Cursor: function () {
2505
- Log.Debug(">> set_cursor");
2506
- var x = this._FBU.x; // hotspot-x
2507
- var y = this._FBU.y; // hotspot-y
2508
- var w = this._FBU.width;
2509
- var h = this._FBU.height;
2510
-
2511
- var pixelslength = w * h * 4;
2512
- var masklength = Math.floor((w + 7) / 8) * h;
2513
-
2514
- this._FBU.bytes = pixelslength + masklength;
2515
- if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
2516
-
2517
- this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
2518
- this._sock.rQshiftBytes(masklength),
2519
- x, y, w, h);
2520
-
2521
- this._FBU.bytes = 0;
2522
- this._FBU.rects--;
2523
-
2524
- Log.Debug("<< set_cursor");
2525
- return true;
2526
- },
2527
-
2528
- QEMUExtendedKeyEvent: function () {
2529
- this._FBU.rects--;
2530
-
2531
- // Old Safari doesn't support creating keyboard events
2532
- try {
2533
- var keyboardEvent = document.createEvent("keyboardEvent");
2534
- if (keyboardEvent.code !== undefined) {
2535
- this._qemuExtKeyEventSupported = true;
2536
- }
2537
- } catch (err) {
2538
- }
2539
- },
2963
+ RFB.cursors = {
2964
+ none: {
2965
+ rgbaPixels: new Uint8Array(),
2966
+ w: 0, h: 0,
2967
+ hotx: 0, hoty: 0,
2968
+ },
2969
+
2970
+ dot: {
2971
+ /* eslint-disable indent */
2972
+ rgbaPixels: new Uint8Array([
2973
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2974
+ 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2975
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2976
+ ]),
2977
+ /* eslint-enable indent */
2978
+ w: 3, h: 3,
2979
+ hotx: 1, hoty: 1,
2980
+ }
2540
2981
  };