@novnc/novnc 1.2.0 → 1.3.0-g65d6357
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +0 -6
- package/README.md +9 -4
- package/core/decoders/copyrect.js +5 -0
- package/core/decoders/hextile.js +57 -3
- package/core/decoders/raw.js +12 -2
- package/core/decoders/tight.js +24 -8
- package/core/decoders/zrle.js +185 -0
- package/core/display.js +9 -151
- package/core/encodings.js +2 -0
- package/core/input/domkeytable.js +25 -21
- package/core/input/keyboard.js +12 -127
- package/core/input/util.js +18 -35
- package/core/input/vkeys.js +0 -1
- package/core/input/xtscancodes.js +5 -3
- package/core/rfb.js +119 -109
- package/core/util/browser.js +0 -17
- package/core/util/cursor.js +1 -11
- package/core/util/events.js +0 -4
- package/core/websock.js +76 -17
- package/docs/API.md +10 -3
- package/docs/LIBRARY.md +3 -7
- package/lib/base64.js +4 -4
- package/lib/decoders/copyrect.js +7 -2
- package/lib/decoders/hextile.js +63 -7
- package/lib/decoders/raw.js +13 -4
- package/lib/decoders/rre.js +2 -2
- package/lib/decoders/tight.js +38 -20
- package/lib/decoders/tightpng.js +8 -8
- package/lib/decoders/zrle.js +234 -0
- package/lib/deflator.js +4 -4
- package/lib/des.js +2 -2
- package/lib/display.js +45 -212
- package/lib/encodings.js +4 -0
- package/lib/inflator.js +4 -4
- package/lib/input/domkeytable.js +197 -194
- package/lib/input/fixedkeys.js +2 -2
- package/lib/input/gesturehandler.js +2 -2
- package/lib/input/keyboard.js +38 -158
- package/lib/input/keysym.js +2 -2
- package/lib/input/keysymdef.js +2 -2
- package/lib/input/util.js +34 -79
- package/lib/input/vkeys.js +2 -4
- package/lib/input/xtscancodes.js +11 -5
- package/lib/rfb.js +295 -285
- package/lib/util/browser.js +8 -26
- package/lib/util/cursor.js +4 -16
- package/lib/util/events.js +3 -5
- package/lib/util/eventtarget.js +3 -3
- package/lib/util/int.js +1 -1
- package/lib/util/logging.js +2 -2
- package/lib/vendor/pako/lib/utils/common.js +2 -2
- package/lib/vendor/pako/lib/zlib/adler32.js +1 -1
- package/lib/vendor/pako/lib/zlib/constants.js +2 -2
- package/lib/vendor/pako/lib/zlib/crc32.js +1 -1
- package/lib/vendor/pako/lib/zlib/deflate.js +113 -112
- package/lib/vendor/pako/lib/zlib/gzheader.js +1 -1
- package/lib/vendor/pako/lib/zlib/inffast.js +5 -5
- package/lib/vendor/pako/lib/zlib/inflate.js +50 -48
- package/lib/vendor/pako/lib/zlib/inftrees.js +3 -3
- package/lib/vendor/pako/lib/zlib/messages.js +2 -2
- package/lib/vendor/pako/lib/zlib/trees.js +4 -4
- package/lib/vendor/pako/lib/zlib/zstream.js +1 -1
- package/lib/websock.js +105 -44
- package/package.json +2 -7
- package/core/util/polyfill.js +0 -61
- package/lib/util/polyfill.js +0 -72
- package/lib/vendor/promise.js +0 -255
package/core/rfb.js
CHANGED
|
@@ -25,7 +25,6 @@ import DES from "./des.js";
|
|
|
25
25
|
import KeyTable from "./input/keysym.js";
|
|
26
26
|
import XtScancode from "./input/xtscancodes.js";
|
|
27
27
|
import { encodings } from "./encodings.js";
|
|
28
|
-
import "./util/polyfill.js";
|
|
29
28
|
|
|
30
29
|
import RawDecoder from "./decoders/raw.js";
|
|
31
30
|
import CopyRectDecoder from "./decoders/copyrect.js";
|
|
@@ -33,6 +32,7 @@ import RREDecoder from "./decoders/rre.js";
|
|
|
33
32
|
import HextileDecoder from "./decoders/hextile.js";
|
|
34
33
|
import TightDecoder from "./decoders/tight.js";
|
|
35
34
|
import TightPNGDecoder from "./decoders/tightpng.js";
|
|
35
|
+
import ZRLEDecoder from "./decoders/zrle.js";
|
|
36
36
|
|
|
37
37
|
// How many seconds to wait for a disconnect to finish
|
|
38
38
|
const DISCONNECT_TIMEOUT = 3;
|
|
@@ -67,20 +67,25 @@ const extendedClipboardActionPeek = 1 << 26;
|
|
|
67
67
|
const extendedClipboardActionNotify = 1 << 27;
|
|
68
68
|
const extendedClipboardActionProvide = 1 << 28;
|
|
69
69
|
|
|
70
|
-
|
|
71
70
|
export default class RFB extends EventTargetMixin {
|
|
72
|
-
constructor(target,
|
|
71
|
+
constructor(target, urlOrChannel, options) {
|
|
73
72
|
if (!target) {
|
|
74
73
|
throw new Error("Must specify target");
|
|
75
74
|
}
|
|
76
|
-
if (!
|
|
77
|
-
throw new Error("Must specify URL");
|
|
75
|
+
if (!urlOrChannel) {
|
|
76
|
+
throw new Error("Must specify URL, WebSocket or RTCDataChannel");
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
super();
|
|
81
80
|
|
|
82
81
|
this._target = target;
|
|
83
|
-
|
|
82
|
+
|
|
83
|
+
if (typeof urlOrChannel === "string") {
|
|
84
|
+
this._url = urlOrChannel;
|
|
85
|
+
} else {
|
|
86
|
+
this._url = null;
|
|
87
|
+
this._rawChannel = urlOrChannel;
|
|
88
|
+
}
|
|
84
89
|
|
|
85
90
|
// Connection details
|
|
86
91
|
options = options || {};
|
|
@@ -130,6 +135,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
130
135
|
this._flushing = false; // Display flushing state
|
|
131
136
|
this._keyboard = null; // Keyboard input handler object
|
|
132
137
|
this._gestures = null; // Gesture input handler object
|
|
138
|
+
this._resizeObserver = null; // Resize observer object
|
|
133
139
|
|
|
134
140
|
// Timers
|
|
135
141
|
this._disconnTimer = null; // disconnection timer
|
|
@@ -167,7 +173,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
167
173
|
// Bound event handlers
|
|
168
174
|
this._eventHandlers = {
|
|
169
175
|
focusCanvas: this._focusCanvas.bind(this),
|
|
170
|
-
|
|
176
|
+
handleResize: this._handleResize.bind(this),
|
|
171
177
|
handleMouse: this._handleMouse.bind(this),
|
|
172
178
|
handleWheel: this._handleWheel.bind(this),
|
|
173
179
|
handleGesture: this._handleGesture.bind(this),
|
|
@@ -187,8 +193,6 @@ export default class RFB extends EventTargetMixin {
|
|
|
187
193
|
this._canvas.style.margin = 'auto';
|
|
188
194
|
// Some browsers add an outline on focus
|
|
189
195
|
this._canvas.style.outline = 'none';
|
|
190
|
-
// IE miscalculates width without this :(
|
|
191
|
-
this._canvas.style.flexShrink = '0';
|
|
192
196
|
this._canvas.width = 0;
|
|
193
197
|
this._canvas.height = 0;
|
|
194
198
|
this._canvas.tabIndex = -1;
|
|
@@ -215,6 +219,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
215
219
|
this._decoders[encodings.encodingHextile] = new HextileDecoder();
|
|
216
220
|
this._decoders[encodings.encodingTight] = new TightDecoder();
|
|
217
221
|
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
|
222
|
+
this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
|
|
218
223
|
|
|
219
224
|
// NB: nothing that needs explicit teardown should be done
|
|
220
225
|
// before this point, since this can throw an exception
|
|
@@ -232,58 +237,15 @@ export default class RFB extends EventTargetMixin {
|
|
|
232
237
|
this._gestures = new GestureHandler();
|
|
233
238
|
|
|
234
239
|
this._sock = new Websock();
|
|
235
|
-
this._sock.on('
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this._sock.on('
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this._rfbInitState = 'ProtocolVersion';
|
|
242
|
-
Log.Debug("Starting VNC handshake");
|
|
243
|
-
} else {
|
|
244
|
-
this._fail("Unexpected server connection while " +
|
|
245
|
-
this._rfbConnectionState);
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
this._sock.on('close', (e) => {
|
|
249
|
-
Log.Debug("WebSocket on-close event");
|
|
250
|
-
let msg = "";
|
|
251
|
-
if (e.code) {
|
|
252
|
-
msg = "(code: " + e.code;
|
|
253
|
-
if (e.reason) {
|
|
254
|
-
msg += ", reason: " + e.reason;
|
|
255
|
-
}
|
|
256
|
-
msg += ")";
|
|
257
|
-
}
|
|
258
|
-
switch (this._rfbConnectionState) {
|
|
259
|
-
case 'connecting':
|
|
260
|
-
this._fail("Connection closed " + msg);
|
|
261
|
-
break;
|
|
262
|
-
case 'connected':
|
|
263
|
-
// Handle disconnects that were initiated server-side
|
|
264
|
-
this._updateConnectionState('disconnecting');
|
|
265
|
-
this._updateConnectionState('disconnected');
|
|
266
|
-
break;
|
|
267
|
-
case 'disconnecting':
|
|
268
|
-
// Normal disconnection path
|
|
269
|
-
this._updateConnectionState('disconnected');
|
|
270
|
-
break;
|
|
271
|
-
case 'disconnected':
|
|
272
|
-
this._fail("Unexpected server disconnect " +
|
|
273
|
-
"when already disconnected " + msg);
|
|
274
|
-
break;
|
|
275
|
-
default:
|
|
276
|
-
this._fail("Unexpected server disconnect before connecting " +
|
|
277
|
-
msg);
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
this._sock.off('close');
|
|
281
|
-
});
|
|
282
|
-
this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
|
|
240
|
+
this._sock.on('open', this._socketOpen.bind(this));
|
|
241
|
+
this._sock.on('close', this._socketClose.bind(this));
|
|
242
|
+
this._sock.on('message', this._handleMessage.bind(this));
|
|
243
|
+
this._sock.on('error', this._socketError.bind(this));
|
|
244
|
+
|
|
245
|
+
this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
|
|
283
246
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
setTimeout(this._updateConnectionState.bind(this, 'connecting'));
|
|
247
|
+
// All prepared, kick off the connection
|
|
248
|
+
this._updateConnectionState('connecting');
|
|
287
249
|
|
|
288
250
|
Log.Debug("<< RFB.constructor");
|
|
289
251
|
|
|
@@ -472,8 +434,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
472
434
|
}
|
|
473
435
|
}
|
|
474
436
|
|
|
475
|
-
focus() {
|
|
476
|
-
this._canvas.focus();
|
|
437
|
+
focus(options) {
|
|
438
|
+
this._canvas.focus(options);
|
|
477
439
|
}
|
|
478
440
|
|
|
479
441
|
blur() {
|
|
@@ -504,16 +466,22 @@ export default class RFB extends EventTargetMixin {
|
|
|
504
466
|
_connect() {
|
|
505
467
|
Log.Debug(">> RFB.connect");
|
|
506
468
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
// WebSocket.onopen transitions to the RFB init states
|
|
469
|
+
if (this._url) {
|
|
470
|
+
Log.Info(`connecting to ${this._url}`);
|
|
511
471
|
this._sock.open(this._url, this._wsProtocols);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
472
|
+
} else {
|
|
473
|
+
Log.Info(`attaching ${this._rawChannel} to Websock`);
|
|
474
|
+
this._sock.attach(this._rawChannel);
|
|
475
|
+
|
|
476
|
+
if (this._sock.readyState === 'closed') {
|
|
477
|
+
throw Error("Cannot use already closed WebSocket/RTCDataChannel");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (this._sock.readyState === 'open') {
|
|
481
|
+
// FIXME: _socketOpen() can in theory call _fail(), which
|
|
482
|
+
// isn't allowed this early, but I'm not sure that can
|
|
483
|
+
// happen without a bug messing up our state variables
|
|
484
|
+
this._socketOpen();
|
|
517
485
|
}
|
|
518
486
|
}
|
|
519
487
|
|
|
@@ -525,9 +493,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
525
493
|
this._cursor.attach(this._canvas);
|
|
526
494
|
this._refreshCursor();
|
|
527
495
|
|
|
528
|
-
// Monitor size changes of the screen
|
|
529
|
-
|
|
530
|
-
window.addEventListener('resize', this._eventHandlers.windowResize);
|
|
496
|
+
// Monitor size changes of the screen element
|
|
497
|
+
this._resizeObserver.observe(this._screen);
|
|
531
498
|
|
|
532
499
|
// Always grab focus on some kind of click event
|
|
533
500
|
this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
|
|
@@ -568,7 +535,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
568
535
|
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
|
|
569
536
|
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
|
|
570
537
|
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
|
|
571
|
-
|
|
538
|
+
this._resizeObserver.disconnect();
|
|
572
539
|
this._keyboard.ungrab();
|
|
573
540
|
this._gestures.detach();
|
|
574
541
|
this._sock.close();
|
|
@@ -587,12 +554,64 @@ export default class RFB extends EventTargetMixin {
|
|
|
587
554
|
Log.Debug("<< RFB.disconnect");
|
|
588
555
|
}
|
|
589
556
|
|
|
557
|
+
_socketOpen() {
|
|
558
|
+
if ((this._rfbConnectionState === 'connecting') &&
|
|
559
|
+
(this._rfbInitState === '')) {
|
|
560
|
+
this._rfbInitState = 'ProtocolVersion';
|
|
561
|
+
Log.Debug("Starting VNC handshake");
|
|
562
|
+
} else {
|
|
563
|
+
this._fail("Unexpected server connection while " +
|
|
564
|
+
this._rfbConnectionState);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
_socketClose(e) {
|
|
569
|
+
Log.Debug("WebSocket on-close event");
|
|
570
|
+
let msg = "";
|
|
571
|
+
if (e.code) {
|
|
572
|
+
msg = "(code: " + e.code;
|
|
573
|
+
if (e.reason) {
|
|
574
|
+
msg += ", reason: " + e.reason;
|
|
575
|
+
}
|
|
576
|
+
msg += ")";
|
|
577
|
+
}
|
|
578
|
+
switch (this._rfbConnectionState) {
|
|
579
|
+
case 'connecting':
|
|
580
|
+
this._fail("Connection closed " + msg);
|
|
581
|
+
break;
|
|
582
|
+
case 'connected':
|
|
583
|
+
// Handle disconnects that were initiated server-side
|
|
584
|
+
this._updateConnectionState('disconnecting');
|
|
585
|
+
this._updateConnectionState('disconnected');
|
|
586
|
+
break;
|
|
587
|
+
case 'disconnecting':
|
|
588
|
+
// Normal disconnection path
|
|
589
|
+
this._updateConnectionState('disconnected');
|
|
590
|
+
break;
|
|
591
|
+
case 'disconnected':
|
|
592
|
+
this._fail("Unexpected server disconnect " +
|
|
593
|
+
"when already disconnected " + msg);
|
|
594
|
+
break;
|
|
595
|
+
default:
|
|
596
|
+
this._fail("Unexpected server disconnect before connecting " +
|
|
597
|
+
msg);
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
this._sock.off('close');
|
|
601
|
+
// Delete reference to raw channel to allow cleanup.
|
|
602
|
+
this._rawChannel = null;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
_socketError(e) {
|
|
606
|
+
Log.Warn("WebSocket on-error event");
|
|
607
|
+
}
|
|
608
|
+
|
|
590
609
|
_focusCanvas(event) {
|
|
591
610
|
if (!this.focusOnClick) {
|
|
592
611
|
return;
|
|
593
612
|
}
|
|
594
613
|
|
|
595
|
-
this.focus();
|
|
614
|
+
this.focus({ preventScroll: true });
|
|
596
615
|
}
|
|
597
616
|
|
|
598
617
|
_setDesktopName(name) {
|
|
@@ -602,7 +621,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
602
621
|
{ detail: { name: this._fbName } }));
|
|
603
622
|
}
|
|
604
623
|
|
|
605
|
-
|
|
624
|
+
_handleResize() {
|
|
606
625
|
// If the window resized then our screen element might have
|
|
607
626
|
// as well. Update the viewport dimensions.
|
|
608
627
|
window.requestAnimationFrame(() => {
|
|
@@ -1263,17 +1282,6 @@ export default class RFB extends EventTargetMixin {
|
|
|
1263
1282
|
}
|
|
1264
1283
|
|
|
1265
1284
|
_negotiateSecurity() {
|
|
1266
|
-
// Polyfill since IE and PhantomJS doesn't have
|
|
1267
|
-
// TypedArray.includes()
|
|
1268
|
-
function includes(item, array) {
|
|
1269
|
-
for (let i = 0; i < array.length; i++) {
|
|
1270
|
-
if (array[i] === item) {
|
|
1271
|
-
return true;
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
return false;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
1285
|
if (this._rfbVersion >= 3.7) {
|
|
1278
1286
|
// Server sends supported list, client decides
|
|
1279
1287
|
const numTypes = this._sock.rQshift8();
|
|
@@ -1290,15 +1298,15 @@ export default class RFB extends EventTargetMixin {
|
|
|
1290
1298
|
Log.Debug("Server security types: " + types);
|
|
1291
1299
|
|
|
1292
1300
|
// Look for each auth in preferred order
|
|
1293
|
-
if (includes(1
|
|
1301
|
+
if (types.includes(1)) {
|
|
1294
1302
|
this._rfbAuthScheme = 1; // None
|
|
1295
|
-
} else if (includes(22
|
|
1303
|
+
} else if (types.includes(22)) {
|
|
1296
1304
|
this._rfbAuthScheme = 22; // XVP
|
|
1297
|
-
} else if (includes(16
|
|
1305
|
+
} else if (types.includes(16)) {
|
|
1298
1306
|
this._rfbAuthScheme = 16; // Tight
|
|
1299
|
-
} else if (includes(2
|
|
1307
|
+
} else if (types.includes(2)) {
|
|
1300
1308
|
this._rfbAuthScheme = 2; // VNC Auth
|
|
1301
|
-
} else if (includes(19
|
|
1309
|
+
} else if (types.includes(19)) {
|
|
1302
1310
|
this._rfbAuthScheme = 19; // VeNCrypt Auth
|
|
1303
1311
|
} else {
|
|
1304
1312
|
return this._fail("Unsupported security types (types: " + types + ")");
|
|
@@ -1441,8 +1449,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
1441
1449
|
|
|
1442
1450
|
// negotiated Plain subtype, server waits for password
|
|
1443
1451
|
if (this._rfbVeNCryptState == 4) {
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
1452
|
+
if (this._rfbCredentials.username === undefined ||
|
|
1453
|
+
this._rfbCredentials.password === undefined) {
|
|
1446
1454
|
this.dispatchEvent(new CustomEvent(
|
|
1447
1455
|
"credentialsrequired",
|
|
1448
1456
|
{ detail: { types: ["username", "password"] } }));
|
|
@@ -1452,9 +1460,18 @@ export default class RFB extends EventTargetMixin {
|
|
|
1452
1460
|
const user = encodeUTF8(this._rfbCredentials.username);
|
|
1453
1461
|
const pass = encodeUTF8(this._rfbCredentials.password);
|
|
1454
1462
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1463
|
+
this._sock.send([
|
|
1464
|
+
(user.length >> 24) & 0xFF,
|
|
1465
|
+
(user.length >> 16) & 0xFF,
|
|
1466
|
+
(user.length >> 8) & 0xFF,
|
|
1467
|
+
user.length & 0xFF
|
|
1468
|
+
]);
|
|
1469
|
+
this._sock.send([
|
|
1470
|
+
(pass.length >> 24) & 0xFF,
|
|
1471
|
+
(pass.length >> 16) & 0xFF,
|
|
1472
|
+
(pass.length >> 8) & 0xFF,
|
|
1473
|
+
pass.length & 0xFF
|
|
1474
|
+
]);
|
|
1458
1475
|
this._sock.sendString(user);
|
|
1459
1476
|
this._sock.sendString(pass);
|
|
1460
1477
|
|
|
@@ -1757,6 +1774,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
1757
1774
|
if (this._fbDepth == 24) {
|
|
1758
1775
|
encs.push(encodings.encodingTight);
|
|
1759
1776
|
encs.push(encodings.encodingTightPNG);
|
|
1777
|
+
encs.push(encodings.encodingZRLE);
|
|
1760
1778
|
encs.push(encodings.encodingHextile);
|
|
1761
1779
|
encs.push(encodings.encodingRRE);
|
|
1762
1780
|
}
|
|
@@ -2183,15 +2201,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
2183
2201
|
return this._handleCursor();
|
|
2184
2202
|
|
|
2185
2203
|
case encodings.pseudoEncodingQEMUExtendedKeyEvent:
|
|
2186
|
-
|
|
2187
|
-
try {
|
|
2188
|
-
const keyboardEvent = document.createEvent("keyboardEvent");
|
|
2189
|
-
if (keyboardEvent.code !== undefined) {
|
|
2190
|
-
this._qemuExtKeyEventSupported = true;
|
|
2191
|
-
}
|
|
2192
|
-
} catch (err) {
|
|
2193
|
-
// Do nothing
|
|
2194
|
-
}
|
|
2204
|
+
this._qemuExtKeyEventSupported = true;
|
|
2195
2205
|
return true;
|
|
2196
2206
|
|
|
2197
2207
|
case encodings.pseudoEncodingDesktopName:
|
|
@@ -2882,9 +2892,9 @@ RFB.messages = {
|
|
|
2882
2892
|
buff[offset + 12] = 0; // blue-max
|
|
2883
2893
|
buff[offset + 13] = (1 << bits) - 1; // blue-max
|
|
2884
2894
|
|
|
2885
|
-
buff[offset + 14] = bits *
|
|
2895
|
+
buff[offset + 14] = bits * 0; // red-shift
|
|
2886
2896
|
buff[offset + 15] = bits * 1; // green-shift
|
|
2887
|
-
buff[offset + 16] = bits *
|
|
2897
|
+
buff[offset + 16] = bits * 2; // blue-shift
|
|
2888
2898
|
|
|
2889
2899
|
buff[offset + 17] = 0; // padding
|
|
2890
2900
|
buff[offset + 18] = 0; // padding
|
package/core/util/browser.js
CHANGED
|
@@ -45,15 +45,6 @@ try {
|
|
|
45
45
|
|
|
46
46
|
export const supportsCursorURIs = _supportsCursorURIs;
|
|
47
47
|
|
|
48
|
-
let _supportsImageMetadata = false;
|
|
49
|
-
try {
|
|
50
|
-
new ImageData(new Uint8ClampedArray(4), 1, 1);
|
|
51
|
-
_supportsImageMetadata = true;
|
|
52
|
-
} catch (ex) {
|
|
53
|
-
// ignore failure
|
|
54
|
-
}
|
|
55
|
-
export const supportsImageMetadata = _supportsImageMetadata;
|
|
56
|
-
|
|
57
48
|
let _hasScrollbarGutter = true;
|
|
58
49
|
try {
|
|
59
50
|
// Create invisible container
|
|
@@ -106,14 +97,6 @@ export function isSafari() {
|
|
|
106
97
|
navigator.userAgent.indexOf('Chrome') === -1);
|
|
107
98
|
}
|
|
108
99
|
|
|
109
|
-
export function isIE() {
|
|
110
|
-
return navigator && !!(/trident/i).exec(navigator.userAgent);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function isEdge() {
|
|
114
|
-
return navigator && !!(/edge/i).exec(navigator.userAgent);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
100
|
export function isFirefox() {
|
|
118
101
|
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
|
119
102
|
}
|
package/core/util/cursor.js
CHANGED
|
@@ -43,9 +43,6 @@ export default class Cursor {
|
|
|
43
43
|
if (useFallback) {
|
|
44
44
|
document.body.appendChild(this._canvas);
|
|
45
45
|
|
|
46
|
-
// FIXME: These don't fire properly except for mouse
|
|
47
|
-
/// movement in IE. We want to also capture element
|
|
48
|
-
// movement, size changes, visibility, etc.
|
|
49
46
|
const options = { capture: true, passive: true };
|
|
50
47
|
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
|
51
48
|
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
|
@@ -90,14 +87,7 @@ export default class Cursor {
|
|
|
90
87
|
this._canvas.width = w;
|
|
91
88
|
this._canvas.height = h;
|
|
92
89
|
|
|
93
|
-
let img;
|
|
94
|
-
try {
|
|
95
|
-
// IE doesn't support this
|
|
96
|
-
img = new ImageData(new Uint8ClampedArray(rgba), w, h);
|
|
97
|
-
} catch (ex) {
|
|
98
|
-
img = ctx.createImageData(w, h);
|
|
99
|
-
img.data.set(new Uint8ClampedArray(rgba));
|
|
100
|
-
}
|
|
90
|
+
let img = new ImageData(new Uint8ClampedArray(rgba), w, h);
|
|
101
91
|
ctx.clearRect(0, 0, w, h);
|
|
102
92
|
ctx.putImageData(img, 0, 0);
|
|
103
93
|
|
package/core/util/events.js
CHANGED
|
@@ -65,10 +65,6 @@ export function setCapture(target) {
|
|
|
65
65
|
|
|
66
66
|
target.setCapture();
|
|
67
67
|
document.captureElement = target;
|
|
68
|
-
|
|
69
|
-
// IE releases capture on 'click' events which might not trigger
|
|
70
|
-
target.addEventListener('mouseup', releaseCapture);
|
|
71
|
-
|
|
72
68
|
} else {
|
|
73
69
|
// Release any existing capture in case this method is
|
|
74
70
|
// called multiple times without coordination
|
package/core/websock.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Websock: high-performance
|
|
2
|
+
* Websock: high-performance buffering wrapper
|
|
3
3
|
* Copyright (C) 2019 The noVNC Authors
|
|
4
4
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
|
5
5
|
*
|
|
6
|
-
* Websock is similar to the standard WebSocket
|
|
7
|
-
* buffer handling.
|
|
6
|
+
* Websock is similar to the standard WebSocket / RTCDataChannel object
|
|
7
|
+
* but with extra buffer handling.
|
|
8
8
|
*
|
|
9
9
|
* Websock has built-in receive queue buffering; the message event
|
|
10
10
|
* does not contain actual data but is simply a notification that
|
|
@@ -17,14 +17,39 @@ import * as Log from './util/logging.js';
|
|
|
17
17
|
// this has performance issues in some versions Chromium, and
|
|
18
18
|
// doesn't gain a tremendous amount of performance increase in Firefox
|
|
19
19
|
// at the moment. It may be valuable to turn it on in the future.
|
|
20
|
-
// Also copyWithin() for TypedArrays is not supported in IE 11 or
|
|
21
|
-
// Safari 13 (at the moment we want to support Safari 11).
|
|
22
|
-
const ENABLE_COPYWITHIN = false;
|
|
23
20
|
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
|
24
21
|
|
|
22
|
+
// Constants pulled from RTCDataChannelState enum
|
|
23
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/readyState#RTCDataChannelState_enum
|
|
24
|
+
const DataChannel = {
|
|
25
|
+
CONNECTING: "connecting",
|
|
26
|
+
OPEN: "open",
|
|
27
|
+
CLOSING: "closing",
|
|
28
|
+
CLOSED: "closed"
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ReadyStates = {
|
|
32
|
+
CONNECTING: [WebSocket.CONNECTING, DataChannel.CONNECTING],
|
|
33
|
+
OPEN: [WebSocket.OPEN, DataChannel.OPEN],
|
|
34
|
+
CLOSING: [WebSocket.CLOSING, DataChannel.CLOSING],
|
|
35
|
+
CLOSED: [WebSocket.CLOSED, DataChannel.CLOSED],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Properties a raw channel must have, WebSocket and RTCDataChannel are two examples
|
|
39
|
+
const rawChannelProps = [
|
|
40
|
+
"send",
|
|
41
|
+
"close",
|
|
42
|
+
"binaryType",
|
|
43
|
+
"onerror",
|
|
44
|
+
"onmessage",
|
|
45
|
+
"onopen",
|
|
46
|
+
"protocol",
|
|
47
|
+
"readyState",
|
|
48
|
+
];
|
|
49
|
+
|
|
25
50
|
export default class Websock {
|
|
26
51
|
constructor() {
|
|
27
|
-
this._websocket = null; // WebSocket object
|
|
52
|
+
this._websocket = null; // WebSocket or RTCDataChannel object
|
|
28
53
|
|
|
29
54
|
this._rQi = 0; // Receive queue index
|
|
30
55
|
this._rQlen = 0; // Next write position in the receive queue
|
|
@@ -46,6 +71,29 @@ export default class Websock {
|
|
|
46
71
|
}
|
|
47
72
|
|
|
48
73
|
// Getters and Setters
|
|
74
|
+
|
|
75
|
+
get readyState() {
|
|
76
|
+
let subState;
|
|
77
|
+
|
|
78
|
+
if (this._websocket === null) {
|
|
79
|
+
return "unused";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
subState = this._websocket.readyState;
|
|
83
|
+
|
|
84
|
+
if (ReadyStates.CONNECTING.includes(subState)) {
|
|
85
|
+
return "connecting";
|
|
86
|
+
} else if (ReadyStates.OPEN.includes(subState)) {
|
|
87
|
+
return "open";
|
|
88
|
+
} else if (ReadyStates.CLOSING.includes(subState)) {
|
|
89
|
+
return "closing";
|
|
90
|
+
} else if (ReadyStates.CLOSED.includes(subState)) {
|
|
91
|
+
return "closed";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return "unknown";
|
|
95
|
+
}
|
|
96
|
+
|
|
49
97
|
get sQ() {
|
|
50
98
|
return this._sQ;
|
|
51
99
|
}
|
|
@@ -143,7 +191,7 @@ export default class Websock {
|
|
|
143
191
|
// Send Queue
|
|
144
192
|
|
|
145
193
|
flush() {
|
|
146
|
-
if (this._sQlen > 0 && this.
|
|
194
|
+
if (this._sQlen > 0 && this.readyState === 'open') {
|
|
147
195
|
this._websocket.send(this._encodeMessage());
|
|
148
196
|
this._sQlen = 0;
|
|
149
197
|
}
|
|
@@ -180,12 +228,25 @@ export default class Websock {
|
|
|
180
228
|
}
|
|
181
229
|
|
|
182
230
|
open(uri, protocols) {
|
|
231
|
+
this.attach(new WebSocket(uri, protocols));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
attach(rawChannel) {
|
|
183
235
|
this.init();
|
|
184
236
|
|
|
185
|
-
|
|
186
|
-
|
|
237
|
+
// Must get object and class methods to be compatible with the tests.
|
|
238
|
+
const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];
|
|
239
|
+
for (let i = 0; i < rawChannelProps.length; i++) {
|
|
240
|
+
const prop = rawChannelProps[i];
|
|
241
|
+
if (channelProps.indexOf(prop) < 0) {
|
|
242
|
+
throw new Error('Raw channel missing property: ' + prop);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
187
245
|
|
|
246
|
+
this._websocket = rawChannel;
|
|
247
|
+
this._websocket.binaryType = "arraybuffer";
|
|
188
248
|
this._websocket.onmessage = this._recvMessage.bind(this);
|
|
249
|
+
|
|
189
250
|
this._websocket.onopen = () => {
|
|
190
251
|
Log.Debug('>> WebSock.onopen');
|
|
191
252
|
if (this._websocket.protocol) {
|
|
@@ -195,11 +256,13 @@ export default class Websock {
|
|
|
195
256
|
this._eventHandlers.open();
|
|
196
257
|
Log.Debug("<< WebSock.onopen");
|
|
197
258
|
};
|
|
259
|
+
|
|
198
260
|
this._websocket.onclose = (e) => {
|
|
199
261
|
Log.Debug(">> WebSock.onclose");
|
|
200
262
|
this._eventHandlers.close(e);
|
|
201
263
|
Log.Debug("<< WebSock.onclose");
|
|
202
264
|
};
|
|
265
|
+
|
|
203
266
|
this._websocket.onerror = (e) => {
|
|
204
267
|
Log.Debug(">> WebSock.onerror: " + e);
|
|
205
268
|
this._eventHandlers.error(e);
|
|
@@ -209,8 +272,8 @@ export default class Websock {
|
|
|
209
272
|
|
|
210
273
|
close() {
|
|
211
274
|
if (this._websocket) {
|
|
212
|
-
if (
|
|
213
|
-
|
|
275
|
+
if (this.readyState === 'connecting' ||
|
|
276
|
+
this.readyState === 'open') {
|
|
214
277
|
Log.Info("Closing WebSocket connection");
|
|
215
278
|
this._websocket.close();
|
|
216
279
|
}
|
|
@@ -256,11 +319,7 @@ export default class Websock {
|
|
|
256
319
|
this._rQ = new Uint8Array(this._rQbufferSize);
|
|
257
320
|
this._rQ.set(new Uint8Array(oldRQbuffer, this._rQi, this._rQlen - this._rQi));
|
|
258
321
|
} else {
|
|
259
|
-
|
|
260
|
-
this._rQ.copyWithin(0, this._rQi, this._rQlen);
|
|
261
|
-
} else {
|
|
262
|
-
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi, this._rQlen - this._rQi));
|
|
263
|
-
}
|
|
322
|
+
this._rQ.copyWithin(0, this._rQi, this._rQlen);
|
|
264
323
|
}
|
|
265
324
|
|
|
266
325
|
this._rQlen = this._rQlen - this._rQi;
|
package/docs/API.md
CHANGED
|
@@ -165,9 +165,9 @@ connection to a specified VNC server.
|
|
|
165
165
|
existing contents of the `HTMLElement` will be untouched, but new
|
|
166
166
|
elements will be added during the lifetime of the `RFB` object.
|
|
167
167
|
|
|
168
|
-
**`
|
|
168
|
+
**`urlOrDataChannel`**
|
|
169
169
|
- A `DOMString` specifying the VNC server to connect to. This must be
|
|
170
|
-
a valid WebSocket URL.
|
|
170
|
+
a valid WebSocket URL. This can also be a `WebSocket` or `RTCDataChannel`.
|
|
171
171
|
|
|
172
172
|
**`options`** *Optional*
|
|
173
173
|
- An `Object` specifying extra details about how the connection
|
|
@@ -328,7 +328,14 @@ Keyboard events will be sent to the remote server after this point.
|
|
|
328
328
|
|
|
329
329
|
##### Syntax
|
|
330
330
|
|
|
331
|
-
RFB.focus( );
|
|
331
|
+
RFB.focus( [options] );
|
|
332
|
+
|
|
333
|
+
###### Parameters
|
|
334
|
+
|
|
335
|
+
**`options`** *Optional*
|
|
336
|
+
- A `object` providing options to control how the focus will be
|
|
337
|
+
performed. Please see [`HTMLElement.focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus)
|
|
338
|
+
for available options.
|
|
332
339
|
|
|
333
340
|
#### RFB.blur()
|
|
334
341
|
|
package/docs/LIBRARY.md
CHANGED
|
@@ -18,18 +18,14 @@ do things.
|
|
|
18
18
|
|
|
19
19
|
## Conversion of Modules
|
|
20
20
|
|
|
21
|
-
noVNC is written using ECMAScript 6 modules.
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
noVNC is written using ECMAScript 6 modules. This is not supported by older
|
|
22
|
+
versions of Node.js. To use noVNC with those older versions of Node.js the
|
|
23
|
+
library must first be converted.
|
|
24
24
|
|
|
25
25
|
Fortunately noVNC includes a script to handle this conversion. Please follow
|
|
26
26
|
the following steps:
|
|
27
27
|
|
|
28
28
|
1. Install Node.js
|
|
29
29
|
2. Run `npm install` in the noVNC directory
|
|
30
|
-
3. Run `./utils/use_require.js --as <module format>`
|
|
31
|
-
|
|
32
|
-
Several module formats are available. Please run
|
|
33
|
-
`./utils/use_require.js --help` to see them all.
|
|
34
30
|
|
|
35
31
|
The result of the conversion is available in the `lib/` directory.
|