susi-qemu 0.0.3 → 0.0.6

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/bin/susi +9 -4
  3. data/lib/disk.rb +7 -5
  4. data/lib/novnc/core/base64.js +104 -0
  5. data/lib/novnc/core/crypto/aes.js +178 -0
  6. data/lib/novnc/core/crypto/bigint.js +34 -0
  7. data/lib/novnc/core/crypto/crypto.js +90 -0
  8. data/lib/novnc/core/crypto/des.js +330 -0
  9. data/lib/novnc/core/crypto/dh.js +55 -0
  10. data/lib/novnc/core/crypto/md5.js +82 -0
  11. data/lib/novnc/core/crypto/rsa.js +132 -0
  12. data/lib/novnc/core/decoders/copyrect.js +27 -0
  13. data/lib/novnc/core/decoders/h264.js +321 -0
  14. data/lib/novnc/core/decoders/hextile.js +181 -0
  15. data/lib/novnc/core/decoders/jpeg.js +146 -0
  16. data/lib/novnc/core/decoders/raw.js +59 -0
  17. data/lib/novnc/core/decoders/rre.js +44 -0
  18. data/lib/novnc/core/decoders/tight.js +393 -0
  19. data/lib/novnc/core/decoders/tightpng.js +27 -0
  20. data/lib/novnc/core/decoders/zlib.js +51 -0
  21. data/lib/novnc/core/decoders/zrle.js +185 -0
  22. data/lib/novnc/core/deflator.js +84 -0
  23. data/lib/novnc/core/display.js +575 -0
  24. data/lib/novnc/core/encodings.js +53 -0
  25. data/lib/novnc/core/inflator.js +65 -0
  26. data/lib/novnc/core/input/domkeytable.js +311 -0
  27. data/lib/novnc/core/input/fixedkeys.js +129 -0
  28. data/lib/novnc/core/input/gesturehandler.js +567 -0
  29. data/lib/novnc/core/input/keyboard.js +294 -0
  30. data/lib/novnc/core/input/keysym.js +616 -0
  31. data/lib/novnc/core/input/keysymdef.js +688 -0
  32. data/lib/novnc/core/input/util.js +191 -0
  33. data/lib/novnc/core/input/vkeys.js +116 -0
  34. data/lib/novnc/core/input/xtscancodes.js +173 -0
  35. data/lib/novnc/core/ra2.js +312 -0
  36. data/lib/novnc/core/rfb.js +3257 -0
  37. data/lib/novnc/core/util/browser.js +172 -0
  38. data/lib/novnc/core/util/cursor.js +249 -0
  39. data/lib/novnc/core/util/element.js +32 -0
  40. data/lib/novnc/core/util/events.js +138 -0
  41. data/lib/novnc/core/util/eventtarget.js +35 -0
  42. data/lib/novnc/core/util/int.js +15 -0
  43. data/lib/novnc/core/util/logging.js +56 -0
  44. data/lib/novnc/core/util/strings.js +28 -0
  45. data/lib/novnc/core/websock.js +365 -0
  46. data/lib/novnc/screen.html +21 -0
  47. data/lib/novnc/vendor/pako/lib/utils/common.js +45 -0
  48. data/lib/novnc/vendor/pako/lib/zlib/adler32.js +27 -0
  49. data/lib/novnc/vendor/pako/lib/zlib/constants.js +47 -0
  50. data/lib/novnc/vendor/pako/lib/zlib/crc32.js +36 -0
  51. data/lib/novnc/vendor/pako/lib/zlib/deflate.js +1846 -0
  52. data/lib/novnc/vendor/pako/lib/zlib/gzheader.js +35 -0
  53. data/lib/novnc/vendor/pako/lib/zlib/inffast.js +324 -0
  54. data/lib/novnc/vendor/pako/lib/zlib/inflate.js +1527 -0
  55. data/lib/novnc/vendor/pako/lib/zlib/inftrees.js +322 -0
  56. data/lib/novnc/vendor/pako/lib/zlib/messages.js +11 -0
  57. data/lib/novnc/vendor/pako/lib/zlib/trees.js +1195 -0
  58. data/lib/novnc/vendor/pako/lib/zlib/zstream.js +24 -0
  59. data/lib/output.rb +11 -0
  60. data/lib/qmp.rb +6 -0
  61. data/lib/ssh.rb +3 -1
  62. data/lib/susi.rb +7 -6
  63. data/lib/version.rb +1 -1
  64. data/lib/vm.rb +36 -13
  65. data/lib/vnc.rb +34 -30
  66. metadata +85 -1
@@ -0,0 +1,3257 @@
1
+ /*
2
+ * noVNC: HTML5 VNC client
3
+ * Copyright (C) 2020 The noVNC Authors
4
+ * Licensed under MPL 2.0 (see LICENSE.txt)
5
+ *
6
+ * See README.md for usage and integration instructions.
7
+ *
8
+ */
9
+
10
+ import { toUnsigned32bit, toSigned32bit } from './util/int.js';
11
+ import * as Log from './util/logging.js';
12
+ import { encodeUTF8, decodeUTF8 } from './util/strings.js';
13
+ import { dragThreshold, supportsWebCodecsH264Decode } from './util/browser.js';
14
+ import { clientToElement } from './util/element.js';
15
+ import { setCapture } from './util/events.js';
16
+ import EventTargetMixin from './util/eventtarget.js';
17
+ import Display from "./display.js";
18
+ import Inflator from "./inflator.js";
19
+ import Deflator from "./deflator.js";
20
+ import Keyboard from "./input/keyboard.js";
21
+ import GestureHandler from "./input/gesturehandler.js";
22
+ import Cursor from "./util/cursor.js";
23
+ import Websock from "./websock.js";
24
+ import KeyTable from "./input/keysym.js";
25
+ import XtScancode from "./input/xtscancodes.js";
26
+ import { encodings } from "./encodings.js";
27
+ import RSAAESAuthenticationState from "./ra2.js";
28
+ import legacyCrypto from "./crypto/crypto.js";
29
+
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 ZlibDecoder from './decoders/zlib.js';
35
+ import TightDecoder from "./decoders/tight.js";
36
+ import TightPNGDecoder from "./decoders/tightpng.js";
37
+ import ZRLEDecoder from "./decoders/zrle.js";
38
+ import JPEGDecoder from "./decoders/jpeg.js";
39
+ import H264Decoder from "./decoders/h264.js";
40
+
41
+ // How many seconds to wait for a disconnect to finish
42
+ const DISCONNECT_TIMEOUT = 3;
43
+ const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
44
+
45
+ // Minimum wait (ms) between two mouse moves
46
+ const MOUSE_MOVE_DELAY = 17;
47
+
48
+ // Wheel thresholds
49
+ const WHEEL_STEP = 50; // Pixels needed for one step
50
+ const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
51
+
52
+ // Gesture thresholds
53
+ const GESTURE_ZOOMSENS = 75;
54
+ const GESTURE_SCRLSENS = 50;
55
+ const DOUBLE_TAP_TIMEOUT = 1000;
56
+ const DOUBLE_TAP_THRESHOLD = 50;
57
+
58
+ // Security types
59
+ const securityTypeNone = 1;
60
+ const securityTypeVNCAuth = 2;
61
+ const securityTypeRA2ne = 6;
62
+ const securityTypeTight = 16;
63
+ const securityTypeVeNCrypt = 19;
64
+ const securityTypeXVP = 22;
65
+ const securityTypeARD = 30;
66
+ const securityTypeMSLogonII = 113;
67
+
68
+ // Special Tight security types
69
+ const securityTypeUnixLogon = 129;
70
+
71
+ // VeNCrypt security types
72
+ const securityTypePlain = 256;
73
+
74
+ // Extended clipboard pseudo-encoding formats
75
+ const extendedClipboardFormatText = 1;
76
+ /*eslint-disable no-unused-vars */
77
+ const extendedClipboardFormatRtf = 1 << 1;
78
+ const extendedClipboardFormatHtml = 1 << 2;
79
+ const extendedClipboardFormatDib = 1 << 3;
80
+ const extendedClipboardFormatFiles = 1 << 4;
81
+ /*eslint-enable */
82
+
83
+ // Extended clipboard pseudo-encoding actions
84
+ const extendedClipboardActionCaps = 1 << 24;
85
+ const extendedClipboardActionRequest = 1 << 25;
86
+ const extendedClipboardActionPeek = 1 << 26;
87
+ const extendedClipboardActionNotify = 1 << 27;
88
+ const extendedClipboardActionProvide = 1 << 28;
89
+
90
+ export default class RFB extends EventTargetMixin {
91
+ constructor(target, urlOrChannel, options) {
92
+ if (!target) {
93
+ throw new Error("Must specify target");
94
+ }
95
+ if (!urlOrChannel) {
96
+ throw new Error("Must specify URL, WebSocket or RTCDataChannel");
97
+ }
98
+
99
+ // We rely on modern APIs which might not be available in an
100
+ // insecure context
101
+ if (!window.isSecureContext) {
102
+ Log.Error("noVNC requires a secure context (TLS). Expect crashes!");
103
+ }
104
+
105
+ super();
106
+
107
+ this._target = target;
108
+
109
+ if (typeof urlOrChannel === "string") {
110
+ this._url = urlOrChannel;
111
+ } else {
112
+ this._url = null;
113
+ this._rawChannel = urlOrChannel;
114
+ }
115
+
116
+ // Connection details
117
+ options = options || {};
118
+ this._rfbCredentials = options.credentials || {};
119
+ this._shared = 'shared' in options ? !!options.shared : true;
120
+ this._repeaterID = options.repeaterID || '';
121
+ this._wsProtocols = options.wsProtocols || [];
122
+
123
+ // Internal state
124
+ this._rfbConnectionState = '';
125
+ this._rfbInitState = '';
126
+ this._rfbAuthScheme = -1;
127
+ this._rfbCleanDisconnect = true;
128
+ this._rfbRSAAESAuthenticationState = null;
129
+
130
+ // Server capabilities
131
+ this._rfbVersion = 0;
132
+ this._rfbMaxVersion = 3.8;
133
+ this._rfbTightVNC = false;
134
+ this._rfbVeNCryptState = 0;
135
+ this._rfbXvpVer = 0;
136
+
137
+ this._fbWidth = 0;
138
+ this._fbHeight = 0;
139
+
140
+ this._fbName = "";
141
+
142
+ this._capabilities = { power: false };
143
+
144
+ this._supportsFence = false;
145
+
146
+ this._supportsContinuousUpdates = false;
147
+ this._enabledContinuousUpdates = false;
148
+
149
+ this._supportsSetDesktopSize = false;
150
+ this._screenID = 0;
151
+ this._screenFlags = 0;
152
+
153
+ this._qemuExtKeyEventSupported = false;
154
+
155
+ this._clipboardText = null;
156
+ this._clipboardServerCapabilitiesActions = {};
157
+ this._clipboardServerCapabilitiesFormats = {};
158
+
159
+ // Internal objects
160
+ this._sock = null; // Websock object
161
+ this._display = null; // Display object
162
+ this._flushing = false; // Display flushing state
163
+ this._keyboard = null; // Keyboard input handler object
164
+ this._gestures = null; // Gesture input handler object
165
+ this._resizeObserver = null; // Resize observer object
166
+
167
+ // Timers
168
+ this._disconnTimer = null; // disconnection timer
169
+ this._resizeTimeout = null; // resize rate limiting
170
+ this._mouseMoveTimer = null;
171
+
172
+ // Decoder states
173
+ this._decoders = {};
174
+
175
+ this._FBU = {
176
+ rects: 0,
177
+ x: 0,
178
+ y: 0,
179
+ width: 0,
180
+ height: 0,
181
+ encoding: null,
182
+ };
183
+
184
+ // Mouse state
185
+ this._mousePos = {};
186
+ this._mouseButtonMask = 0;
187
+ this._mouseLastMoveTime = 0;
188
+ this._viewportDragging = false;
189
+ this._viewportDragPos = {};
190
+ this._viewportHasMoved = false;
191
+ this._accumulatedWheelDeltaX = 0;
192
+ this._accumulatedWheelDeltaY = 0;
193
+
194
+ // Gesture state
195
+ this._gestureLastTapTime = null;
196
+ this._gestureFirstDoubleTapEv = null;
197
+ this._gestureLastMagnitudeX = 0;
198
+ this._gestureLastMagnitudeY = 0;
199
+
200
+ // Bound event handlers
201
+ this._eventHandlers = {
202
+ focusCanvas: this._focusCanvas.bind(this),
203
+ handleResize: this._handleResize.bind(this),
204
+ handleMouse: this._handleMouse.bind(this),
205
+ handleWheel: this._handleWheel.bind(this),
206
+ handleGesture: this._handleGesture.bind(this),
207
+ handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
208
+ handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
209
+ };
210
+
211
+ // main setup
212
+ Log.Debug(">> RFB.constructor");
213
+
214
+ // Create DOM elements
215
+ this._screen = document.createElement('div');
216
+ this._screen.style.display = 'flex';
217
+ this._screen.style.width = '100%';
218
+ this._screen.style.height = '100%';
219
+ this._screen.style.overflow = 'auto';
220
+ this._screen.style.background = DEFAULT_BACKGROUND;
221
+ this._canvas = document.createElement('canvas');
222
+ this._canvas.style.margin = 'auto';
223
+ // Some browsers add an outline on focus
224
+ this._canvas.style.outline = 'none';
225
+ this._canvas.width = 0;
226
+ this._canvas.height = 0;
227
+ this._canvas.tabIndex = -1;
228
+ this._screen.appendChild(this._canvas);
229
+
230
+ // Cursor
231
+ this._cursor = new Cursor();
232
+
233
+ // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
234
+ // it. Result: no cursor at all until a window border or an edit field
235
+ // is hit blindly. But there are also VNC servers that draw the cursor
236
+ // in the framebuffer and don't send the empty local cursor. There is
237
+ // no way to satisfy both sides.
238
+ //
239
+ // The spec is unclear on this "initial cursor" issue. Many other
240
+ // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
241
+ // initial cursor instead.
242
+ this._cursorImage = RFB.cursors.none;
243
+
244
+ // populate decoder array with objects
245
+ this._decoders[encodings.encodingRaw] = new RawDecoder();
246
+ this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
247
+ this._decoders[encodings.encodingRRE] = new RREDecoder();
248
+ this._decoders[encodings.encodingHextile] = new HextileDecoder();
249
+ this._decoders[encodings.encodingZlib] = new ZlibDecoder();
250
+ this._decoders[encodings.encodingTight] = new TightDecoder();
251
+ this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
252
+ this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
253
+ this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
254
+ this._decoders[encodings.encodingH264] = new H264Decoder();
255
+
256
+ // NB: nothing that needs explicit teardown should be done
257
+ // before this point, since this can throw an exception
258
+ try {
259
+ this._display = new Display(this._canvas);
260
+ } catch (exc) {
261
+ Log.Error("Display exception: " + exc);
262
+ throw exc;
263
+ }
264
+
265
+ this._keyboard = new Keyboard(this._canvas);
266
+ this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
267
+ this._remoteCapsLock = null; // Null indicates unknown or irrelevant
268
+ this._remoteNumLock = null;
269
+
270
+ this._gestures = new GestureHandler();
271
+
272
+ this._sock = new Websock();
273
+ this._sock.on('open', this._socketOpen.bind(this));
274
+ this._sock.on('close', this._socketClose.bind(this));
275
+ this._sock.on('message', this._handleMessage.bind(this));
276
+ this._sock.on('error', this._socketError.bind(this));
277
+
278
+ this._expectedClientWidth = null;
279
+ this._expectedClientHeight = null;
280
+ this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
281
+
282
+ // All prepared, kick off the connection
283
+ this._updateConnectionState('connecting');
284
+
285
+ Log.Debug("<< RFB.constructor");
286
+
287
+ // ===== PROPERTIES =====
288
+
289
+ this.dragViewport = false;
290
+ this.focusOnClick = true;
291
+
292
+ this._viewOnly = false;
293
+ this._clipViewport = false;
294
+ this._clippingViewport = false;
295
+ this._scaleViewport = false;
296
+ this._resizeSession = false;
297
+
298
+ this._showDotCursor = false;
299
+ if (options.showDotCursor !== undefined) {
300
+ Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
301
+ this._showDotCursor = options.showDotCursor;
302
+ }
303
+
304
+ this._qualityLevel = 6;
305
+ this._compressionLevel = 2;
306
+ }
307
+
308
+ // ===== PROPERTIES =====
309
+
310
+ get viewOnly() { return this._viewOnly; }
311
+ set viewOnly(viewOnly) {
312
+ this._viewOnly = viewOnly;
313
+
314
+ if (this._rfbConnectionState === "connecting" ||
315
+ this._rfbConnectionState === "connected") {
316
+ if (viewOnly) {
317
+ this._keyboard.ungrab();
318
+ } else {
319
+ this._keyboard.grab();
320
+ }
321
+ }
322
+ }
323
+
324
+ get capabilities() { return this._capabilities; }
325
+
326
+ get clippingViewport() { return this._clippingViewport; }
327
+ _setClippingViewport(on) {
328
+ if (on === this._clippingViewport) {
329
+ return;
330
+ }
331
+ this._clippingViewport = on;
332
+ this.dispatchEvent(new CustomEvent("clippingviewport",
333
+ { detail: this._clippingViewport }));
334
+ }
335
+
336
+ get touchButton() { return 0; }
337
+ set touchButton(button) { Log.Warn("Using old API!"); }
338
+
339
+ get clipViewport() { return this._clipViewport; }
340
+ set clipViewport(viewport) {
341
+ this._clipViewport = viewport;
342
+ this._updateClip();
343
+ }
344
+
345
+ get scaleViewport() { return this._scaleViewport; }
346
+ set scaleViewport(scale) {
347
+ this._scaleViewport = scale;
348
+ // Scaling trumps clipping, so we may need to adjust
349
+ // clipping when enabling or disabling scaling
350
+ if (scale && this._clipViewport) {
351
+ this._updateClip();
352
+ }
353
+ this._updateScale();
354
+ if (!scale && this._clipViewport) {
355
+ this._updateClip();
356
+ }
357
+ }
358
+
359
+ get resizeSession() { return this._resizeSession; }
360
+ set resizeSession(resize) {
361
+ this._resizeSession = resize;
362
+ if (resize) {
363
+ this._requestRemoteResize();
364
+ }
365
+ }
366
+
367
+ get showDotCursor() { return this._showDotCursor; }
368
+ set showDotCursor(show) {
369
+ this._showDotCursor = show;
370
+ this._refreshCursor();
371
+ }
372
+
373
+ get background() { return this._screen.style.background; }
374
+ set background(cssValue) { this._screen.style.background = cssValue; }
375
+
376
+ get qualityLevel() {
377
+ return this._qualityLevel;
378
+ }
379
+ set qualityLevel(qualityLevel) {
380
+ if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {
381
+ Log.Error("qualityLevel must be an integer between 0 and 9");
382
+ return;
383
+ }
384
+
385
+ if (this._qualityLevel === qualityLevel) {
386
+ return;
387
+ }
388
+
389
+ this._qualityLevel = qualityLevel;
390
+
391
+ if (this._rfbConnectionState === 'connected') {
392
+ this._sendEncodings();
393
+ }
394
+ }
395
+
396
+ get compressionLevel() {
397
+ return this._compressionLevel;
398
+ }
399
+ set compressionLevel(compressionLevel) {
400
+ if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) {
401
+ Log.Error("compressionLevel must be an integer between 0 and 9");
402
+ return;
403
+ }
404
+
405
+ if (this._compressionLevel === compressionLevel) {
406
+ return;
407
+ }
408
+
409
+ this._compressionLevel = compressionLevel;
410
+
411
+ if (this._rfbConnectionState === 'connected') {
412
+ this._sendEncodings();
413
+ }
414
+ }
415
+
416
+ // ===== PUBLIC METHODS =====
417
+
418
+ disconnect() {
419
+ this._updateConnectionState('disconnecting');
420
+ this._sock.off('error');
421
+ this._sock.off('message');
422
+ this._sock.off('open');
423
+ if (this._rfbRSAAESAuthenticationState !== null) {
424
+ this._rfbRSAAESAuthenticationState.disconnect();
425
+ }
426
+ }
427
+
428
+ approveServer() {
429
+ if (this._rfbRSAAESAuthenticationState !== null) {
430
+ this._rfbRSAAESAuthenticationState.approveServer();
431
+ }
432
+ }
433
+
434
+ sendCredentials(creds) {
435
+ this._rfbCredentials = creds;
436
+ this._resumeAuthentication();
437
+ }
438
+
439
+ sendCtrlAltDel() {
440
+ if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
441
+ Log.Info("Sending Ctrl-Alt-Del");
442
+
443
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
444
+ this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
445
+ this.sendKey(KeyTable.XK_Delete, "Delete", true);
446
+ this.sendKey(KeyTable.XK_Delete, "Delete", false);
447
+ this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
448
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
449
+ }
450
+
451
+ machineShutdown() {
452
+ this._xvpOp(1, 2);
453
+ }
454
+
455
+ machineReboot() {
456
+ this._xvpOp(1, 3);
457
+ }
458
+
459
+ machineReset() {
460
+ this._xvpOp(1, 4);
461
+ }
462
+
463
+ // Send a key press. If 'down' is not specified then send a down key
464
+ // followed by an up key.
465
+ sendKey(keysym, code, down) {
466
+ if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
467
+
468
+ if (down === undefined) {
469
+ this.sendKey(keysym, code, true);
470
+ this.sendKey(keysym, code, false);
471
+ return;
472
+ }
473
+
474
+ const scancode = XtScancode[code];
475
+
476
+ if (this._qemuExtKeyEventSupported && scancode) {
477
+ // 0 is NoSymbol
478
+ keysym = keysym || 0;
479
+
480
+ Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
481
+
482
+ RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
483
+ } else {
484
+ if (!keysym) {
485
+ return;
486
+ }
487
+ Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
488
+ RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
489
+ }
490
+ }
491
+
492
+ focus(options) {
493
+ this._canvas.focus(options);
494
+ }
495
+
496
+ blur() {
497
+ this._canvas.blur();
498
+ }
499
+
500
+ clipboardPasteFrom(text) {
501
+ if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
502
+
503
+ if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&
504
+ this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
505
+
506
+ this._clipboardText = text;
507
+ RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
508
+ } else {
509
+ let length, i;
510
+ let data;
511
+
512
+ length = 0;
513
+ // eslint-disable-next-line no-unused-vars
514
+ for (let codePoint of text) {
515
+ length++;
516
+ }
517
+
518
+ data = new Uint8Array(length);
519
+
520
+ i = 0;
521
+ for (let codePoint of text) {
522
+ let code = codePoint.codePointAt(0);
523
+
524
+ /* Only ISO 8859-1 is supported */
525
+ if (code > 0xff) {
526
+ code = 0x3f; // '?'
527
+ }
528
+
529
+ data[i++] = code;
530
+ }
531
+
532
+ RFB.messages.clientCutText(this._sock, data);
533
+ }
534
+ }
535
+
536
+ getImageData() {
537
+ return this._display.getImageData();
538
+ }
539
+
540
+ toDataURL(type, encoderOptions) {
541
+ return this._display.toDataURL(type, encoderOptions);
542
+ }
543
+
544
+ toBlob(callback, type, quality) {
545
+ return this._display.toBlob(callback, type, quality);
546
+ }
547
+
548
+ // ===== PRIVATE METHODS =====
549
+
550
+ _connect() {
551
+ Log.Debug(">> RFB.connect");
552
+
553
+ if (this._url) {
554
+ Log.Info(`connecting to ${this._url}`);
555
+ this._sock.open(this._url, this._wsProtocols);
556
+ } else {
557
+ Log.Info(`attaching ${this._rawChannel} to Websock`);
558
+ this._sock.attach(this._rawChannel);
559
+
560
+ if (this._sock.readyState === 'closed') {
561
+ throw Error("Cannot use already closed WebSocket/RTCDataChannel");
562
+ }
563
+
564
+ if (this._sock.readyState === 'open') {
565
+ // FIXME: _socketOpen() can in theory call _fail(), which
566
+ // isn't allowed this early, but I'm not sure that can
567
+ // happen without a bug messing up our state variables
568
+ this._socketOpen();
569
+ }
570
+ }
571
+
572
+ // Make our elements part of the page
573
+ this._target.appendChild(this._screen);
574
+
575
+ this._gestures.attach(this._canvas);
576
+
577
+ this._cursor.attach(this._canvas);
578
+ this._refreshCursor();
579
+
580
+ // Monitor size changes of the screen element
581
+ this._resizeObserver.observe(this._screen);
582
+
583
+ // Always grab focus on some kind of click event
584
+ this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
585
+ this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
586
+
587
+ // Mouse events
588
+ this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);
589
+ this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);
590
+ this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);
591
+ // Prevent middle-click pasting (see handler for why we bind to document)
592
+ this._canvas.addEventListener('click', this._eventHandlers.handleMouse);
593
+ // preventDefault() on mousedown doesn't stop this event for some
594
+ // reason so we have to explicitly block it
595
+ this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
596
+
597
+ // Wheel events
598
+ this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
599
+
600
+ // Gesture events
601
+ this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);
602
+ this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);
603
+ this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture);
604
+
605
+ Log.Debug("<< RFB.connect");
606
+ }
607
+
608
+ _disconnect() {
609
+ Log.Debug(">> RFB.disconnect");
610
+ this._cursor.detach();
611
+ this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);
612
+ this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);
613
+ this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);
614
+ this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);
615
+ this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);
616
+ this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);
617
+ this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
618
+ this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
619
+ this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
620
+ this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
621
+ this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
622
+ this._resizeObserver.disconnect();
623
+ this._keyboard.ungrab();
624
+ this._gestures.detach();
625
+ this._sock.close();
626
+ try {
627
+ this._target.removeChild(this._screen);
628
+ } catch (e) {
629
+ if (e.name === 'NotFoundError') {
630
+ // Some cases where the initial connection fails
631
+ // can disconnect before the _screen is created
632
+ } else {
633
+ throw e;
634
+ }
635
+ }
636
+ clearTimeout(this._resizeTimeout);
637
+ clearTimeout(this._mouseMoveTimer);
638
+ Log.Debug("<< RFB.disconnect");
639
+ }
640
+
641
+ _socketOpen() {
642
+ if ((this._rfbConnectionState === 'connecting') &&
643
+ (this._rfbInitState === '')) {
644
+ this._rfbInitState = 'ProtocolVersion';
645
+ Log.Debug("Starting VNC handshake");
646
+ } else {
647
+ this._fail("Unexpected server connection while " +
648
+ this._rfbConnectionState);
649
+ }
650
+ }
651
+
652
+ _socketClose(e) {
653
+ Log.Debug("WebSocket on-close event");
654
+ let msg = "";
655
+ if (e.code) {
656
+ msg = "(code: " + e.code;
657
+ if (e.reason) {
658
+ msg += ", reason: " + e.reason;
659
+ }
660
+ msg += ")";
661
+ }
662
+ switch (this._rfbConnectionState) {
663
+ case 'connecting':
664
+ this._fail("Connection closed " + msg);
665
+ break;
666
+ case 'connected':
667
+ // Handle disconnects that were initiated server-side
668
+ this._updateConnectionState('disconnecting');
669
+ this._updateConnectionState('disconnected');
670
+ break;
671
+ case 'disconnecting':
672
+ // Normal disconnection path
673
+ this._updateConnectionState('disconnected');
674
+ break;
675
+ case 'disconnected':
676
+ this._fail("Unexpected server disconnect " +
677
+ "when already disconnected " + msg);
678
+ break;
679
+ default:
680
+ this._fail("Unexpected server disconnect before connecting " +
681
+ msg);
682
+ break;
683
+ }
684
+ this._sock.off('close');
685
+ // Delete reference to raw channel to allow cleanup.
686
+ this._rawChannel = null;
687
+ }
688
+
689
+ _socketError(e) {
690
+ Log.Warn("WebSocket on-error event");
691
+ }
692
+
693
+ _focusCanvas(event) {
694
+ if (!this.focusOnClick) {
695
+ return;
696
+ }
697
+
698
+ this.focus({ preventScroll: true });
699
+ }
700
+
701
+ _setDesktopName(name) {
702
+ this._fbName = name;
703
+ this.dispatchEvent(new CustomEvent(
704
+ "desktopname",
705
+ { detail: { name: this._fbName } }));
706
+ }
707
+
708
+ _saveExpectedClientSize() {
709
+ this._expectedClientWidth = this._screen.clientWidth;
710
+ this._expectedClientHeight = this._screen.clientHeight;
711
+ }
712
+
713
+ _currentClientSize() {
714
+ return [this._screen.clientWidth, this._screen.clientHeight];
715
+ }
716
+
717
+ _clientHasExpectedSize() {
718
+ const [currentWidth, currentHeight] = this._currentClientSize();
719
+ return currentWidth == this._expectedClientWidth &&
720
+ currentHeight == this._expectedClientHeight;
721
+ }
722
+
723
+ _handleResize() {
724
+ // Don't change anything if the client size is already as expected
725
+ if (this._clientHasExpectedSize()) {
726
+ return;
727
+ }
728
+ // If the window resized then our screen element might have
729
+ // as well. Update the viewport dimensions.
730
+ window.requestAnimationFrame(() => {
731
+ this._updateClip();
732
+ this._updateScale();
733
+ });
734
+
735
+ if (this._resizeSession) {
736
+ // Request changing the resolution of the remote display to
737
+ // the size of the local browser viewport.
738
+
739
+ // In order to not send multiple requests before the browser-resize
740
+ // is finished we wait 0.5 seconds before sending the request.
741
+ clearTimeout(this._resizeTimeout);
742
+ this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
743
+ }
744
+ }
745
+
746
+ // Update state of clipping in Display object, and make sure the
747
+ // configured viewport matches the current screen size
748
+ _updateClip() {
749
+ const curClip = this._display.clipViewport;
750
+ let newClip = this._clipViewport;
751
+
752
+ if (this._scaleViewport) {
753
+ // Disable viewport clipping if we are scaling
754
+ newClip = false;
755
+ }
756
+
757
+ if (curClip !== newClip) {
758
+ this._display.clipViewport = newClip;
759
+ }
760
+
761
+ if (newClip) {
762
+ // When clipping is enabled, the screen is limited to
763
+ // the size of the container.
764
+ const size = this._screenSize();
765
+ this._display.viewportChangeSize(size.w, size.h);
766
+ this._fixScrollbars();
767
+ this._setClippingViewport(size.w < this._display.width ||
768
+ size.h < this._display.height);
769
+ } else {
770
+ this._setClippingViewport(false);
771
+ }
772
+
773
+ // When changing clipping we might show or hide scrollbars.
774
+ // This causes the expected client dimensions to change.
775
+ if (curClip !== newClip) {
776
+ this._saveExpectedClientSize();
777
+ }
778
+ }
779
+
780
+ _updateScale() {
781
+ if (!this._scaleViewport) {
782
+ this._display.scale = 1.0;
783
+ } else {
784
+ const size = this._screenSize();
785
+ this._display.autoscale(size.w, size.h);
786
+ }
787
+ this._fixScrollbars();
788
+ }
789
+
790
+ // Requests a change of remote desktop size. This message is an extension
791
+ // and may only be sent if we have received an ExtendedDesktopSize message
792
+ _requestRemoteResize() {
793
+ clearTimeout(this._resizeTimeout);
794
+ this._resizeTimeout = null;
795
+
796
+ if (!this._resizeSession || this._viewOnly ||
797
+ !this._supportsSetDesktopSize) {
798
+ return;
799
+ }
800
+
801
+ const size = this._screenSize();
802
+
803
+ RFB.messages.setDesktopSize(this._sock,
804
+ Math.floor(size.w), Math.floor(size.h),
805
+ this._screenID, this._screenFlags);
806
+
807
+ Log.Debug('Requested new desktop size: ' +
808
+ size.w + 'x' + size.h);
809
+ }
810
+
811
+ // Gets the the size of the available screen
812
+ _screenSize() {
813
+ let r = this._screen.getBoundingClientRect();
814
+ return { w: r.width, h: r.height };
815
+ }
816
+
817
+ _fixScrollbars() {
818
+ // This is a hack because Safari on macOS screws up the calculation
819
+ // for when scrollbars are needed. We get scrollbars when making the
820
+ // browser smaller, despite remote resize being enabled. So to fix it
821
+ // we temporarily toggle them off and on.
822
+ const orig = this._screen.style.overflow;
823
+ this._screen.style.overflow = 'hidden';
824
+ // Force Safari to recalculate the layout by asking for
825
+ // an element's dimensions
826
+ this._screen.getBoundingClientRect();
827
+ this._screen.style.overflow = orig;
828
+ }
829
+
830
+ /*
831
+ * Connection states:
832
+ * connecting
833
+ * connected
834
+ * disconnecting
835
+ * disconnected - permanent state
836
+ */
837
+ _updateConnectionState(state) {
838
+ const oldstate = this._rfbConnectionState;
839
+
840
+ if (state === oldstate) {
841
+ Log.Debug("Already in state '" + state + "', ignoring");
842
+ return;
843
+ }
844
+
845
+ // The 'disconnected' state is permanent for each RFB object
846
+ if (oldstate === 'disconnected') {
847
+ Log.Error("Tried changing state of a disconnected RFB object");
848
+ return;
849
+ }
850
+
851
+ // Ensure proper transitions before doing anything
852
+ switch (state) {
853
+ case 'connected':
854
+ if (oldstate !== 'connecting') {
855
+ Log.Error("Bad transition to connected state, " +
856
+ "previous connection state: " + oldstate);
857
+ return;
858
+ }
859
+ break;
860
+
861
+ case 'disconnected':
862
+ if (oldstate !== 'disconnecting') {
863
+ Log.Error("Bad transition to disconnected state, " +
864
+ "previous connection state: " + oldstate);
865
+ return;
866
+ }
867
+ break;
868
+
869
+ case 'connecting':
870
+ if (oldstate !== '') {
871
+ Log.Error("Bad transition to connecting state, " +
872
+ "previous connection state: " + oldstate);
873
+ return;
874
+ }
875
+ break;
876
+
877
+ case 'disconnecting':
878
+ if (oldstate !== 'connected' && oldstate !== 'connecting') {
879
+ Log.Error("Bad transition to disconnecting state, " +
880
+ "previous connection state: " + oldstate);
881
+ return;
882
+ }
883
+ break;
884
+
885
+ default:
886
+ Log.Error("Unknown connection state: " + state);
887
+ return;
888
+ }
889
+
890
+ // State change actions
891
+
892
+ this._rfbConnectionState = state;
893
+
894
+ Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
895
+
896
+ if (this._disconnTimer && state !== 'disconnecting') {
897
+ Log.Debug("Clearing disconnect timer");
898
+ clearTimeout(this._disconnTimer);
899
+ this._disconnTimer = null;
900
+
901
+ // make sure we don't get a double event
902
+ this._sock.off('close');
903
+ }
904
+
905
+ switch (state) {
906
+ case 'connecting':
907
+ this._connect();
908
+ break;
909
+
910
+ case 'connected':
911
+ this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
912
+ break;
913
+
914
+ case 'disconnecting':
915
+ this._disconnect();
916
+
917
+ this._disconnTimer = setTimeout(() => {
918
+ Log.Error("Disconnection timed out.");
919
+ this._updateConnectionState('disconnected');
920
+ }, DISCONNECT_TIMEOUT * 1000);
921
+ break;
922
+
923
+ case 'disconnected':
924
+ this.dispatchEvent(new CustomEvent(
925
+ "disconnect", { detail:
926
+ { clean: this._rfbCleanDisconnect } }));
927
+ break;
928
+ }
929
+ }
930
+
931
+ /* Print errors and disconnect
932
+ *
933
+ * The parameter 'details' is used for information that
934
+ * should be logged but not sent to the user interface.
935
+ */
936
+ _fail(details) {
937
+ switch (this._rfbConnectionState) {
938
+ case 'disconnecting':
939
+ Log.Error("Failed when disconnecting: " + details);
940
+ break;
941
+ case 'connected':
942
+ Log.Error("Failed while connected: " + details);
943
+ break;
944
+ case 'connecting':
945
+ Log.Error("Failed when connecting: " + details);
946
+ break;
947
+ default:
948
+ Log.Error("RFB failure: " + details);
949
+ break;
950
+ }
951
+ this._rfbCleanDisconnect = false; //This is sent to the UI
952
+
953
+ // Transition to disconnected without waiting for socket to close
954
+ this._updateConnectionState('disconnecting');
955
+ this._updateConnectionState('disconnected');
956
+
957
+ return false;
958
+ }
959
+
960
+ _setCapability(cap, val) {
961
+ this._capabilities[cap] = val;
962
+ this.dispatchEvent(new CustomEvent("capabilities",
963
+ { detail: { capabilities: this._capabilities } }));
964
+ }
965
+
966
+ _handleMessage() {
967
+ if (this._sock.rQwait("message", 1)) {
968
+ Log.Warn("handleMessage called on an empty receive queue");
969
+ return;
970
+ }
971
+
972
+ switch (this._rfbConnectionState) {
973
+ case 'disconnected':
974
+ Log.Error("Got data while disconnected");
975
+ break;
976
+ case 'connected':
977
+ while (true) {
978
+ if (this._flushing) {
979
+ break;
980
+ }
981
+ if (!this._normalMsg()) {
982
+ break;
983
+ }
984
+ if (this._sock.rQwait("message", 1)) {
985
+ break;
986
+ }
987
+ }
988
+ break;
989
+ case 'connecting':
990
+ while (this._rfbConnectionState === 'connecting') {
991
+ if (!this._initMsg()) {
992
+ break;
993
+ }
994
+ }
995
+ break;
996
+ default:
997
+ Log.Error("Got data while in an invalid state");
998
+ break;
999
+ }
1000
+ }
1001
+
1002
+ _handleKeyEvent(keysym, code, down, numlock, capslock) {
1003
+ // If remote state of capslock is known, and it doesn't match the local led state of
1004
+ // the keyboard, we send a capslock keypress first to bring it into sync.
1005
+ // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync
1006
+ // we clear the remote state so that we don't send duplicate or spurious fixes,
1007
+ // since it may take some time to receive the new remote CapsLock state.
1008
+ if (code == 'CapsLock' && down) {
1009
+ this._remoteCapsLock = null;
1010
+ }
1011
+ if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {
1012
+ Log.Debug("Fixing remote caps lock");
1013
+
1014
+ this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);
1015
+ this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);
1016
+ // We clear the remote capsLock state when we do this to prevent issues with doing this twice
1017
+ // before we receive an update of the the remote state.
1018
+ this._remoteCapsLock = null;
1019
+ }
1020
+
1021
+ // Logic for numlock is exactly the same.
1022
+ if (code == 'NumLock' && down) {
1023
+ this._remoteNumLock = null;
1024
+ }
1025
+ if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {
1026
+ Log.Debug("Fixing remote num lock");
1027
+ this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);
1028
+ this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);
1029
+ this._remoteNumLock = null;
1030
+ }
1031
+ this.sendKey(keysym, code, down);
1032
+ }
1033
+
1034
+ _handleMouse(ev) {
1035
+ /*
1036
+ * We don't check connection status or viewOnly here as the
1037
+ * mouse events might be used to control the viewport
1038
+ */
1039
+
1040
+ if (ev.type === 'click') {
1041
+ /*
1042
+ * Note: This is only needed for the 'click' event as it fails
1043
+ * to fire properly for the target element so we have
1044
+ * to listen on the document element instead.
1045
+ */
1046
+ if (ev.target !== this._canvas) {
1047
+ return;
1048
+ }
1049
+ }
1050
+
1051
+ // FIXME: if we're in view-only and not dragging,
1052
+ // should we stop events?
1053
+ ev.stopPropagation();
1054
+ ev.preventDefault();
1055
+
1056
+ if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
1057
+ return;
1058
+ }
1059
+
1060
+ let pos = clientToElement(ev.clientX, ev.clientY,
1061
+ this._canvas);
1062
+
1063
+ switch (ev.type) {
1064
+ case 'mousedown':
1065
+ setCapture(this._canvas);
1066
+ this._handleMouseButton(pos.x, pos.y,
1067
+ true, 1 << ev.button);
1068
+ break;
1069
+ case 'mouseup':
1070
+ this._handleMouseButton(pos.x, pos.y,
1071
+ false, 1 << ev.button);
1072
+ break;
1073
+ case 'mousemove':
1074
+ this._handleMouseMove(pos.x, pos.y);
1075
+ break;
1076
+ }
1077
+ }
1078
+
1079
+ _handleMouseButton(x, y, down, bmask) {
1080
+ if (this.dragViewport) {
1081
+ if (down && !this._viewportDragging) {
1082
+ this._viewportDragging = true;
1083
+ this._viewportDragPos = {'x': x, 'y': y};
1084
+ this._viewportHasMoved = false;
1085
+
1086
+ // Skip sending mouse events
1087
+ return;
1088
+ } else {
1089
+ this._viewportDragging = false;
1090
+
1091
+ // If we actually performed a drag then we are done
1092
+ // here and should not send any mouse events
1093
+ if (this._viewportHasMoved) {
1094
+ return;
1095
+ }
1096
+
1097
+ // Otherwise we treat this as a mouse click event.
1098
+ // Send the button down event here, as the button up
1099
+ // event is sent at the end of this function.
1100
+ this._sendMouse(x, y, bmask);
1101
+ }
1102
+ }
1103
+
1104
+ // Flush waiting move event first
1105
+ if (this._mouseMoveTimer !== null) {
1106
+ clearTimeout(this._mouseMoveTimer);
1107
+ this._mouseMoveTimer = null;
1108
+ this._sendMouse(x, y, this._mouseButtonMask);
1109
+ }
1110
+
1111
+ if (down) {
1112
+ this._mouseButtonMask |= bmask;
1113
+ } else {
1114
+ this._mouseButtonMask &= ~bmask;
1115
+ }
1116
+
1117
+ this._sendMouse(x, y, this._mouseButtonMask);
1118
+ }
1119
+
1120
+ _handleMouseMove(x, y) {
1121
+ if (this._viewportDragging) {
1122
+ const deltaX = this._viewportDragPos.x - x;
1123
+ const deltaY = this._viewportDragPos.y - y;
1124
+
1125
+ if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
1126
+ Math.abs(deltaY) > dragThreshold)) {
1127
+ this._viewportHasMoved = true;
1128
+
1129
+ this._viewportDragPos = {'x': x, 'y': y};
1130
+ this._display.viewportChangePos(deltaX, deltaY);
1131
+ }
1132
+
1133
+ // Skip sending mouse events
1134
+ return;
1135
+ }
1136
+
1137
+ this._mousePos = { 'x': x, 'y': y };
1138
+
1139
+ // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
1140
+ if (this._mouseMoveTimer == null) {
1141
+
1142
+ const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;
1143
+ if (timeSinceLastMove > MOUSE_MOVE_DELAY) {
1144
+ this._sendMouse(x, y, this._mouseButtonMask);
1145
+ this._mouseLastMoveTime = Date.now();
1146
+ } else {
1147
+ // Too soon since the latest move, wait the remaining time
1148
+ this._mouseMoveTimer = setTimeout(() => {
1149
+ this._handleDelayedMouseMove();
1150
+ }, MOUSE_MOVE_DELAY - timeSinceLastMove);
1151
+ }
1152
+ }
1153
+ }
1154
+
1155
+ _handleDelayedMouseMove() {
1156
+ this._mouseMoveTimer = null;
1157
+ this._sendMouse(this._mousePos.x, this._mousePos.y,
1158
+ this._mouseButtonMask);
1159
+ this._mouseLastMoveTime = Date.now();
1160
+ }
1161
+
1162
+ _sendMouse(x, y, mask) {
1163
+ if (this._rfbConnectionState !== 'connected') { return; }
1164
+ if (this._viewOnly) { return; } // View only, skip mouse events
1165
+
1166
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x),
1167
+ this._display.absY(y), mask);
1168
+ }
1169
+
1170
+ _handleWheel(ev) {
1171
+ if (this._rfbConnectionState !== 'connected') { return; }
1172
+ if (this._viewOnly) { return; } // View only, skip mouse events
1173
+
1174
+ ev.stopPropagation();
1175
+ ev.preventDefault();
1176
+
1177
+ let pos = clientToElement(ev.clientX, ev.clientY,
1178
+ this._canvas);
1179
+
1180
+ let dX = ev.deltaX;
1181
+ let dY = ev.deltaY;
1182
+
1183
+ // Pixel units unless it's non-zero.
1184
+ // Note that if deltamode is line or page won't matter since we aren't
1185
+ // sending the mouse wheel delta to the server anyway.
1186
+ // The difference between pixel and line can be important however since
1187
+ // we have a threshold that can be smaller than the line height.
1188
+ if (ev.deltaMode !== 0) {
1189
+ dX *= WHEEL_LINE_HEIGHT;
1190
+ dY *= WHEEL_LINE_HEIGHT;
1191
+ }
1192
+
1193
+ // Mouse wheel events are sent in steps over VNC. This means that the VNC
1194
+ // protocol can't handle a wheel event with specific distance or speed.
1195
+ // Therefor, if we get a lot of small mouse wheel events we combine them.
1196
+ this._accumulatedWheelDeltaX += dX;
1197
+ this._accumulatedWheelDeltaY += dY;
1198
+
1199
+ // Generate a mouse wheel step event when the accumulated delta
1200
+ // for one of the axes is large enough.
1201
+ if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
1202
+ if (this._accumulatedWheelDeltaX < 0) {
1203
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
1204
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
1205
+ } else if (this._accumulatedWheelDeltaX > 0) {
1206
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
1207
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
1208
+ }
1209
+
1210
+ this._accumulatedWheelDeltaX = 0;
1211
+ }
1212
+ if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
1213
+ if (this._accumulatedWheelDeltaY < 0) {
1214
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
1215
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
1216
+ } else if (this._accumulatedWheelDeltaY > 0) {
1217
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
1218
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
1219
+ }
1220
+
1221
+ this._accumulatedWheelDeltaY = 0;
1222
+ }
1223
+ }
1224
+
1225
+ _fakeMouseMove(ev, elementX, elementY) {
1226
+ this._handleMouseMove(elementX, elementY);
1227
+ this._cursor.move(ev.detail.clientX, ev.detail.clientY);
1228
+ }
1229
+
1230
+ _handleTapEvent(ev, bmask) {
1231
+ let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
1232
+ this._canvas);
1233
+
1234
+ // If the user quickly taps multiple times we assume they meant to
1235
+ // hit the same spot, so slightly adjust coordinates
1236
+
1237
+ if ((this._gestureLastTapTime !== null) &&
1238
+ ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&
1239
+ (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {
1240
+ let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;
1241
+ let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;
1242
+ let distance = Math.hypot(dx, dy);
1243
+
1244
+ if (distance < DOUBLE_TAP_THRESHOLD) {
1245
+ pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,
1246
+ this._gestureFirstDoubleTapEv.detail.clientY,
1247
+ this._canvas);
1248
+ } else {
1249
+ this._gestureFirstDoubleTapEv = ev;
1250
+ }
1251
+ } else {
1252
+ this._gestureFirstDoubleTapEv = ev;
1253
+ }
1254
+ this._gestureLastTapTime = Date.now();
1255
+
1256
+ this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);
1257
+ this._handleMouseButton(pos.x, pos.y, true, bmask);
1258
+ this._handleMouseButton(pos.x, pos.y, false, bmask);
1259
+ }
1260
+
1261
+ _handleGesture(ev) {
1262
+ let magnitude;
1263
+
1264
+ let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
1265
+ this._canvas);
1266
+ switch (ev.type) {
1267
+ case 'gesturestart':
1268
+ switch (ev.detail.type) {
1269
+ case 'onetap':
1270
+ this._handleTapEvent(ev, 0x1);
1271
+ break;
1272
+ case 'twotap':
1273
+ this._handleTapEvent(ev, 0x4);
1274
+ break;
1275
+ case 'threetap':
1276
+ this._handleTapEvent(ev, 0x2);
1277
+ break;
1278
+ case 'drag':
1279
+ this._fakeMouseMove(ev, pos.x, pos.y);
1280
+ this._handleMouseButton(pos.x, pos.y, true, 0x1);
1281
+ break;
1282
+ case 'longpress':
1283
+ this._fakeMouseMove(ev, pos.x, pos.y);
1284
+ this._handleMouseButton(pos.x, pos.y, true, 0x4);
1285
+ break;
1286
+
1287
+ case 'twodrag':
1288
+ this._gestureLastMagnitudeX = ev.detail.magnitudeX;
1289
+ this._gestureLastMagnitudeY = ev.detail.magnitudeY;
1290
+ this._fakeMouseMove(ev, pos.x, pos.y);
1291
+ break;
1292
+ case 'pinch':
1293
+ this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,
1294
+ ev.detail.magnitudeY);
1295
+ this._fakeMouseMove(ev, pos.x, pos.y);
1296
+ break;
1297
+ }
1298
+ break;
1299
+
1300
+ case 'gesturemove':
1301
+ switch (ev.detail.type) {
1302
+ case 'onetap':
1303
+ case 'twotap':
1304
+ case 'threetap':
1305
+ break;
1306
+ case 'drag':
1307
+ case 'longpress':
1308
+ this._fakeMouseMove(ev, pos.x, pos.y);
1309
+ break;
1310
+ case 'twodrag':
1311
+ // Always scroll in the same position.
1312
+ // We don't know if the mouse was moved so we need to move it
1313
+ // every update.
1314
+ this._fakeMouseMove(ev, pos.x, pos.y);
1315
+ while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
1316
+ this._handleMouseButton(pos.x, pos.y, true, 0x8);
1317
+ this._handleMouseButton(pos.x, pos.y, false, 0x8);
1318
+ this._gestureLastMagnitudeY += GESTURE_SCRLSENS;
1319
+ }
1320
+ while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {
1321
+ this._handleMouseButton(pos.x, pos.y, true, 0x10);
1322
+ this._handleMouseButton(pos.x, pos.y, false, 0x10);
1323
+ this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;
1324
+ }
1325
+ while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {
1326
+ this._handleMouseButton(pos.x, pos.y, true, 0x20);
1327
+ this._handleMouseButton(pos.x, pos.y, false, 0x20);
1328
+ this._gestureLastMagnitudeX += GESTURE_SCRLSENS;
1329
+ }
1330
+ while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {
1331
+ this._handleMouseButton(pos.x, pos.y, true, 0x40);
1332
+ this._handleMouseButton(pos.x, pos.y, false, 0x40);
1333
+ this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;
1334
+ }
1335
+ break;
1336
+ case 'pinch':
1337
+ // Always scroll in the same position.
1338
+ // We don't know if the mouse was moved so we need to move it
1339
+ // every update.
1340
+ this._fakeMouseMove(ev, pos.x, pos.y);
1341
+ magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
1342
+ if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
1343
+ this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
1344
+ while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
1345
+ this._handleMouseButton(pos.x, pos.y, true, 0x8);
1346
+ this._handleMouseButton(pos.x, pos.y, false, 0x8);
1347
+ this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;
1348
+ }
1349
+ while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {
1350
+ this._handleMouseButton(pos.x, pos.y, true, 0x10);
1351
+ this._handleMouseButton(pos.x, pos.y, false, 0x10);
1352
+ this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;
1353
+ }
1354
+ }
1355
+ this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
1356
+ break;
1357
+ }
1358
+ break;
1359
+
1360
+ case 'gestureend':
1361
+ switch (ev.detail.type) {
1362
+ case 'onetap':
1363
+ case 'twotap':
1364
+ case 'threetap':
1365
+ case 'pinch':
1366
+ case 'twodrag':
1367
+ break;
1368
+ case 'drag':
1369
+ this._fakeMouseMove(ev, pos.x, pos.y);
1370
+ this._handleMouseButton(pos.x, pos.y, false, 0x1);
1371
+ break;
1372
+ case 'longpress':
1373
+ this._fakeMouseMove(ev, pos.x, pos.y);
1374
+ this._handleMouseButton(pos.x, pos.y, false, 0x4);
1375
+ break;
1376
+ }
1377
+ break;
1378
+ }
1379
+ }
1380
+
1381
+ // Message Handlers
1382
+
1383
+ _negotiateProtocolVersion() {
1384
+ if (this._sock.rQwait("version", 12)) {
1385
+ return false;
1386
+ }
1387
+
1388
+ const sversion = this._sock.rQshiftStr(12).substr(4, 7);
1389
+ Log.Info("Server ProtocolVersion: " + sversion);
1390
+ let isRepeater = 0;
1391
+ switch (sversion) {
1392
+ case "000.000": // UltraVNC repeater
1393
+ isRepeater = 1;
1394
+ break;
1395
+ case "003.003":
1396
+ case "003.006": // UltraVNC
1397
+ this._rfbVersion = 3.3;
1398
+ break;
1399
+ case "003.007":
1400
+ this._rfbVersion = 3.7;
1401
+ break;
1402
+ case "003.008":
1403
+ case "003.889": // Apple Remote Desktop
1404
+ case "004.000": // Intel AMT KVM
1405
+ case "004.001": // RealVNC 4.6
1406
+ case "005.000": // RealVNC 5.3
1407
+ this._rfbVersion = 3.8;
1408
+ break;
1409
+ default:
1410
+ return this._fail("Invalid server version " + sversion);
1411
+ }
1412
+
1413
+ if (isRepeater) {
1414
+ let repeaterID = "ID:" + this._repeaterID;
1415
+ while (repeaterID.length < 250) {
1416
+ repeaterID += "\0";
1417
+ }
1418
+ this._sock.sQpushString(repeaterID);
1419
+ this._sock.flush();
1420
+ return true;
1421
+ }
1422
+
1423
+ if (this._rfbVersion > this._rfbMaxVersion) {
1424
+ this._rfbVersion = this._rfbMaxVersion;
1425
+ }
1426
+
1427
+ const cversion = "00" + parseInt(this._rfbVersion, 10) +
1428
+ ".00" + ((this._rfbVersion * 10) % 10);
1429
+ this._sock.sQpushString("RFB " + cversion + "\n");
1430
+ this._sock.flush();
1431
+ Log.Debug('Sent ProtocolVersion: ' + cversion);
1432
+
1433
+ this._rfbInitState = 'Security';
1434
+ }
1435
+
1436
+ _isSupportedSecurityType(type) {
1437
+ const clientTypes = [
1438
+ securityTypeNone,
1439
+ securityTypeVNCAuth,
1440
+ securityTypeRA2ne,
1441
+ securityTypeTight,
1442
+ securityTypeVeNCrypt,
1443
+ securityTypeXVP,
1444
+ securityTypeARD,
1445
+ securityTypeMSLogonII,
1446
+ securityTypePlain,
1447
+ ];
1448
+
1449
+ return clientTypes.includes(type);
1450
+ }
1451
+
1452
+ _negotiateSecurity() {
1453
+ if (this._rfbVersion >= 3.7) {
1454
+ // Server sends supported list, client decides
1455
+ const numTypes = this._sock.rQshift8();
1456
+ if (this._sock.rQwait("security type", numTypes, 1)) { return false; }
1457
+
1458
+ if (numTypes === 0) {
1459
+ this._rfbInitState = "SecurityReason";
1460
+ this._securityContext = "no security types";
1461
+ this._securityStatus = 1;
1462
+ return true;
1463
+ }
1464
+
1465
+ const types = this._sock.rQshiftBytes(numTypes);
1466
+ Log.Debug("Server security types: " + types);
1467
+
1468
+ // Look for a matching security type in the order that the
1469
+ // server prefers
1470
+ this._rfbAuthScheme = -1;
1471
+ for (let type of types) {
1472
+ if (this._isSupportedSecurityType(type)) {
1473
+ this._rfbAuthScheme = type;
1474
+ break;
1475
+ }
1476
+ }
1477
+
1478
+ if (this._rfbAuthScheme === -1) {
1479
+ return this._fail("Unsupported security types (types: " + types + ")");
1480
+ }
1481
+
1482
+ this._sock.sQpush8(this._rfbAuthScheme);
1483
+ this._sock.flush();
1484
+ } else {
1485
+ // Server decides
1486
+ if (this._sock.rQwait("security scheme", 4)) { return false; }
1487
+ this._rfbAuthScheme = this._sock.rQshift32();
1488
+
1489
+ if (this._rfbAuthScheme == 0) {
1490
+ this._rfbInitState = "SecurityReason";
1491
+ this._securityContext = "authentication scheme";
1492
+ this._securityStatus = 1;
1493
+ return true;
1494
+ }
1495
+ }
1496
+
1497
+ this._rfbInitState = 'Authentication';
1498
+ Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
1499
+
1500
+ return true;
1501
+ }
1502
+
1503
+ _handleSecurityReason() {
1504
+ if (this._sock.rQwait("reason length", 4)) {
1505
+ return false;
1506
+ }
1507
+ const strlen = this._sock.rQshift32();
1508
+ let reason = "";
1509
+
1510
+ if (strlen > 0) {
1511
+ if (this._sock.rQwait("reason", strlen, 4)) { return false; }
1512
+ reason = this._sock.rQshiftStr(strlen);
1513
+ }
1514
+
1515
+ if (reason !== "") {
1516
+ this.dispatchEvent(new CustomEvent(
1517
+ "securityfailure",
1518
+ { detail: { status: this._securityStatus,
1519
+ reason: reason } }));
1520
+
1521
+ return this._fail("Security negotiation failed on " +
1522
+ this._securityContext +
1523
+ " (reason: " + reason + ")");
1524
+ } else {
1525
+ this.dispatchEvent(new CustomEvent(
1526
+ "securityfailure",
1527
+ { detail: { status: this._securityStatus } }));
1528
+
1529
+ return this._fail("Security negotiation failed on " +
1530
+ this._securityContext);
1531
+ }
1532
+ }
1533
+
1534
+ // authentication
1535
+ _negotiateXvpAuth() {
1536
+ if (this._rfbCredentials.username === undefined ||
1537
+ this._rfbCredentials.password === undefined ||
1538
+ this._rfbCredentials.target === undefined) {
1539
+ this.dispatchEvent(new CustomEvent(
1540
+ "credentialsrequired",
1541
+ { detail: { types: ["username", "password", "target"] } }));
1542
+ return false;
1543
+ }
1544
+
1545
+ this._sock.sQpush8(this._rfbCredentials.username.length);
1546
+ this._sock.sQpush8(this._rfbCredentials.target.length);
1547
+ this._sock.sQpushString(this._rfbCredentials.username);
1548
+ this._sock.sQpushString(this._rfbCredentials.target);
1549
+
1550
+ this._sock.flush();
1551
+
1552
+ this._rfbAuthScheme = securityTypeVNCAuth;
1553
+
1554
+ return this._negotiateAuthentication();
1555
+ }
1556
+
1557
+ // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
1558
+ _negotiateVeNCryptAuth() {
1559
+
1560
+ // waiting for VeNCrypt version
1561
+ if (this._rfbVeNCryptState == 0) {
1562
+ if (this._sock.rQwait("vencrypt version", 2)) { return false; }
1563
+
1564
+ const major = this._sock.rQshift8();
1565
+ const minor = this._sock.rQshift8();
1566
+
1567
+ if (!(major == 0 && minor == 2)) {
1568
+ return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
1569
+ }
1570
+
1571
+ this._sock.sQpush8(0);
1572
+ this._sock.sQpush8(2);
1573
+ this._sock.flush();
1574
+ this._rfbVeNCryptState = 1;
1575
+ }
1576
+
1577
+ // waiting for ACK
1578
+ if (this._rfbVeNCryptState == 1) {
1579
+ if (this._sock.rQwait("vencrypt ack", 1)) { return false; }
1580
+
1581
+ const res = this._sock.rQshift8();
1582
+
1583
+ if (res != 0) {
1584
+ return this._fail("VeNCrypt failure " + res);
1585
+ }
1586
+
1587
+ this._rfbVeNCryptState = 2;
1588
+ }
1589
+ // must fall through here (i.e. no "else if"), beacause we may have already received
1590
+ // the subtypes length and won't be called again
1591
+
1592
+ if (this._rfbVeNCryptState == 2) { // waiting for subtypes length
1593
+ if (this._sock.rQwait("vencrypt subtypes length", 1)) { return false; }
1594
+
1595
+ const subtypesLength = this._sock.rQshift8();
1596
+ if (subtypesLength < 1) {
1597
+ return this._fail("VeNCrypt subtypes empty");
1598
+ }
1599
+
1600
+ this._rfbVeNCryptSubtypesLength = subtypesLength;
1601
+ this._rfbVeNCryptState = 3;
1602
+ }
1603
+
1604
+ // waiting for subtypes list
1605
+ if (this._rfbVeNCryptState == 3) {
1606
+ if (this._sock.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength)) { return false; }
1607
+
1608
+ const subtypes = [];
1609
+ for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) {
1610
+ subtypes.push(this._sock.rQshift32());
1611
+ }
1612
+
1613
+ // Look for a matching security type in the order that the
1614
+ // server prefers
1615
+ this._rfbAuthScheme = -1;
1616
+ for (let type of subtypes) {
1617
+ // Avoid getting in to a loop
1618
+ if (type === securityTypeVeNCrypt) {
1619
+ continue;
1620
+ }
1621
+
1622
+ if (this._isSupportedSecurityType(type)) {
1623
+ this._rfbAuthScheme = type;
1624
+ break;
1625
+ }
1626
+ }
1627
+
1628
+ if (this._rfbAuthScheme === -1) {
1629
+ return this._fail("Unsupported security types (types: " + subtypes + ")");
1630
+ }
1631
+
1632
+ this._sock.sQpush32(this._rfbAuthScheme);
1633
+ this._sock.flush();
1634
+
1635
+ this._rfbVeNCryptState = 4;
1636
+ return true;
1637
+ }
1638
+ }
1639
+
1640
+ _negotiatePlainAuth() {
1641
+ if (this._rfbCredentials.username === undefined ||
1642
+ this._rfbCredentials.password === undefined) {
1643
+ this.dispatchEvent(new CustomEvent(
1644
+ "credentialsrequired",
1645
+ { detail: { types: ["username", "password"] } }));
1646
+ return false;
1647
+ }
1648
+
1649
+ const user = encodeUTF8(this._rfbCredentials.username);
1650
+ const pass = encodeUTF8(this._rfbCredentials.password);
1651
+
1652
+ this._sock.sQpush32(user.length);
1653
+ this._sock.sQpush32(pass.length);
1654
+ this._sock.sQpushString(user);
1655
+ this._sock.sQpushString(pass);
1656
+ this._sock.flush();
1657
+
1658
+ this._rfbInitState = "SecurityResult";
1659
+ return true;
1660
+ }
1661
+
1662
+ _negotiateStdVNCAuth() {
1663
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
1664
+
1665
+ if (this._rfbCredentials.password === undefined) {
1666
+ this.dispatchEvent(new CustomEvent(
1667
+ "credentialsrequired",
1668
+ { detail: { types: ["password"] } }));
1669
+ return false;
1670
+ }
1671
+
1672
+ // TODO(directxman12): make genDES not require an Array
1673
+ const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
1674
+ const response = RFB.genDES(this._rfbCredentials.password, challenge);
1675
+ this._sock.sQpushBytes(response);
1676
+ this._sock.flush();
1677
+ this._rfbInitState = "SecurityResult";
1678
+ return true;
1679
+ }
1680
+
1681
+ _negotiateARDAuth() {
1682
+
1683
+ if (this._rfbCredentials.username === undefined ||
1684
+ this._rfbCredentials.password === undefined) {
1685
+ this.dispatchEvent(new CustomEvent(
1686
+ "credentialsrequired",
1687
+ { detail: { types: ["username", "password"] } }));
1688
+ return false;
1689
+ }
1690
+
1691
+ if (this._rfbCredentials.ardPublicKey != undefined &&
1692
+ this._rfbCredentials.ardCredentials != undefined) {
1693
+ // if the async web crypto is done return the results
1694
+ this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
1695
+ this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
1696
+ this._sock.flush();
1697
+ this._rfbCredentials.ardCredentials = null;
1698
+ this._rfbCredentials.ardPublicKey = null;
1699
+ this._rfbInitState = "SecurityResult";
1700
+ return true;
1701
+ }
1702
+
1703
+ if (this._sock.rQwait("read ard", 4)) { return false; }
1704
+
1705
+ let generator = this._sock.rQshiftBytes(2); // DH base generator value
1706
+
1707
+ let keyLength = this._sock.rQshift16();
1708
+
1709
+ if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
1710
+
1711
+ // read the server values
1712
+ let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
1713
+ let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
1714
+
1715
+ let clientKey = legacyCrypto.generateKey(
1716
+ { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
1717
+ this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
1718
+
1719
+ return false;
1720
+ }
1721
+
1722
+ async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
1723
+ const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
1724
+ const sharedKey = legacyCrypto.deriveBits(
1725
+ { name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
1726
+
1727
+ const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
1728
+ const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1729
+
1730
+ const credentials = window.crypto.getRandomValues(new Uint8Array(128));
1731
+ for (let i = 0; i < username.length; i++) {
1732
+ credentials[i] = username.charCodeAt(i);
1733
+ }
1734
+ credentials[username.length] = 0;
1735
+ for (let i = 0; i < password.length; i++) {
1736
+ credentials[64 + i] = password.charCodeAt(i);
1737
+ }
1738
+ credentials[64 + password.length] = 0;
1739
+
1740
+ const key = await legacyCrypto.digest("MD5", sharedKey);
1741
+ const cipher = await legacyCrypto.importKey(
1742
+ "raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
1743
+ const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
1744
+
1745
+ this._rfbCredentials.ardCredentials = encrypted;
1746
+ this._rfbCredentials.ardPublicKey = clientPublicKey;
1747
+
1748
+ this._resumeAuthentication();
1749
+ }
1750
+
1751
+ _negotiateTightUnixAuth() {
1752
+ if (this._rfbCredentials.username === undefined ||
1753
+ this._rfbCredentials.password === undefined) {
1754
+ this.dispatchEvent(new CustomEvent(
1755
+ "credentialsrequired",
1756
+ { detail: { types: ["username", "password"] } }));
1757
+ return false;
1758
+ }
1759
+
1760
+ this._sock.sQpush32(this._rfbCredentials.username.length);
1761
+ this._sock.sQpush32(this._rfbCredentials.password.length);
1762
+ this._sock.sQpushString(this._rfbCredentials.username);
1763
+ this._sock.sQpushString(this._rfbCredentials.password);
1764
+ this._sock.flush();
1765
+
1766
+ this._rfbInitState = "SecurityResult";
1767
+ return true;
1768
+ }
1769
+
1770
+ _negotiateTightTunnels(numTunnels) {
1771
+ const clientSupportedTunnelTypes = {
1772
+ 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
1773
+ };
1774
+ const serverSupportedTunnelTypes = {};
1775
+ // receive tunnel capabilities
1776
+ for (let i = 0; i < numTunnels; i++) {
1777
+ const capCode = this._sock.rQshift32();
1778
+ const capVendor = this._sock.rQshiftStr(4);
1779
+ const capSignature = this._sock.rQshiftStr(8);
1780
+ serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };
1781
+ }
1782
+
1783
+ Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
1784
+
1785
+ // Siemens touch panels have a VNC server that supports NOTUNNEL,
1786
+ // but forgets to advertise it. Try to detect such servers by
1787
+ // looking for their custom tunnel type.
1788
+ if (serverSupportedTunnelTypes[1] &&
1789
+ (serverSupportedTunnelTypes[1].vendor === "SICR") &&
1790
+ (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
1791
+ Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1792
+ serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
1793
+ }
1794
+
1795
+ // choose the notunnel type
1796
+ if (serverSupportedTunnelTypes[0]) {
1797
+ if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
1798
+ serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
1799
+ return this._fail("Client's tunnel type had the incorrect " +
1800
+ "vendor or signature");
1801
+ }
1802
+ Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
1803
+ this._sock.sQpush32(0); // use NOTUNNEL
1804
+ this._sock.flush();
1805
+ return false; // wait until we receive the sub auth count to continue
1806
+ } else {
1807
+ return this._fail("Server wanted tunnels, but doesn't support " +
1808
+ "the notunnel type");
1809
+ }
1810
+ }
1811
+
1812
+ _negotiateTightAuth() {
1813
+ if (!this._rfbTightVNC) { // first pass, do the tunnel negotiation
1814
+ if (this._sock.rQwait("num tunnels", 4)) { return false; }
1815
+ const numTunnels = this._sock.rQshift32();
1816
+ if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
1817
+
1818
+ this._rfbTightVNC = true;
1819
+
1820
+ if (numTunnels > 0) {
1821
+ this._negotiateTightTunnels(numTunnels);
1822
+ return false; // wait until we receive the sub auth to continue
1823
+ }
1824
+ }
1825
+
1826
+ // second pass, do the sub-auth negotiation
1827
+ if (this._sock.rQwait("sub auth count", 4)) { return false; }
1828
+ const subAuthCount = this._sock.rQshift32();
1829
+ if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
1830
+ this._rfbInitState = 'SecurityResult';
1831
+ return true;
1832
+ }
1833
+
1834
+ if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
1835
+
1836
+ const clientSupportedTypes = {
1837
+ 'STDVNOAUTH__': 1,
1838
+ 'STDVVNCAUTH_': 2,
1839
+ 'TGHTULGNAUTH': 129
1840
+ };
1841
+
1842
+ const serverSupportedTypes = [];
1843
+
1844
+ for (let i = 0; i < subAuthCount; i++) {
1845
+ this._sock.rQshift32(); // capNum
1846
+ const capabilities = this._sock.rQshiftStr(12);
1847
+ serverSupportedTypes.push(capabilities);
1848
+ }
1849
+
1850
+ Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
1851
+
1852
+ for (let authType in clientSupportedTypes) {
1853
+ if (serverSupportedTypes.indexOf(authType) != -1) {
1854
+ this._sock.sQpush32(clientSupportedTypes[authType]);
1855
+ this._sock.flush();
1856
+ Log.Debug("Selected authentication type: " + authType);
1857
+
1858
+ switch (authType) {
1859
+ case 'STDVNOAUTH__': // no auth
1860
+ this._rfbInitState = 'SecurityResult';
1861
+ return true;
1862
+ case 'STDVVNCAUTH_':
1863
+ this._rfbAuthScheme = securityTypeVNCAuth;
1864
+ return true;
1865
+ case 'TGHTULGNAUTH':
1866
+ this._rfbAuthScheme = securityTypeUnixLogon;
1867
+ return true;
1868
+ default:
1869
+ return this._fail("Unsupported tiny auth scheme " +
1870
+ "(scheme: " + authType + ")");
1871
+ }
1872
+ }
1873
+ }
1874
+
1875
+ return this._fail("No supported sub-auth types!");
1876
+ }
1877
+
1878
+ _handleRSAAESCredentialsRequired(event) {
1879
+ this.dispatchEvent(event);
1880
+ }
1881
+
1882
+ _handleRSAAESServerVerification(event) {
1883
+ this.dispatchEvent(event);
1884
+ }
1885
+
1886
+ _negotiateRA2neAuth() {
1887
+ if (this._rfbRSAAESAuthenticationState === null) {
1888
+ this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
1889
+ this._rfbRSAAESAuthenticationState.addEventListener(
1890
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1891
+ this._rfbRSAAESAuthenticationState.addEventListener(
1892
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1893
+ }
1894
+ this._rfbRSAAESAuthenticationState.checkInternalEvents();
1895
+ if (!this._rfbRSAAESAuthenticationState.hasStarted) {
1896
+ this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
1897
+ .catch((e) => {
1898
+ if (e.message !== "disconnect normally") {
1899
+ this._fail(e.message);
1900
+ }
1901
+ })
1902
+ .then(() => {
1903
+ this._rfbInitState = "SecurityResult";
1904
+ return true;
1905
+ }).finally(() => {
1906
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1907
+ "serververification", this._eventHandlers.handleRSAAESServerVerification);
1908
+ this._rfbRSAAESAuthenticationState.removeEventListener(
1909
+ "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
1910
+ this._rfbRSAAESAuthenticationState = null;
1911
+ });
1912
+ }
1913
+ return false;
1914
+ }
1915
+
1916
+ _negotiateMSLogonIIAuth() {
1917
+ if (this._sock.rQwait("mslogonii dh param", 24)) { return false; }
1918
+
1919
+ if (this._rfbCredentials.username === undefined ||
1920
+ this._rfbCredentials.password === undefined) {
1921
+ this.dispatchEvent(new CustomEvent(
1922
+ "credentialsrequired",
1923
+ { detail: { types: ["username", "password"] } }));
1924
+ return false;
1925
+ }
1926
+
1927
+ const g = this._sock.rQshiftBytes(8);
1928
+ const p = this._sock.rQshiftBytes(8);
1929
+ const A = this._sock.rQshiftBytes(8);
1930
+ const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
1931
+ const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
1932
+ const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
1933
+
1934
+ const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
1935
+ const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
1936
+ const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
1937
+ let usernameBytes = new Uint8Array(256);
1938
+ let passwordBytes = new Uint8Array(64);
1939
+ window.crypto.getRandomValues(usernameBytes);
1940
+ window.crypto.getRandomValues(passwordBytes);
1941
+ for (let i = 0; i < username.length; i++) {
1942
+ usernameBytes[i] = username.charCodeAt(i);
1943
+ }
1944
+ usernameBytes[username.length] = 0;
1945
+ for (let i = 0; i < password.length; i++) {
1946
+ passwordBytes[i] = password.charCodeAt(i);
1947
+ }
1948
+ passwordBytes[password.length] = 0;
1949
+ usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
1950
+ passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
1951
+ this._sock.sQpushBytes(B);
1952
+ this._sock.sQpushBytes(usernameBytes);
1953
+ this._sock.sQpushBytes(passwordBytes);
1954
+ this._sock.flush();
1955
+ this._rfbInitState = "SecurityResult";
1956
+ return true;
1957
+ }
1958
+
1959
+ _negotiateAuthentication() {
1960
+ switch (this._rfbAuthScheme) {
1961
+ case securityTypeNone:
1962
+ if (this._rfbVersion >= 3.8) {
1963
+ this._rfbInitState = 'SecurityResult';
1964
+ } else {
1965
+ this._rfbInitState = 'ClientInitialisation';
1966
+ }
1967
+ return true;
1968
+
1969
+ case securityTypeXVP:
1970
+ return this._negotiateXvpAuth();
1971
+
1972
+ case securityTypeARD:
1973
+ return this._negotiateARDAuth();
1974
+
1975
+ case securityTypeVNCAuth:
1976
+ return this._negotiateStdVNCAuth();
1977
+
1978
+ case securityTypeTight:
1979
+ return this._negotiateTightAuth();
1980
+
1981
+ case securityTypeVeNCrypt:
1982
+ return this._negotiateVeNCryptAuth();
1983
+
1984
+ case securityTypePlain:
1985
+ return this._negotiatePlainAuth();
1986
+
1987
+ case securityTypeUnixLogon:
1988
+ return this._negotiateTightUnixAuth();
1989
+
1990
+ case securityTypeRA2ne:
1991
+ return this._negotiateRA2neAuth();
1992
+
1993
+ case securityTypeMSLogonII:
1994
+ return this._negotiateMSLogonIIAuth();
1995
+
1996
+ default:
1997
+ return this._fail("Unsupported auth scheme (scheme: " +
1998
+ this._rfbAuthScheme + ")");
1999
+ }
2000
+ }
2001
+
2002
+ _handleSecurityResult() {
2003
+ if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
2004
+
2005
+ const status = this._sock.rQshift32();
2006
+
2007
+ if (status === 0) { // OK
2008
+ this._rfbInitState = 'ClientInitialisation';
2009
+ Log.Debug('Authentication OK');
2010
+ return true;
2011
+ } else {
2012
+ if (this._rfbVersion >= 3.8) {
2013
+ this._rfbInitState = "SecurityReason";
2014
+ this._securityContext = "security result";
2015
+ this._securityStatus = status;
2016
+ return true;
2017
+ } else {
2018
+ this.dispatchEvent(new CustomEvent(
2019
+ "securityfailure",
2020
+ { detail: { status: status } }));
2021
+
2022
+ return this._fail("Security handshake failed");
2023
+ }
2024
+ }
2025
+ }
2026
+
2027
+ _negotiateServerInit() {
2028
+ if (this._sock.rQwait("server initialization", 24)) { return false; }
2029
+
2030
+ /* Screen size */
2031
+ const width = this._sock.rQshift16();
2032
+ const height = this._sock.rQshift16();
2033
+
2034
+ /* PIXEL_FORMAT */
2035
+ const bpp = this._sock.rQshift8();
2036
+ const depth = this._sock.rQshift8();
2037
+ const bigEndian = this._sock.rQshift8();
2038
+ const trueColor = this._sock.rQshift8();
2039
+
2040
+ const redMax = this._sock.rQshift16();
2041
+ const greenMax = this._sock.rQshift16();
2042
+ const blueMax = this._sock.rQshift16();
2043
+ const redShift = this._sock.rQshift8();
2044
+ const greenShift = this._sock.rQshift8();
2045
+ const blueShift = this._sock.rQshift8();
2046
+ this._sock.rQskipBytes(3); // padding
2047
+
2048
+ // NB(directxman12): we don't want to call any callbacks or print messages until
2049
+ // *after* we're past the point where we could backtrack
2050
+
2051
+ /* Connection name/title */
2052
+ const nameLength = this._sock.rQshift32();
2053
+ if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
2054
+ let name = this._sock.rQshiftStr(nameLength);
2055
+ name = decodeUTF8(name, true);
2056
+
2057
+ if (this._rfbTightVNC) {
2058
+ if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
2059
+ // In TightVNC mode, ServerInit message is extended
2060
+ const numServerMessages = this._sock.rQshift16();
2061
+ const numClientMessages = this._sock.rQshift16();
2062
+ const numEncodings = this._sock.rQshift16();
2063
+ this._sock.rQskipBytes(2); // padding
2064
+
2065
+ const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
2066
+ if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
2067
+
2068
+ // we don't actually do anything with the capability information that TIGHT sends,
2069
+ // so we just skip the all of this.
2070
+
2071
+ // TIGHT server message capabilities
2072
+ this._sock.rQskipBytes(16 * numServerMessages);
2073
+
2074
+ // TIGHT client message capabilities
2075
+ this._sock.rQskipBytes(16 * numClientMessages);
2076
+
2077
+ // TIGHT encoding capabilities
2078
+ this._sock.rQskipBytes(16 * numEncodings);
2079
+ }
2080
+
2081
+ // NB(directxman12): these are down here so that we don't run them multiple times
2082
+ // if we backtrack
2083
+ Log.Info("Screen: " + width + "x" + height +
2084
+ ", bpp: " + bpp + ", depth: " + depth +
2085
+ ", bigEndian: " + bigEndian +
2086
+ ", trueColor: " + trueColor +
2087
+ ", redMax: " + redMax +
2088
+ ", greenMax: " + greenMax +
2089
+ ", blueMax: " + blueMax +
2090
+ ", redShift: " + redShift +
2091
+ ", greenShift: " + greenShift +
2092
+ ", blueShift: " + blueShift);
2093
+
2094
+ // we're past the point where we could backtrack, so it's safe to call this
2095
+ this._setDesktopName(name);
2096
+ this._resize(width, height);
2097
+
2098
+ if (!this._viewOnly) { this._keyboard.grab(); }
2099
+
2100
+ this._fbDepth = 24;
2101
+
2102
+ if (this._fbName === "Intel(r) AMT KVM") {
2103
+ Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
2104
+ this._fbDepth = 8;
2105
+ }
2106
+
2107
+ RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
2108
+ this._sendEncodings();
2109
+ RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
2110
+
2111
+ this._updateConnectionState('connected');
2112
+ return true;
2113
+ }
2114
+
2115
+ _sendEncodings() {
2116
+ const encs = [];
2117
+
2118
+ // In preference order
2119
+ encs.push(encodings.encodingCopyRect);
2120
+ // Only supported with full depth support
2121
+ if (this._fbDepth == 24) {
2122
+ if (supportsWebCodecsH264Decode) {
2123
+ encs.push(encodings.encodingH264);
2124
+ }
2125
+ encs.push(encodings.encodingTight);
2126
+ encs.push(encodings.encodingTightPNG);
2127
+ encs.push(encodings.encodingZRLE);
2128
+ encs.push(encodings.encodingJPEG);
2129
+ encs.push(encodings.encodingHextile);
2130
+ encs.push(encodings.encodingRRE);
2131
+ encs.push(encodings.encodingZlib);
2132
+ }
2133
+ encs.push(encodings.encodingRaw);
2134
+
2135
+ // Psuedo-encoding settings
2136
+ encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
2137
+ encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
2138
+
2139
+ encs.push(encodings.pseudoEncodingDesktopSize);
2140
+ encs.push(encodings.pseudoEncodingLastRect);
2141
+ encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
2142
+ encs.push(encodings.pseudoEncodingQEMULedEvent);
2143
+ encs.push(encodings.pseudoEncodingExtendedDesktopSize);
2144
+ encs.push(encodings.pseudoEncodingXvp);
2145
+ encs.push(encodings.pseudoEncodingFence);
2146
+ encs.push(encodings.pseudoEncodingContinuousUpdates);
2147
+ encs.push(encodings.pseudoEncodingDesktopName);
2148
+ encs.push(encodings.pseudoEncodingExtendedClipboard);
2149
+
2150
+ if (this._fbDepth == 24) {
2151
+ encs.push(encodings.pseudoEncodingVMwareCursor);
2152
+ encs.push(encodings.pseudoEncodingCursor);
2153
+ }
2154
+
2155
+ RFB.messages.clientEncodings(this._sock, encs);
2156
+ }
2157
+
2158
+ /* RFB protocol initialization states:
2159
+ * ProtocolVersion
2160
+ * Security
2161
+ * Authentication
2162
+ * SecurityResult
2163
+ * ClientInitialization - not triggered by server message
2164
+ * ServerInitialization
2165
+ */
2166
+ _initMsg() {
2167
+ switch (this._rfbInitState) {
2168
+ case 'ProtocolVersion':
2169
+ return this._negotiateProtocolVersion();
2170
+
2171
+ case 'Security':
2172
+ return this._negotiateSecurity();
2173
+
2174
+ case 'Authentication':
2175
+ return this._negotiateAuthentication();
2176
+
2177
+ case 'SecurityResult':
2178
+ return this._handleSecurityResult();
2179
+
2180
+ case 'SecurityReason':
2181
+ return this._handleSecurityReason();
2182
+
2183
+ case 'ClientInitialisation':
2184
+ this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
2185
+ this._sock.flush();
2186
+ this._rfbInitState = 'ServerInitialisation';
2187
+ return true;
2188
+
2189
+ case 'ServerInitialisation':
2190
+ return this._negotiateServerInit();
2191
+
2192
+ default:
2193
+ return this._fail("Unknown init state (state: " +
2194
+ this._rfbInitState + ")");
2195
+ }
2196
+ }
2197
+
2198
+ // Resume authentication handshake after it was paused for some
2199
+ // reason, e.g. waiting for a password from the user
2200
+ _resumeAuthentication() {
2201
+ // We use setTimeout() so it's run in its own context, just like
2202
+ // it originally did via the WebSocket's event handler
2203
+ setTimeout(this._initMsg.bind(this), 0);
2204
+ }
2205
+
2206
+ _handleSetColourMapMsg() {
2207
+ Log.Debug("SetColorMapEntries");
2208
+
2209
+ return this._fail("Unexpected SetColorMapEntries message");
2210
+ }
2211
+
2212
+ _handleServerCutText() {
2213
+ Log.Debug("ServerCutText");
2214
+
2215
+ if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
2216
+
2217
+ this._sock.rQskipBytes(3); // Padding
2218
+
2219
+ let length = this._sock.rQshift32();
2220
+ length = toSigned32bit(length);
2221
+
2222
+ if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }
2223
+
2224
+ if (length >= 0) {
2225
+ //Standard msg
2226
+ const text = this._sock.rQshiftStr(length);
2227
+ if (this._viewOnly) {
2228
+ return true;
2229
+ }
2230
+
2231
+ this.dispatchEvent(new CustomEvent(
2232
+ "clipboard",
2233
+ { detail: { text: text } }));
2234
+
2235
+ } else {
2236
+ //Extended msg.
2237
+ length = Math.abs(length);
2238
+ const flags = this._sock.rQshift32();
2239
+ let formats = flags & 0x0000FFFF;
2240
+ let actions = flags & 0xFF000000;
2241
+
2242
+ let isCaps = (!!(actions & extendedClipboardActionCaps));
2243
+ if (isCaps) {
2244
+ this._clipboardServerCapabilitiesFormats = {};
2245
+ this._clipboardServerCapabilitiesActions = {};
2246
+
2247
+ // Update our server capabilities for Formats
2248
+ for (let i = 0; i <= 15; i++) {
2249
+ let index = 1 << i;
2250
+
2251
+ // Check if format flag is set.
2252
+ if ((formats & index)) {
2253
+ this._clipboardServerCapabilitiesFormats[index] = true;
2254
+ // We don't send unsolicited clipboard, so we
2255
+ // ignore the size
2256
+ this._sock.rQshift32();
2257
+ }
2258
+ }
2259
+
2260
+ // Update our server capabilities for Actions
2261
+ for (let i = 24; i <= 31; i++) {
2262
+ let index = 1 << i;
2263
+ this._clipboardServerCapabilitiesActions[index] = !!(actions & index);
2264
+ }
2265
+
2266
+ /* Caps handling done, send caps with the clients
2267
+ capabilities set as a response */
2268
+ let clientActions = [
2269
+ extendedClipboardActionCaps,
2270
+ extendedClipboardActionRequest,
2271
+ extendedClipboardActionPeek,
2272
+ extendedClipboardActionNotify,
2273
+ extendedClipboardActionProvide
2274
+ ];
2275
+ RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});
2276
+
2277
+ } else if (actions === extendedClipboardActionRequest) {
2278
+ if (this._viewOnly) {
2279
+ return true;
2280
+ }
2281
+
2282
+ // Check if server has told us it can handle Provide and there is clipboard data to send.
2283
+ if (this._clipboardText != null &&
2284
+ this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {
2285
+
2286
+ if (formats & extendedClipboardFormatText) {
2287
+ RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);
2288
+ }
2289
+ }
2290
+
2291
+ } else if (actions === extendedClipboardActionPeek) {
2292
+ if (this._viewOnly) {
2293
+ return true;
2294
+ }
2295
+
2296
+ if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
2297
+
2298
+ if (this._clipboardText != null) {
2299
+ RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
2300
+ } else {
2301
+ RFB.messages.extendedClipboardNotify(this._sock, []);
2302
+ }
2303
+ }
2304
+
2305
+ } else if (actions === extendedClipboardActionNotify) {
2306
+ if (this._viewOnly) {
2307
+ return true;
2308
+ }
2309
+
2310
+ if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {
2311
+
2312
+ if (formats & extendedClipboardFormatText) {
2313
+ RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);
2314
+ }
2315
+ }
2316
+
2317
+ } else if (actions === extendedClipboardActionProvide) {
2318
+ if (this._viewOnly) {
2319
+ return true;
2320
+ }
2321
+
2322
+ if (!(formats & extendedClipboardFormatText)) {
2323
+ return true;
2324
+ }
2325
+ // Ignore what we had in our clipboard client side.
2326
+ this._clipboardText = null;
2327
+
2328
+ // FIXME: Should probably verify that this data was actually requested
2329
+ let zlibStream = this._sock.rQshiftBytes(length - 4);
2330
+ let streamInflator = new Inflator();
2331
+ let textData = null;
2332
+
2333
+ streamInflator.setInput(zlibStream);
2334
+ for (let i = 0; i <= 15; i++) {
2335
+ let format = 1 << i;
2336
+
2337
+ if (formats & format) {
2338
+
2339
+ let size = 0x00;
2340
+ let sizeArray = streamInflator.inflate(4);
2341
+
2342
+ size |= (sizeArray[0] << 24);
2343
+ size |= (sizeArray[1] << 16);
2344
+ size |= (sizeArray[2] << 8);
2345
+ size |= (sizeArray[3]);
2346
+ let chunk = streamInflator.inflate(size);
2347
+
2348
+ if (format === extendedClipboardFormatText) {
2349
+ textData = chunk;
2350
+ }
2351
+ }
2352
+ }
2353
+ streamInflator.setInput(null);
2354
+
2355
+ if (textData !== null) {
2356
+ let tmpText = "";
2357
+ for (let i = 0; i < textData.length; i++) {
2358
+ tmpText += String.fromCharCode(textData[i]);
2359
+ }
2360
+ textData = tmpText;
2361
+
2362
+ textData = decodeUTF8(textData);
2363
+ if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {
2364
+ textData = textData.slice(0, -1);
2365
+ }
2366
+
2367
+ textData = textData.replaceAll("\r\n", "\n");
2368
+
2369
+ this.dispatchEvent(new CustomEvent(
2370
+ "clipboard",
2371
+ { detail: { text: textData } }));
2372
+ }
2373
+ } else {
2374
+ return this._fail("Unexpected action in extended clipboard message: " + actions);
2375
+ }
2376
+ }
2377
+ return true;
2378
+ }
2379
+
2380
+ _handleServerFenceMsg() {
2381
+ if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
2382
+ this._sock.rQskipBytes(3); // Padding
2383
+ let flags = this._sock.rQshift32();
2384
+ let length = this._sock.rQshift8();
2385
+
2386
+ if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
2387
+
2388
+ if (length > 64) {
2389
+ Log.Warn("Bad payload length (" + length + ") in fence response");
2390
+ length = 64;
2391
+ }
2392
+
2393
+ const payload = this._sock.rQshiftStr(length);
2394
+
2395
+ this._supportsFence = true;
2396
+
2397
+ /*
2398
+ * Fence flags
2399
+ *
2400
+ * (1<<0) - BlockBefore
2401
+ * (1<<1) - BlockAfter
2402
+ * (1<<2) - SyncNext
2403
+ * (1<<31) - Request
2404
+ */
2405
+
2406
+ if (!(flags & (1<<31))) {
2407
+ return this._fail("Unexpected fence response");
2408
+ }
2409
+
2410
+ // Filter out unsupported flags
2411
+ // FIXME: support syncNext
2412
+ flags &= (1<<0) | (1<<1);
2413
+
2414
+ // BlockBefore and BlockAfter are automatically handled by
2415
+ // the fact that we process each incoming message
2416
+ // synchronuosly.
2417
+ RFB.messages.clientFence(this._sock, flags, payload);
2418
+
2419
+ return true;
2420
+ }
2421
+
2422
+ _handleXvpMsg() {
2423
+ if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
2424
+ this._sock.rQskipBytes(1); // Padding
2425
+ const xvpVer = this._sock.rQshift8();
2426
+ const xvpMsg = this._sock.rQshift8();
2427
+
2428
+ switch (xvpMsg) {
2429
+ case 0: // XVP_FAIL
2430
+ Log.Error("XVP Operation Failed");
2431
+ break;
2432
+ case 1: // XVP_INIT
2433
+ this._rfbXvpVer = xvpVer;
2434
+ Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")");
2435
+ this._setCapability("power", true);
2436
+ break;
2437
+ default:
2438
+ this._fail("Illegal server XVP message (msg: " + xvpMsg + ")");
2439
+ break;
2440
+ }
2441
+
2442
+ return true;
2443
+ }
2444
+
2445
+ _normalMsg() {
2446
+ let msgType;
2447
+ if (this._FBU.rects > 0) {
2448
+ msgType = 0;
2449
+ } else {
2450
+ msgType = this._sock.rQshift8();
2451
+ }
2452
+
2453
+ let first, ret;
2454
+ switch (msgType) {
2455
+ case 0: // FramebufferUpdate
2456
+ ret = this._framebufferUpdate();
2457
+ if (ret && !this._enabledContinuousUpdates) {
2458
+ RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
2459
+ this._fbWidth, this._fbHeight);
2460
+ }
2461
+ return ret;
2462
+
2463
+ case 1: // SetColorMapEntries
2464
+ return this._handleSetColourMapMsg();
2465
+
2466
+ case 2: // Bell
2467
+ Log.Debug("Bell");
2468
+ this.dispatchEvent(new CustomEvent(
2469
+ "bell",
2470
+ { detail: {} }));
2471
+ return true;
2472
+
2473
+ case 3: // ServerCutText
2474
+ return this._handleServerCutText();
2475
+
2476
+ case 150: // EndOfContinuousUpdates
2477
+ first = !this._supportsContinuousUpdates;
2478
+ this._supportsContinuousUpdates = true;
2479
+ this._enabledContinuousUpdates = false;
2480
+ if (first) {
2481
+ this._enabledContinuousUpdates = true;
2482
+ this._updateContinuousUpdates();
2483
+ Log.Info("Enabling continuous updates.");
2484
+ } else {
2485
+ // FIXME: We need to send a framebufferupdaterequest here
2486
+ // if we add support for turning off continuous updates
2487
+ }
2488
+ return true;
2489
+
2490
+ case 248: // ServerFence
2491
+ return this._handleServerFenceMsg();
2492
+
2493
+ case 250: // XVP
2494
+ return this._handleXvpMsg();
2495
+
2496
+ default:
2497
+ this._fail("Unexpected server message (type " + msgType + ")");
2498
+ Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
2499
+ return true;
2500
+ }
2501
+ }
2502
+
2503
+ _framebufferUpdate() {
2504
+ if (this._FBU.rects === 0) {
2505
+ if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
2506
+ this._sock.rQskipBytes(1); // Padding
2507
+ this._FBU.rects = this._sock.rQshift16();
2508
+
2509
+ // Make sure the previous frame is fully rendered first
2510
+ // to avoid building up an excessive queue
2511
+ if (this._display.pending()) {
2512
+ this._flushing = true;
2513
+ this._display.flush()
2514
+ .then(() => {
2515
+ this._flushing = false;
2516
+ // Resume processing
2517
+ if (!this._sock.rQwait("message", 1)) {
2518
+ this._handleMessage();
2519
+ }
2520
+ });
2521
+ return false;
2522
+ }
2523
+ }
2524
+
2525
+ while (this._FBU.rects > 0) {
2526
+ if (this._FBU.encoding === null) {
2527
+ if (this._sock.rQwait("rect header", 12)) { return false; }
2528
+ /* New FramebufferUpdate */
2529
+
2530
+ this._FBU.x = this._sock.rQshift16();
2531
+ this._FBU.y = this._sock.rQshift16();
2532
+ this._FBU.width = this._sock.rQshift16();
2533
+ this._FBU.height = this._sock.rQshift16();
2534
+ this._FBU.encoding = this._sock.rQshift32();
2535
+ /* Encodings are signed */
2536
+ this._FBU.encoding >>= 0;
2537
+ }
2538
+
2539
+ if (!this._handleRect()) {
2540
+ return false;
2541
+ }
2542
+
2543
+ this._FBU.rects--;
2544
+ this._FBU.encoding = null;
2545
+ }
2546
+
2547
+ this._display.flip();
2548
+
2549
+ return true; // We finished this FBU
2550
+ }
2551
+
2552
+ _handleRect() {
2553
+ switch (this._FBU.encoding) {
2554
+ case encodings.pseudoEncodingLastRect:
2555
+ this._FBU.rects = 1; // Will be decreased when we return
2556
+ return true;
2557
+
2558
+ case encodings.pseudoEncodingVMwareCursor:
2559
+ return this._handleVMwareCursor();
2560
+
2561
+ case encodings.pseudoEncodingCursor:
2562
+ return this._handleCursor();
2563
+
2564
+ case encodings.pseudoEncodingQEMUExtendedKeyEvent:
2565
+ this._qemuExtKeyEventSupported = true;
2566
+ return true;
2567
+
2568
+ case encodings.pseudoEncodingDesktopName:
2569
+ return this._handleDesktopName();
2570
+
2571
+ case encodings.pseudoEncodingDesktopSize:
2572
+ this._resize(this._FBU.width, this._FBU.height);
2573
+ return true;
2574
+
2575
+ case encodings.pseudoEncodingExtendedDesktopSize:
2576
+ return this._handleExtendedDesktopSize();
2577
+
2578
+ case encodings.pseudoEncodingQEMULedEvent:
2579
+ return this._handleLedEvent();
2580
+
2581
+ default:
2582
+ return this._handleDataRect();
2583
+ }
2584
+ }
2585
+
2586
+ _handleVMwareCursor() {
2587
+ const hotx = this._FBU.x; // hotspot-x
2588
+ const hoty = this._FBU.y; // hotspot-y
2589
+ const w = this._FBU.width;
2590
+ const h = this._FBU.height;
2591
+ if (this._sock.rQwait("VMware cursor encoding", 1)) {
2592
+ return false;
2593
+ }
2594
+
2595
+ const cursorType = this._sock.rQshift8();
2596
+
2597
+ this._sock.rQshift8(); //Padding
2598
+
2599
+ let rgba;
2600
+ const bytesPerPixel = 4;
2601
+
2602
+ //Classic cursor
2603
+ if (cursorType == 0) {
2604
+ //Used to filter away unimportant bits.
2605
+ //OR is used for correct conversion in js.
2606
+ const PIXEL_MASK = 0xffffff00 | 0;
2607
+ rgba = new Array(w * h * bytesPerPixel);
2608
+
2609
+ if (this._sock.rQwait("VMware cursor classic encoding",
2610
+ (w * h * bytesPerPixel) * 2, 2)) {
2611
+ return false;
2612
+ }
2613
+
2614
+ let andMask = new Array(w * h);
2615
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2616
+ andMask[pixel] = this._sock.rQshift32();
2617
+ }
2618
+
2619
+ let xorMask = new Array(w * h);
2620
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2621
+ xorMask[pixel] = this._sock.rQshift32();
2622
+ }
2623
+
2624
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2625
+ if (andMask[pixel] == 0) {
2626
+ //Fully opaque pixel
2627
+ let bgr = xorMask[pixel];
2628
+ let r = bgr >> 8 & 0xff;
2629
+ let g = bgr >> 16 & 0xff;
2630
+ let b = bgr >> 24 & 0xff;
2631
+
2632
+ rgba[(pixel * bytesPerPixel) ] = r; //r
2633
+ rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
2634
+ rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
2635
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
2636
+
2637
+ } else if ((andMask[pixel] & PIXEL_MASK) ==
2638
+ PIXEL_MASK) {
2639
+ //Only screen value matters, no mouse colouring
2640
+ if (xorMask[pixel] == 0) {
2641
+ //Transparent pixel
2642
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2643
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2644
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2645
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
2646
+
2647
+ } else if ((xorMask[pixel] & PIXEL_MASK) ==
2648
+ PIXEL_MASK) {
2649
+ //Inverted pixel, not supported in browsers.
2650
+ //Fully opaque instead.
2651
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2652
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2653
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2654
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2655
+
2656
+ } else {
2657
+ //Unhandled xorMask
2658
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2659
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2660
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2661
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2662
+ }
2663
+
2664
+ } else {
2665
+ //Unhandled andMask
2666
+ rgba[(pixel * bytesPerPixel) ] = 0x00;
2667
+ rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2668
+ rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2669
+ rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2670
+ }
2671
+ }
2672
+
2673
+ //Alpha cursor.
2674
+ } else if (cursorType == 1) {
2675
+ if (this._sock.rQwait("VMware cursor alpha encoding",
2676
+ (w * h * 4), 2)) {
2677
+ return false;
2678
+ }
2679
+
2680
+ rgba = new Array(w * h * bytesPerPixel);
2681
+
2682
+ for (let pixel = 0; pixel < (w * h); pixel++) {
2683
+ let data = this._sock.rQshift32();
2684
+
2685
+ rgba[(pixel * 4) ] = data >> 24 & 0xff; //r
2686
+ rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
2687
+ rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff; //b
2688
+ rgba[(pixel * 4) + 3 ] = data & 0xff; //a
2689
+ }
2690
+
2691
+ } else {
2692
+ Log.Warn("The given cursor type is not supported: "
2693
+ + cursorType + " given.");
2694
+ return false;
2695
+ }
2696
+
2697
+ this._updateCursor(rgba, hotx, hoty, w, h);
2698
+
2699
+ return true;
2700
+ }
2701
+
2702
+ _handleCursor() {
2703
+ const hotx = this._FBU.x; // hotspot-x
2704
+ const hoty = this._FBU.y; // hotspot-y
2705
+ const w = this._FBU.width;
2706
+ const h = this._FBU.height;
2707
+
2708
+ const pixelslength = w * h * 4;
2709
+ const masklength = Math.ceil(w / 8) * h;
2710
+
2711
+ let bytes = pixelslength + masklength;
2712
+ if (this._sock.rQwait("cursor encoding", bytes)) {
2713
+ return false;
2714
+ }
2715
+
2716
+ // Decode from BGRX pixels + bit mask to RGBA
2717
+ const pixels = this._sock.rQshiftBytes(pixelslength);
2718
+ const mask = this._sock.rQshiftBytes(masklength);
2719
+ let rgba = new Uint8Array(w * h * 4);
2720
+
2721
+ let pixIdx = 0;
2722
+ for (let y = 0; y < h; y++) {
2723
+ for (let x = 0; x < w; x++) {
2724
+ let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);
2725
+ let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;
2726
+ rgba[pixIdx ] = pixels[pixIdx + 2];
2727
+ rgba[pixIdx + 1] = pixels[pixIdx + 1];
2728
+ rgba[pixIdx + 2] = pixels[pixIdx];
2729
+ rgba[pixIdx + 3] = alpha;
2730
+ pixIdx += 4;
2731
+ }
2732
+ }
2733
+
2734
+ this._updateCursor(rgba, hotx, hoty, w, h);
2735
+
2736
+ return true;
2737
+ }
2738
+
2739
+ _handleDesktopName() {
2740
+ if (this._sock.rQwait("DesktopName", 4)) {
2741
+ return false;
2742
+ }
2743
+
2744
+ let length = this._sock.rQshift32();
2745
+
2746
+ if (this._sock.rQwait("DesktopName", length, 4)) {
2747
+ return false;
2748
+ }
2749
+
2750
+ let name = this._sock.rQshiftStr(length);
2751
+ name = decodeUTF8(name, true);
2752
+
2753
+ this._setDesktopName(name);
2754
+
2755
+ return true;
2756
+ }
2757
+
2758
+ _handleLedEvent() {
2759
+ if (this._sock.rQwait("LED Status", 1)) {
2760
+ return false;
2761
+ }
2762
+
2763
+ let data = this._sock.rQshift8();
2764
+ // ScrollLock state can be retrieved with data & 1. This is currently not needed.
2765
+ let numLock = data & 2 ? true : false;
2766
+ let capsLock = data & 4 ? true : false;
2767
+ this._remoteCapsLock = capsLock;
2768
+ this._remoteNumLock = numLock;
2769
+
2770
+ return true;
2771
+ }
2772
+
2773
+ _handleExtendedDesktopSize() {
2774
+ if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
2775
+ return false;
2776
+ }
2777
+
2778
+ const numberOfScreens = this._sock.rQpeek8();
2779
+
2780
+ let bytes = 4 + (numberOfScreens * 16);
2781
+ if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
2782
+ return false;
2783
+ }
2784
+
2785
+ const firstUpdate = !this._supportsSetDesktopSize;
2786
+ this._supportsSetDesktopSize = true;
2787
+
2788
+ this._sock.rQskipBytes(1); // number-of-screens
2789
+ this._sock.rQskipBytes(3); // padding
2790
+
2791
+ for (let i = 0; i < numberOfScreens; i += 1) {
2792
+ // Save the id and flags of the first screen
2793
+ if (i === 0) {
2794
+ this._screenID = this._sock.rQshift32(); // id
2795
+ this._sock.rQskipBytes(2); // x-position
2796
+ this._sock.rQskipBytes(2); // y-position
2797
+ this._sock.rQskipBytes(2); // width
2798
+ this._sock.rQskipBytes(2); // height
2799
+ this._screenFlags = this._sock.rQshift32(); // flags
2800
+ } else {
2801
+ this._sock.rQskipBytes(16);
2802
+ }
2803
+ }
2804
+
2805
+ /*
2806
+ * The x-position indicates the reason for the change:
2807
+ *
2808
+ * 0 - server resized on its own
2809
+ * 1 - this client requested the resize
2810
+ * 2 - another client requested the resize
2811
+ */
2812
+
2813
+ // We need to handle errors when we requested the resize.
2814
+ if (this._FBU.x === 1 && this._FBU.y !== 0) {
2815
+ let msg = "";
2816
+ // The y-position indicates the status code from the server
2817
+ switch (this._FBU.y) {
2818
+ case 1:
2819
+ msg = "Resize is administratively prohibited";
2820
+ break;
2821
+ case 2:
2822
+ msg = "Out of resources";
2823
+ break;
2824
+ case 3:
2825
+ msg = "Invalid screen layout";
2826
+ break;
2827
+ default:
2828
+ msg = "Unknown reason";
2829
+ break;
2830
+ }
2831
+ Log.Warn("Server did not accept the resize request: "
2832
+ + msg);
2833
+ } else {
2834
+ this._resize(this._FBU.width, this._FBU.height);
2835
+ }
2836
+
2837
+ // Normally we only apply the current resize mode after a
2838
+ // window resize event. However there is no such trigger on the
2839
+ // initial connect. And we don't know if the server supports
2840
+ // resizing until we've gotten here.
2841
+ if (firstUpdate) {
2842
+ this._requestRemoteResize();
2843
+ }
2844
+
2845
+ return true;
2846
+ }
2847
+
2848
+ _handleDataRect() {
2849
+ let decoder = this._decoders[this._FBU.encoding];
2850
+ if (!decoder) {
2851
+ this._fail("Unsupported encoding (encoding: " +
2852
+ this._FBU.encoding + ")");
2853
+ return false;
2854
+ }
2855
+
2856
+ try {
2857
+ return decoder.decodeRect(this._FBU.x, this._FBU.y,
2858
+ this._FBU.width, this._FBU.height,
2859
+ this._sock, this._display,
2860
+ this._fbDepth);
2861
+ } catch (err) {
2862
+ this._fail("Error decoding rect: " + err);
2863
+ return false;
2864
+ }
2865
+ }
2866
+
2867
+ _updateContinuousUpdates() {
2868
+ if (!this._enabledContinuousUpdates) { return; }
2869
+
2870
+ RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
2871
+ this._fbWidth, this._fbHeight);
2872
+ }
2873
+
2874
+ _resize(width, height) {
2875
+ this._fbWidth = width;
2876
+ this._fbHeight = height;
2877
+
2878
+ this._display.resize(this._fbWidth, this._fbHeight);
2879
+
2880
+ // Adjust the visible viewport based on the new dimensions
2881
+ this._updateClip();
2882
+ this._updateScale();
2883
+
2884
+ this._updateContinuousUpdates();
2885
+
2886
+ // Keep this size until browser client size changes
2887
+ this._saveExpectedClientSize();
2888
+ }
2889
+
2890
+ _xvpOp(ver, op) {
2891
+ if (this._rfbXvpVer < ver) { return; }
2892
+ Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
2893
+ RFB.messages.xvpOp(this._sock, ver, op);
2894
+ }
2895
+
2896
+ _updateCursor(rgba, hotx, hoty, w, h) {
2897
+ this._cursorImage = {
2898
+ rgbaPixels: rgba,
2899
+ hotx: hotx, hoty: hoty, w: w, h: h,
2900
+ };
2901
+ this._refreshCursor();
2902
+ }
2903
+
2904
+ _shouldShowDotCursor() {
2905
+ // Called when this._cursorImage is updated
2906
+ if (!this._showDotCursor) {
2907
+ // User does not want to see the dot, so...
2908
+ return false;
2909
+ }
2910
+
2911
+ // The dot should not be shown if the cursor is already visible,
2912
+ // i.e. contains at least one not-fully-transparent pixel.
2913
+ // So iterate through all alpha bytes in rgba and stop at the
2914
+ // first non-zero.
2915
+ for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
2916
+ if (this._cursorImage.rgbaPixels[i]) {
2917
+ return false;
2918
+ }
2919
+ }
2920
+
2921
+ // At this point, we know that the cursor is fully transparent, and
2922
+ // the user wants to see the dot instead of this.
2923
+ return true;
2924
+ }
2925
+
2926
+ _refreshCursor() {
2927
+ if (this._rfbConnectionState !== "connecting" &&
2928
+ this._rfbConnectionState !== "connected") {
2929
+ return;
2930
+ }
2931
+ const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
2932
+ this._cursor.change(image.rgbaPixels,
2933
+ image.hotx, image.hoty,
2934
+ image.w, image.h
2935
+ );
2936
+ }
2937
+
2938
+ static genDES(password, challenge) {
2939
+ const passwordChars = password.split('').map(c => c.charCodeAt(0));
2940
+ const key = legacyCrypto.importKey(
2941
+ "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
2942
+ return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
2943
+ }
2944
+ }
2945
+
2946
+ // Class Methods
2947
+ RFB.messages = {
2948
+ keyEvent(sock, keysym, down) {
2949
+ sock.sQpush8(4); // msg-type
2950
+ sock.sQpush8(down);
2951
+
2952
+ sock.sQpush16(0);
2953
+
2954
+ sock.sQpush32(keysym);
2955
+
2956
+ sock.flush();
2957
+ },
2958
+
2959
+ QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
2960
+ function getRFBkeycode(xtScanCode) {
2961
+ const upperByte = (keycode >> 8);
2962
+ const lowerByte = (keycode & 0x00ff);
2963
+ if (upperByte === 0xe0 && lowerByte < 0x7f) {
2964
+ return lowerByte | 0x80;
2965
+ }
2966
+ return xtScanCode;
2967
+ }
2968
+
2969
+ sock.sQpush8(255); // msg-type
2970
+ sock.sQpush8(0); // sub msg-type
2971
+
2972
+ sock.sQpush16(down);
2973
+
2974
+ sock.sQpush32(keysym);
2975
+
2976
+ const RFBkeycode = getRFBkeycode(keycode);
2977
+
2978
+ sock.sQpush32(RFBkeycode);
2979
+
2980
+ sock.flush();
2981
+ },
2982
+
2983
+ pointerEvent(sock, x, y, mask) {
2984
+ sock.sQpush8(5); // msg-type
2985
+
2986
+ sock.sQpush8(mask);
2987
+
2988
+ sock.sQpush16(x);
2989
+ sock.sQpush16(y);
2990
+
2991
+ sock.flush();
2992
+ },
2993
+
2994
+ // Used to build Notify and Request data.
2995
+ _buildExtendedClipboardFlags(actions, formats) {
2996
+ let data = new Uint8Array(4);
2997
+ let formatFlag = 0x00000000;
2998
+ let actionFlag = 0x00000000;
2999
+
3000
+ for (let i = 0; i < actions.length; i++) {
3001
+ actionFlag |= actions[i];
3002
+ }
3003
+
3004
+ for (let i = 0; i < formats.length; i++) {
3005
+ formatFlag |= formats[i];
3006
+ }
3007
+
3008
+ data[0] = actionFlag >> 24; // Actions
3009
+ data[1] = 0x00; // Reserved
3010
+ data[2] = 0x00; // Reserved
3011
+ data[3] = formatFlag; // Formats
3012
+
3013
+ return data;
3014
+ },
3015
+
3016
+ extendedClipboardProvide(sock, formats, inData) {
3017
+ // Deflate incomming data and their sizes
3018
+ let deflator = new Deflator();
3019
+ let dataToDeflate = [];
3020
+
3021
+ for (let i = 0; i < formats.length; i++) {
3022
+ // We only support the format Text at this time
3023
+ if (formats[i] != extendedClipboardFormatText) {
3024
+ throw new Error("Unsupported extended clipboard format for Provide message.");
3025
+ }
3026
+
3027
+ // Change lone \r or \n into \r\n as defined in rfbproto
3028
+ inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");
3029
+
3030
+ // Check if it already has \0
3031
+ let text = encodeUTF8(inData[i] + "\0");
3032
+
3033
+ dataToDeflate.push( (text.length >> 24) & 0xFF,
3034
+ (text.length >> 16) & 0xFF,
3035
+ (text.length >> 8) & 0xFF,
3036
+ (text.length & 0xFF));
3037
+
3038
+ for (let j = 0; j < text.length; j++) {
3039
+ dataToDeflate.push(text.charCodeAt(j));
3040
+ }
3041
+ }
3042
+
3043
+ let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));
3044
+
3045
+ // Build data to send
3046
+ let data = new Uint8Array(4 + deflatedData.length);
3047
+ data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],
3048
+ formats));
3049
+ data.set(deflatedData, 4);
3050
+
3051
+ RFB.messages.clientCutText(sock, data, true);
3052
+ },
3053
+
3054
+ extendedClipboardNotify(sock, formats) {
3055
+ let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],
3056
+ formats);
3057
+ RFB.messages.clientCutText(sock, flags, true);
3058
+ },
3059
+
3060
+ extendedClipboardRequest(sock, formats) {
3061
+ let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],
3062
+ formats);
3063
+ RFB.messages.clientCutText(sock, flags, true);
3064
+ },
3065
+
3066
+ extendedClipboardCaps(sock, actions, formats) {
3067
+ let formatKeys = Object.keys(formats);
3068
+ let data = new Uint8Array(4 + (4 * formatKeys.length));
3069
+
3070
+ formatKeys.map(x => parseInt(x));
3071
+ formatKeys.sort((a, b) => a - b);
3072
+
3073
+ data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));
3074
+
3075
+ let loopOffset = 4;
3076
+ for (let i = 0; i < formatKeys.length; i++) {
3077
+ data[loopOffset] = formats[formatKeys[i]] >> 24;
3078
+ data[loopOffset + 1] = formats[formatKeys[i]] >> 16;
3079
+ data[loopOffset + 2] = formats[formatKeys[i]] >> 8;
3080
+ data[loopOffset + 3] = formats[formatKeys[i]] >> 0;
3081
+
3082
+ loopOffset += 4;
3083
+ data[3] |= (1 << formatKeys[i]); // Update our format flags
3084
+ }
3085
+
3086
+ RFB.messages.clientCutText(sock, data, true);
3087
+ },
3088
+
3089
+ clientCutText(sock, data, extended = false) {
3090
+ sock.sQpush8(6); // msg-type
3091
+
3092
+ sock.sQpush8(0); // padding
3093
+ sock.sQpush8(0); // padding
3094
+ sock.sQpush8(0); // padding
3095
+
3096
+ let length;
3097
+ if (extended) {
3098
+ length = toUnsigned32bit(-data.length);
3099
+ } else {
3100
+ length = data.length;
3101
+ }
3102
+
3103
+ sock.sQpush32(length);
3104
+ sock.sQpushBytes(data);
3105
+ sock.flush();
3106
+ },
3107
+
3108
+ setDesktopSize(sock, width, height, id, flags) {
3109
+ sock.sQpush8(251); // msg-type
3110
+
3111
+ sock.sQpush8(0); // padding
3112
+
3113
+ sock.sQpush16(width);
3114
+ sock.sQpush16(height);
3115
+
3116
+ sock.sQpush8(1); // number-of-screens
3117
+
3118
+ sock.sQpush8(0); // padding
3119
+
3120
+ // screen array
3121
+ sock.sQpush32(id);
3122
+ sock.sQpush16(0); // x-position
3123
+ sock.sQpush16(0); // y-position
3124
+ sock.sQpush16(width);
3125
+ sock.sQpush16(height);
3126
+ sock.sQpush32(flags);
3127
+
3128
+ sock.flush();
3129
+ },
3130
+
3131
+ clientFence(sock, flags, payload) {
3132
+ sock.sQpush8(248); // msg-type
3133
+
3134
+ sock.sQpush8(0); // padding
3135
+ sock.sQpush8(0); // padding
3136
+ sock.sQpush8(0); // padding
3137
+
3138
+ sock.sQpush32(flags);
3139
+
3140
+ sock.sQpush8(payload.length);
3141
+ sock.sQpushString(payload);
3142
+
3143
+ sock.flush();
3144
+ },
3145
+
3146
+ enableContinuousUpdates(sock, enable, x, y, width, height) {
3147
+ sock.sQpush8(150); // msg-type
3148
+
3149
+ sock.sQpush8(enable);
3150
+
3151
+ sock.sQpush16(x);
3152
+ sock.sQpush16(y);
3153
+ sock.sQpush16(width);
3154
+ sock.sQpush16(height);
3155
+
3156
+ sock.flush();
3157
+ },
3158
+
3159
+ pixelFormat(sock, depth, trueColor) {
3160
+ let bpp;
3161
+
3162
+ if (depth > 16) {
3163
+ bpp = 32;
3164
+ } else if (depth > 8) {
3165
+ bpp = 16;
3166
+ } else {
3167
+ bpp = 8;
3168
+ }
3169
+
3170
+ const bits = Math.floor(depth/3);
3171
+
3172
+ sock.sQpush8(0); // msg-type
3173
+
3174
+ sock.sQpush8(0); // padding
3175
+ sock.sQpush8(0); // padding
3176
+ sock.sQpush8(0); // padding
3177
+
3178
+ sock.sQpush8(bpp);
3179
+ sock.sQpush8(depth);
3180
+ sock.sQpush8(0); // little-endian
3181
+ sock.sQpush8(trueColor ? 1 : 0);
3182
+
3183
+ sock.sQpush16((1 << bits) - 1); // red-max
3184
+ sock.sQpush16((1 << bits) - 1); // green-max
3185
+ sock.sQpush16((1 << bits) - 1); // blue-max
3186
+
3187
+ sock.sQpush8(bits * 0); // red-shift
3188
+ sock.sQpush8(bits * 1); // green-shift
3189
+ sock.sQpush8(bits * 2); // blue-shift
3190
+
3191
+ sock.sQpush8(0); // padding
3192
+ sock.sQpush8(0); // padding
3193
+ sock.sQpush8(0); // padding
3194
+
3195
+ sock.flush();
3196
+ },
3197
+
3198
+ clientEncodings(sock, encodings) {
3199
+ sock.sQpush8(2); // msg-type
3200
+
3201
+ sock.sQpush8(0); // padding
3202
+
3203
+ sock.sQpush16(encodings.length);
3204
+ for (let i = 0; i < encodings.length; i++) {
3205
+ sock.sQpush32(encodings[i]);
3206
+ }
3207
+
3208
+ sock.flush();
3209
+ },
3210
+
3211
+ fbUpdateRequest(sock, incremental, x, y, w, h) {
3212
+ if (typeof(x) === "undefined") { x = 0; }
3213
+ if (typeof(y) === "undefined") { y = 0; }
3214
+
3215
+ sock.sQpush8(3); // msg-type
3216
+
3217
+ sock.sQpush8(incremental ? 1 : 0);
3218
+
3219
+ sock.sQpush16(x);
3220
+ sock.sQpush16(y);
3221
+ sock.sQpush16(w);
3222
+ sock.sQpush16(h);
3223
+
3224
+ sock.flush();
3225
+ },
3226
+
3227
+ xvpOp(sock, ver, op) {
3228
+ sock.sQpush8(250); // msg-type
3229
+
3230
+ sock.sQpush8(0); // padding
3231
+
3232
+ sock.sQpush8(ver);
3233
+ sock.sQpush8(op);
3234
+
3235
+ sock.flush();
3236
+ }
3237
+ };
3238
+
3239
+ RFB.cursors = {
3240
+ none: {
3241
+ rgbaPixels: new Uint8Array(),
3242
+ w: 0, h: 0,
3243
+ hotx: 0, hoty: 0,
3244
+ },
3245
+
3246
+ dot: {
3247
+ /* eslint-disable indent */
3248
+ rgbaPixels: new Uint8Array([
3249
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
3250
+ 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
3251
+ 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
3252
+ ]),
3253
+ /* eslint-enable indent */
3254
+ w: 3, h: 3,
3255
+ hotx: 1, hoty: 1,
3256
+ }
3257
+ };