susi-qemu 0.0.3 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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 +57 -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
+ };