susi-qemu 0.0.2 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/susi +9 -4
- data/lib/disk.rb +7 -5
- data/lib/novnc/core/base64.js +104 -0
- data/lib/novnc/core/crypto/aes.js +178 -0
- data/lib/novnc/core/crypto/bigint.js +34 -0
- data/lib/novnc/core/crypto/crypto.js +90 -0
- data/lib/novnc/core/crypto/des.js +330 -0
- data/lib/novnc/core/crypto/dh.js +55 -0
- data/lib/novnc/core/crypto/md5.js +82 -0
- data/lib/novnc/core/crypto/rsa.js +132 -0
- data/lib/novnc/core/decoders/copyrect.js +27 -0
- data/lib/novnc/core/decoders/h264.js +321 -0
- data/lib/novnc/core/decoders/hextile.js +181 -0
- data/lib/novnc/core/decoders/jpeg.js +146 -0
- data/lib/novnc/core/decoders/raw.js +59 -0
- data/lib/novnc/core/decoders/rre.js +44 -0
- data/lib/novnc/core/decoders/tight.js +393 -0
- data/lib/novnc/core/decoders/tightpng.js +27 -0
- data/lib/novnc/core/decoders/zlib.js +51 -0
- data/lib/novnc/core/decoders/zrle.js +185 -0
- data/lib/novnc/core/deflator.js +84 -0
- data/lib/novnc/core/display.js +575 -0
- data/lib/novnc/core/encodings.js +53 -0
- data/lib/novnc/core/inflator.js +65 -0
- data/lib/novnc/core/input/domkeytable.js +311 -0
- data/lib/novnc/core/input/fixedkeys.js +129 -0
- data/lib/novnc/core/input/gesturehandler.js +567 -0
- data/lib/novnc/core/input/keyboard.js +294 -0
- data/lib/novnc/core/input/keysym.js +616 -0
- data/lib/novnc/core/input/keysymdef.js +688 -0
- data/lib/novnc/core/input/util.js +191 -0
- data/lib/novnc/core/input/vkeys.js +116 -0
- data/lib/novnc/core/input/xtscancodes.js +173 -0
- data/lib/novnc/core/ra2.js +312 -0
- data/lib/novnc/core/rfb.js +3257 -0
- data/lib/novnc/core/util/browser.js +172 -0
- data/lib/novnc/core/util/cursor.js +249 -0
- data/lib/novnc/core/util/element.js +32 -0
- data/lib/novnc/core/util/events.js +138 -0
- data/lib/novnc/core/util/eventtarget.js +35 -0
- data/lib/novnc/core/util/int.js +15 -0
- data/lib/novnc/core/util/logging.js +56 -0
- data/lib/novnc/core/util/strings.js +28 -0
- data/lib/novnc/core/websock.js +365 -0
- data/lib/novnc/screen.html +21 -0
- data/lib/novnc/vendor/pako/lib/utils/common.js +45 -0
- data/lib/novnc/vendor/pako/lib/zlib/adler32.js +27 -0
- data/lib/novnc/vendor/pako/lib/zlib/constants.js +47 -0
- data/lib/novnc/vendor/pako/lib/zlib/crc32.js +36 -0
- data/lib/novnc/vendor/pako/lib/zlib/deflate.js +1846 -0
- data/lib/novnc/vendor/pako/lib/zlib/gzheader.js +35 -0
- data/lib/novnc/vendor/pako/lib/zlib/inffast.js +324 -0
- data/lib/novnc/vendor/pako/lib/zlib/inflate.js +1527 -0
- data/lib/novnc/vendor/pako/lib/zlib/inftrees.js +322 -0
- data/lib/novnc/vendor/pako/lib/zlib/messages.js +11 -0
- data/lib/novnc/vendor/pako/lib/zlib/trees.js +1195 -0
- data/lib/novnc/vendor/pako/lib/zlib/zstream.js +24 -0
- data/lib/output.rb +11 -0
- data/lib/qmp.rb +6 -0
- data/lib/ssh.rb +3 -1
- data/lib/susi.rb +7 -6
- data/lib/version.rb +1 -1
- data/lib/vm.rb +44 -26
- data/lib/vnc.rb +34 -31
- 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
|
+
};
|