@novnc/novnc 1.2.0-test → 1.3.0-g7ad4e60
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 +4 -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/display.js +9 -151
- 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 +116 -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/deflator.js +4 -4
- package/lib/des.js +2 -2
- package/lib/display.js +45 -212
- 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 +292 -286
- 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/input/keyboard.js
CHANGED
|
@@ -20,16 +20,13 @@ export default class Keyboard {
|
|
|
20
20
|
|
|
21
21
|
this._keyDownList = {}; // List of depressed keys
|
|
22
22
|
// (even if they are happy)
|
|
23
|
-
this._pendingKey = null; // Key waiting for keypress
|
|
24
23
|
this._altGrArmed = false; // Windows AltGr detection
|
|
25
24
|
|
|
26
25
|
// keep these here so we can refer to them later
|
|
27
26
|
this._eventHandlers = {
|
|
28
27
|
'keyup': this._handleKeyUp.bind(this),
|
|
29
28
|
'keydown': this._handleKeyDown.bind(this),
|
|
30
|
-
'keypress': this._handleKeyPress.bind(this),
|
|
31
29
|
'blur': this._allKeysUp.bind(this),
|
|
32
|
-
'checkalt': this._checkAlt.bind(this),
|
|
33
30
|
};
|
|
34
31
|
|
|
35
32
|
// ===== EVENT HANDLERS =====
|
|
@@ -62,9 +59,7 @@ export default class Keyboard {
|
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
// Unstable, but we don't have anything else to go on
|
|
65
|
-
|
|
66
|
-
// WebKit sets it to the same as charCode)
|
|
67
|
-
if (e.keyCode && (e.type !== 'keypress')) {
|
|
62
|
+
if (e.keyCode) {
|
|
68
63
|
// 229 is used for composition events
|
|
69
64
|
if (e.keyCode !== 229) {
|
|
70
65
|
return 'Platform' + e.keyCode;
|
|
@@ -169,20 +164,20 @@ export default class Keyboard {
|
|
|
169
164
|
return;
|
|
170
165
|
}
|
|
171
166
|
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
167
|
+
// Windows doesn't send proper key releases for a bunch of
|
|
168
|
+
// Japanese IM keys so we have to fake the release right away
|
|
169
|
+
const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,
|
|
170
|
+
KeyTable.XK_Eisu_toggle,
|
|
171
|
+
KeyTable.XK_Katakana,
|
|
172
|
+
KeyTable.XK_Hiragana,
|
|
173
|
+
KeyTable.XK_Romaji ];
|
|
174
|
+
if (browser.isWindows() && jpBadKeys.includes(keysym)) {
|
|
175
|
+
this._sendKeyEvent(keysym, code, true);
|
|
176
|
+
this._sendKeyEvent(keysym, code, false);
|
|
177
|
+
stopEvent(e);
|
|
182
178
|
return;
|
|
183
179
|
}
|
|
184
180
|
|
|
185
|
-
this._pendingKey = null;
|
|
186
181
|
stopEvent(e);
|
|
187
182
|
|
|
188
183
|
// Possible start of AltGr sequence? (see above)
|
|
@@ -197,69 +192,6 @@ export default class Keyboard {
|
|
|
197
192
|
this._sendKeyEvent(keysym, code, true);
|
|
198
193
|
}
|
|
199
194
|
|
|
200
|
-
// Legacy event for browsers without code/key
|
|
201
|
-
_handleKeyPress(e) {
|
|
202
|
-
stopEvent(e);
|
|
203
|
-
|
|
204
|
-
// Are we expecting a keypress?
|
|
205
|
-
if (this._pendingKey === null) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let code = this._getKeyCode(e);
|
|
210
|
-
const keysym = KeyboardUtil.getKeysym(e);
|
|
211
|
-
|
|
212
|
-
// The key we were waiting for?
|
|
213
|
-
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
code = this._pendingKey;
|
|
218
|
-
this._pendingKey = null;
|
|
219
|
-
|
|
220
|
-
if (!keysym) {
|
|
221
|
-
Log.Info('keypress with no keysym:', e);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
this._sendKeyEvent(keysym, code, true);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
_handleKeyPressTimeout(e) {
|
|
229
|
-
// Did someone manage to sort out the key already?
|
|
230
|
-
if (this._pendingKey === null) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
let keysym;
|
|
235
|
-
|
|
236
|
-
const code = this._pendingKey;
|
|
237
|
-
this._pendingKey = null;
|
|
238
|
-
|
|
239
|
-
// We have no way of knowing the proper keysym with the
|
|
240
|
-
// information given, but the following are true for most
|
|
241
|
-
// layouts
|
|
242
|
-
if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
|
|
243
|
-
// Digit
|
|
244
|
-
keysym = e.keyCode;
|
|
245
|
-
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
|
|
246
|
-
// Character (A-Z)
|
|
247
|
-
let char = String.fromCharCode(e.keyCode);
|
|
248
|
-
// A feeble attempt at the correct case
|
|
249
|
-
if (e.shiftKey) {
|
|
250
|
-
char = char.toUpperCase();
|
|
251
|
-
} else {
|
|
252
|
-
char = char.toLowerCase();
|
|
253
|
-
}
|
|
254
|
-
keysym = char.charCodeAt();
|
|
255
|
-
} else {
|
|
256
|
-
// Unknown, give up
|
|
257
|
-
keysym = 0;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
this._sendKeyEvent(keysym, code, true);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
195
|
_handleKeyUp(e) {
|
|
264
196
|
stopEvent(e);
|
|
265
197
|
|
|
@@ -312,30 +244,6 @@ export default class Keyboard {
|
|
|
312
244
|
Log.Debug("<< Keyboard.allKeysUp");
|
|
313
245
|
}
|
|
314
246
|
|
|
315
|
-
// Alt workaround for Firefox on Windows, see below
|
|
316
|
-
_checkAlt(e) {
|
|
317
|
-
if (e.skipCheckAlt) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (e.altKey) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const target = this._target;
|
|
325
|
-
const downList = this._keyDownList;
|
|
326
|
-
['AltLeft', 'AltRight'].forEach((code) => {
|
|
327
|
-
if (!(code in downList)) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const event = new KeyboardEvent('keyup',
|
|
332
|
-
{ key: downList[code],
|
|
333
|
-
code: code });
|
|
334
|
-
event.skipCheckAlt = true;
|
|
335
|
-
target.dispatchEvent(event);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
247
|
// ===== PUBLIC METHODS =====
|
|
340
248
|
|
|
341
249
|
grab() {
|
|
@@ -343,41 +251,18 @@ export default class Keyboard {
|
|
|
343
251
|
|
|
344
252
|
this._target.addEventListener('keydown', this._eventHandlers.keydown);
|
|
345
253
|
this._target.addEventListener('keyup', this._eventHandlers.keyup);
|
|
346
|
-
this._target.addEventListener('keypress', this._eventHandlers.keypress);
|
|
347
254
|
|
|
348
255
|
// Release (key up) if window loses focus
|
|
349
256
|
window.addEventListener('blur', this._eventHandlers.blur);
|
|
350
257
|
|
|
351
|
-
// Firefox on Windows has broken handling of Alt, so we need to
|
|
352
|
-
// poll as best we can for releases (still doesn't prevent the
|
|
353
|
-
// menu from popping up though as we can't call
|
|
354
|
-
// preventDefault())
|
|
355
|
-
if (browser.isWindows() && browser.isFirefox()) {
|
|
356
|
-
const handler = this._eventHandlers.checkalt;
|
|
357
|
-
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
|
358
|
-
'touchstart', 'touchend', 'touchmove',
|
|
359
|
-
'keydown', 'keyup'].forEach(type =>
|
|
360
|
-
document.addEventListener(type, handler,
|
|
361
|
-
{ capture: true,
|
|
362
|
-
passive: true }));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
258
|
//Log.Debug("<< Keyboard.grab");
|
|
366
259
|
}
|
|
367
260
|
|
|
368
261
|
ungrab() {
|
|
369
262
|
//Log.Debug(">> Keyboard.ungrab");
|
|
370
263
|
|
|
371
|
-
if (browser.isWindows() && browser.isFirefox()) {
|
|
372
|
-
const handler = this._eventHandlers.checkalt;
|
|
373
|
-
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
|
374
|
-
'touchstart', 'touchend', 'touchmove',
|
|
375
|
-
'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
264
|
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
|
|
379
265
|
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
|
|
380
|
-
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
|
|
381
266
|
window.removeEventListener('blur', this._eventHandlers.blur);
|
|
382
267
|
|
|
383
268
|
// Release (key up) all keys that are in a down state
|
package/core/input/util.js
CHANGED
|
@@ -22,9 +22,8 @@ export function getKeycode(evt) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// The de-facto standard is to use Windows Virtual-Key codes
|
|
25
|
-
// in the 'keyCode' field for non-printable characters
|
|
26
|
-
|
|
27
|
-
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
|
|
25
|
+
// in the 'keyCode' field for non-printable characters
|
|
26
|
+
if (evt.keyCode in vkeys) {
|
|
28
27
|
let code = vkeys[evt.keyCode];
|
|
29
28
|
|
|
30
29
|
// macOS has messed up this code for some reason
|
|
@@ -69,26 +68,6 @@ export function getKeycode(evt) {
|
|
|
69
68
|
export function getKey(evt) {
|
|
70
69
|
// Are we getting a proper key value?
|
|
71
70
|
if (evt.key !== undefined) {
|
|
72
|
-
// IE and Edge use some ancient version of the spec
|
|
73
|
-
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
|
|
74
|
-
switch (evt.key) {
|
|
75
|
-
case 'Spacebar': return ' ';
|
|
76
|
-
case 'Esc': return 'Escape';
|
|
77
|
-
case 'Scroll': return 'ScrollLock';
|
|
78
|
-
case 'Win': return 'Meta';
|
|
79
|
-
case 'Apps': return 'ContextMenu';
|
|
80
|
-
case 'Up': return 'ArrowUp';
|
|
81
|
-
case 'Left': return 'ArrowLeft';
|
|
82
|
-
case 'Right': return 'ArrowRight';
|
|
83
|
-
case 'Down': return 'ArrowDown';
|
|
84
|
-
case 'Del': return 'Delete';
|
|
85
|
-
case 'Divide': return '/';
|
|
86
|
-
case 'Multiply': return '*';
|
|
87
|
-
case 'Subtract': return '-';
|
|
88
|
-
case 'Add': return '+';
|
|
89
|
-
case 'Decimal': return evt.char;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
71
|
// Mozilla isn't fully in sync with the spec yet
|
|
93
72
|
switch (evt.key) {
|
|
94
73
|
case 'OS': return 'Meta';
|
|
@@ -110,18 +89,7 @@ export function getKey(evt) {
|
|
|
110
89
|
return 'Delete';
|
|
111
90
|
}
|
|
112
91
|
|
|
113
|
-
|
|
114
|
-
// can trust the value provided
|
|
115
|
-
if (!browser.isIE() && !browser.isEdge()) {
|
|
116
|
-
return evt.key;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// IE and Edge have broken handling of AltGraph so we can only
|
|
120
|
-
// trust them for non-printable characters (and unfortunately
|
|
121
|
-
// they also specify 'Unidentified' for some problem keys)
|
|
122
|
-
if ((evt.key.length !== 1) && (evt.key !== 'Unidentified')) {
|
|
123
|
-
return evt.key;
|
|
124
|
-
}
|
|
92
|
+
return evt.key;
|
|
125
93
|
}
|
|
126
94
|
|
|
127
95
|
// Try to deduce it based on the physical key
|
|
@@ -189,6 +157,21 @@ export function getKeysym(evt) {
|
|
|
189
157
|
}
|
|
190
158
|
}
|
|
191
159
|
|
|
160
|
+
// Windows sends alternating symbols for some keys when using a
|
|
161
|
+
// Japanese layout. We have no way of synchronising with the IM
|
|
162
|
+
// running on the remote system, so we send some combined keysym
|
|
163
|
+
// instead and hope for the best.
|
|
164
|
+
if (browser.isWindows()) {
|
|
165
|
+
switch (key) {
|
|
166
|
+
case 'Zenkaku':
|
|
167
|
+
case 'Hankaku':
|
|
168
|
+
return KeyTable.XK_Zenkaku_Hankaku;
|
|
169
|
+
case 'Romaji':
|
|
170
|
+
case 'KanaMode':
|
|
171
|
+
return KeyTable.XK_Romaji;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
192
175
|
return DOMKeyTable[key][location];
|
|
193
176
|
}
|
|
194
177
|
|
package/core/input/vkeys.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* This file is auto-generated from keymaps.csv
|
|
3
|
-
* Database checksum sha256(
|
|
2
|
+
* This file is auto-generated from keymaps.csv
|
|
3
|
+
* Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb217e900bf2983ade13a7920)
|
|
4
4
|
* To re-generate, run:
|
|
5
|
-
* keymap-gen --lang=js
|
|
5
|
+
* keymap-gen code-map --lang=js keymaps.csv html atset1
|
|
6
6
|
*/
|
|
7
7
|
export default {
|
|
8
8
|
"Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
|
|
@@ -111,6 +111,8 @@ export default {
|
|
|
111
111
|
"KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
|
|
112
112
|
"KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
|
|
113
113
|
"KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
|
|
114
|
+
"Lang1": 0x72, /* html:Lang1 (Lang1) -> linux:122 (KEY_HANGEUL) -> atset1:114 */
|
|
115
|
+
"Lang2": 0x71, /* html:Lang2 (Lang2) -> linux:123 (KEY_HANJA) -> atset1:113 */
|
|
114
116
|
"Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
|
|
115
117
|
"Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
|
|
116
118
|
"Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
|
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";
|
|
@@ -67,20 +66,25 @@ const extendedClipboardActionPeek = 1 << 26;
|
|
|
67
66
|
const extendedClipboardActionNotify = 1 << 27;
|
|
68
67
|
const extendedClipboardActionProvide = 1 << 28;
|
|
69
68
|
|
|
70
|
-
|
|
71
69
|
export default class RFB extends EventTargetMixin {
|
|
72
|
-
constructor(target,
|
|
70
|
+
constructor(target, urlOrChannel, options) {
|
|
73
71
|
if (!target) {
|
|
74
72
|
throw new Error("Must specify target");
|
|
75
73
|
}
|
|
76
|
-
if (!
|
|
77
|
-
throw new Error("Must specify URL");
|
|
74
|
+
if (!urlOrChannel) {
|
|
75
|
+
throw new Error("Must specify URL, WebSocket or RTCDataChannel");
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
super();
|
|
81
79
|
|
|
82
80
|
this._target = target;
|
|
83
|
-
|
|
81
|
+
|
|
82
|
+
if (typeof urlOrChannel === "string") {
|
|
83
|
+
this._url = urlOrChannel;
|
|
84
|
+
} else {
|
|
85
|
+
this._url = null;
|
|
86
|
+
this._rawChannel = urlOrChannel;
|
|
87
|
+
}
|
|
84
88
|
|
|
85
89
|
// Connection details
|
|
86
90
|
options = options || {};
|
|
@@ -130,6 +134,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
130
134
|
this._flushing = false; // Display flushing state
|
|
131
135
|
this._keyboard = null; // Keyboard input handler object
|
|
132
136
|
this._gestures = null; // Gesture input handler object
|
|
137
|
+
this._resizeObserver = null; // Resize observer object
|
|
133
138
|
|
|
134
139
|
// Timers
|
|
135
140
|
this._disconnTimer = null; // disconnection timer
|
|
@@ -167,7 +172,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
167
172
|
// Bound event handlers
|
|
168
173
|
this._eventHandlers = {
|
|
169
174
|
focusCanvas: this._focusCanvas.bind(this),
|
|
170
|
-
|
|
175
|
+
handleResize: this._handleResize.bind(this),
|
|
171
176
|
handleMouse: this._handleMouse.bind(this),
|
|
172
177
|
handleWheel: this._handleWheel.bind(this),
|
|
173
178
|
handleGesture: this._handleGesture.bind(this),
|
|
@@ -187,8 +192,6 @@ export default class RFB extends EventTargetMixin {
|
|
|
187
192
|
this._canvas.style.margin = 'auto';
|
|
188
193
|
// Some browsers add an outline on focus
|
|
189
194
|
this._canvas.style.outline = 'none';
|
|
190
|
-
// IE miscalculates width without this :(
|
|
191
|
-
this._canvas.style.flexShrink = '0';
|
|
192
195
|
this._canvas.width = 0;
|
|
193
196
|
this._canvas.height = 0;
|
|
194
197
|
this._canvas.tabIndex = -1;
|
|
@@ -232,58 +235,15 @@ export default class RFB extends EventTargetMixin {
|
|
|
232
235
|
this._gestures = new GestureHandler();
|
|
233
236
|
|
|
234
237
|
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"));
|
|
238
|
+
this._sock.on('open', this._socketOpen.bind(this));
|
|
239
|
+
this._sock.on('close', this._socketClose.bind(this));
|
|
240
|
+
this._sock.on('message', this._handleMessage.bind(this));
|
|
241
|
+
this._sock.on('error', this._socketError.bind(this));
|
|
242
|
+
|
|
243
|
+
this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
|
|
283
244
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
setTimeout(this._updateConnectionState.bind(this, 'connecting'));
|
|
245
|
+
// All prepared, kick off the connection
|
|
246
|
+
this._updateConnectionState('connecting');
|
|
287
247
|
|
|
288
248
|
Log.Debug("<< RFB.constructor");
|
|
289
249
|
|
|
@@ -472,8 +432,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
472
432
|
}
|
|
473
433
|
}
|
|
474
434
|
|
|
475
|
-
focus() {
|
|
476
|
-
this._canvas.focus();
|
|
435
|
+
focus(options) {
|
|
436
|
+
this._canvas.focus(options);
|
|
477
437
|
}
|
|
478
438
|
|
|
479
439
|
blur() {
|
|
@@ -504,16 +464,22 @@ export default class RFB extends EventTargetMixin {
|
|
|
504
464
|
_connect() {
|
|
505
465
|
Log.Debug(">> RFB.connect");
|
|
506
466
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
// WebSocket.onopen transitions to the RFB init states
|
|
467
|
+
if (this._url) {
|
|
468
|
+
Log.Info(`connecting to ${this._url}`);
|
|
511
469
|
this._sock.open(this._url, this._wsProtocols);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
470
|
+
} else {
|
|
471
|
+
Log.Info(`attaching ${this._rawChannel} to Websock`);
|
|
472
|
+
this._sock.attach(this._rawChannel);
|
|
473
|
+
|
|
474
|
+
if (this._sock.readyState === 'closed') {
|
|
475
|
+
throw Error("Cannot use already closed WebSocket/RTCDataChannel");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (this._sock.readyState === 'open') {
|
|
479
|
+
// FIXME: _socketOpen() can in theory call _fail(), which
|
|
480
|
+
// isn't allowed this early, but I'm not sure that can
|
|
481
|
+
// happen without a bug messing up our state variables
|
|
482
|
+
this._socketOpen();
|
|
517
483
|
}
|
|
518
484
|
}
|
|
519
485
|
|
|
@@ -525,9 +491,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
525
491
|
this._cursor.attach(this._canvas);
|
|
526
492
|
this._refreshCursor();
|
|
527
493
|
|
|
528
|
-
// Monitor size changes of the screen
|
|
529
|
-
|
|
530
|
-
window.addEventListener('resize', this._eventHandlers.windowResize);
|
|
494
|
+
// Monitor size changes of the screen element
|
|
495
|
+
this._resizeObserver.observe(this._screen);
|
|
531
496
|
|
|
532
497
|
// Always grab focus on some kind of click event
|
|
533
498
|
this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
|
|
@@ -568,7 +533,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
568
533
|
this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
|
|
569
534
|
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
|
|
570
535
|
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
|
|
571
|
-
|
|
536
|
+
this._resizeObserver.disconnect();
|
|
572
537
|
this._keyboard.ungrab();
|
|
573
538
|
this._gestures.detach();
|
|
574
539
|
this._sock.close();
|
|
@@ -587,12 +552,64 @@ export default class RFB extends EventTargetMixin {
|
|
|
587
552
|
Log.Debug("<< RFB.disconnect");
|
|
588
553
|
}
|
|
589
554
|
|
|
555
|
+
_socketOpen() {
|
|
556
|
+
if ((this._rfbConnectionState === 'connecting') &&
|
|
557
|
+
(this._rfbInitState === '')) {
|
|
558
|
+
this._rfbInitState = 'ProtocolVersion';
|
|
559
|
+
Log.Debug("Starting VNC handshake");
|
|
560
|
+
} else {
|
|
561
|
+
this._fail("Unexpected server connection while " +
|
|
562
|
+
this._rfbConnectionState);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
_socketClose(e) {
|
|
567
|
+
Log.Debug("WebSocket on-close event");
|
|
568
|
+
let msg = "";
|
|
569
|
+
if (e.code) {
|
|
570
|
+
msg = "(code: " + e.code;
|
|
571
|
+
if (e.reason) {
|
|
572
|
+
msg += ", reason: " + e.reason;
|
|
573
|
+
}
|
|
574
|
+
msg += ")";
|
|
575
|
+
}
|
|
576
|
+
switch (this._rfbConnectionState) {
|
|
577
|
+
case 'connecting':
|
|
578
|
+
this._fail("Connection closed " + msg);
|
|
579
|
+
break;
|
|
580
|
+
case 'connected':
|
|
581
|
+
// Handle disconnects that were initiated server-side
|
|
582
|
+
this._updateConnectionState('disconnecting');
|
|
583
|
+
this._updateConnectionState('disconnected');
|
|
584
|
+
break;
|
|
585
|
+
case 'disconnecting':
|
|
586
|
+
// Normal disconnection path
|
|
587
|
+
this._updateConnectionState('disconnected');
|
|
588
|
+
break;
|
|
589
|
+
case 'disconnected':
|
|
590
|
+
this._fail("Unexpected server disconnect " +
|
|
591
|
+
"when already disconnected " + msg);
|
|
592
|
+
break;
|
|
593
|
+
default:
|
|
594
|
+
this._fail("Unexpected server disconnect before connecting " +
|
|
595
|
+
msg);
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
this._sock.off('close');
|
|
599
|
+
// Delete reference to raw channel to allow cleanup.
|
|
600
|
+
this._rawChannel = null;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
_socketError(e) {
|
|
604
|
+
Log.Warn("WebSocket on-error event");
|
|
605
|
+
}
|
|
606
|
+
|
|
590
607
|
_focusCanvas(event) {
|
|
591
608
|
if (!this.focusOnClick) {
|
|
592
609
|
return;
|
|
593
610
|
}
|
|
594
611
|
|
|
595
|
-
this.focus();
|
|
612
|
+
this.focus({ preventScroll: true });
|
|
596
613
|
}
|
|
597
614
|
|
|
598
615
|
_setDesktopName(name) {
|
|
@@ -602,7 +619,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
602
619
|
{ detail: { name: this._fbName } }));
|
|
603
620
|
}
|
|
604
621
|
|
|
605
|
-
|
|
622
|
+
_handleResize() {
|
|
606
623
|
// If the window resized then our screen element might have
|
|
607
624
|
// as well. Update the viewport dimensions.
|
|
608
625
|
window.requestAnimationFrame(() => {
|
|
@@ -1263,17 +1280,6 @@ export default class RFB extends EventTargetMixin {
|
|
|
1263
1280
|
}
|
|
1264
1281
|
|
|
1265
1282
|
_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
1283
|
if (this._rfbVersion >= 3.7) {
|
|
1278
1284
|
// Server sends supported list, client decides
|
|
1279
1285
|
const numTypes = this._sock.rQshift8();
|
|
@@ -1290,15 +1296,15 @@ export default class RFB extends EventTargetMixin {
|
|
|
1290
1296
|
Log.Debug("Server security types: " + types);
|
|
1291
1297
|
|
|
1292
1298
|
// Look for each auth in preferred order
|
|
1293
|
-
if (includes(1
|
|
1299
|
+
if (types.includes(1)) {
|
|
1294
1300
|
this._rfbAuthScheme = 1; // None
|
|
1295
|
-
} else if (includes(22
|
|
1301
|
+
} else if (types.includes(22)) {
|
|
1296
1302
|
this._rfbAuthScheme = 22; // XVP
|
|
1297
|
-
} else if (includes(16
|
|
1303
|
+
} else if (types.includes(16)) {
|
|
1298
1304
|
this._rfbAuthScheme = 16; // Tight
|
|
1299
|
-
} else if (includes(2
|
|
1305
|
+
} else if (types.includes(2)) {
|
|
1300
1306
|
this._rfbAuthScheme = 2; // VNC Auth
|
|
1301
|
-
} else if (includes(19
|
|
1307
|
+
} else if (types.includes(19)) {
|
|
1302
1308
|
this._rfbAuthScheme = 19; // VeNCrypt Auth
|
|
1303
1309
|
} else {
|
|
1304
1310
|
return this._fail("Unsupported security types (types: " + types + ")");
|
|
@@ -1441,8 +1447,8 @@ export default class RFB extends EventTargetMixin {
|
|
|
1441
1447
|
|
|
1442
1448
|
// negotiated Plain subtype, server waits for password
|
|
1443
1449
|
if (this._rfbVeNCryptState == 4) {
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
1450
|
+
if (this._rfbCredentials.username === undefined ||
|
|
1451
|
+
this._rfbCredentials.password === undefined) {
|
|
1446
1452
|
this.dispatchEvent(new CustomEvent(
|
|
1447
1453
|
"credentialsrequired",
|
|
1448
1454
|
{ detail: { types: ["username", "password"] } }));
|
|
@@ -1452,9 +1458,18 @@ export default class RFB extends EventTargetMixin {
|
|
|
1452
1458
|
const user = encodeUTF8(this._rfbCredentials.username);
|
|
1453
1459
|
const pass = encodeUTF8(this._rfbCredentials.password);
|
|
1454
1460
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1461
|
+
this._sock.send([
|
|
1462
|
+
(user.length >> 24) & 0xFF,
|
|
1463
|
+
(user.length >> 16) & 0xFF,
|
|
1464
|
+
(user.length >> 8) & 0xFF,
|
|
1465
|
+
user.length & 0xFF
|
|
1466
|
+
]);
|
|
1467
|
+
this._sock.send([
|
|
1468
|
+
(pass.length >> 24) & 0xFF,
|
|
1469
|
+
(pass.length >> 16) & 0xFF,
|
|
1470
|
+
(pass.length >> 8) & 0xFF,
|
|
1471
|
+
pass.length & 0xFF
|
|
1472
|
+
]);
|
|
1458
1473
|
this._sock.sendString(user);
|
|
1459
1474
|
this._sock.sendString(pass);
|
|
1460
1475
|
|
|
@@ -2183,15 +2198,7 @@ export default class RFB extends EventTargetMixin {
|
|
|
2183
2198
|
return this._handleCursor();
|
|
2184
2199
|
|
|
2185
2200
|
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
|
-
}
|
|
2201
|
+
this._qemuExtKeyEventSupported = true;
|
|
2195
2202
|
return true;
|
|
2196
2203
|
|
|
2197
2204
|
case encodings.pseudoEncodingDesktopName:
|
|
@@ -2882,9 +2889,9 @@ RFB.messages = {
|
|
|
2882
2889
|
buff[offset + 12] = 0; // blue-max
|
|
2883
2890
|
buff[offset + 13] = (1 << bits) - 1; // blue-max
|
|
2884
2891
|
|
|
2885
|
-
buff[offset + 14] = bits *
|
|
2892
|
+
buff[offset + 14] = bits * 0; // red-shift
|
|
2886
2893
|
buff[offset + 15] = bits * 1; // green-shift
|
|
2887
|
-
buff[offset + 16] = bits *
|
|
2894
|
+
buff[offset + 16] = bits * 2; // blue-shift
|
|
2888
2895
|
|
|
2889
2896
|
buff[offset + 17] = 0; // padding
|
|
2890
2897
|
buff[offset + 18] = 0; // padding
|