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