novnc-rails 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +0 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +0 -0
  5. data/lib/novnc-rails.rb +8 -0
  6. data/lib/novnc-rails/version.rb +5 -0
  7. data/vendor/assets/javascripts/noVNC/Orbitron700.ttf +0 -0
  8. data/vendor/assets/javascripts/noVNC/Orbitron700.woff +0 -0
  9. data/vendor/assets/javascripts/noVNC/base.css +512 -0
  10. data/vendor/assets/javascripts/noVNC/base64.js +113 -0
  11. data/vendor/assets/javascripts/noVNC/black.css +71 -0
  12. data/vendor/assets/javascripts/noVNC/blue.css +64 -0
  13. data/vendor/assets/javascripts/noVNC/des.js +276 -0
  14. data/vendor/assets/javascripts/noVNC/display.js +751 -0
  15. data/vendor/assets/javascripts/noVNC/input.js +388 -0
  16. data/vendor/assets/javascripts/noVNC/jsunzip.js +676 -0
  17. data/vendor/assets/javascripts/noVNC/keyboard.js +543 -0
  18. data/vendor/assets/javascripts/noVNC/keysym.js +378 -0
  19. data/vendor/assets/javascripts/noVNC/keysymdef.js +15 -0
  20. data/vendor/assets/javascripts/noVNC/logo.js +1 -0
  21. data/vendor/assets/javascripts/noVNC/playback.js +102 -0
  22. data/vendor/assets/javascripts/noVNC/rfb.js +1889 -0
  23. data/vendor/assets/javascripts/noVNC/ui.js +979 -0
  24. data/vendor/assets/javascripts/noVNC/util.js +656 -0
  25. data/vendor/assets/javascripts/noVNC/web-socket-js/README.txt +109 -0
  26. data/vendor/assets/javascripts/noVNC/web-socket-js/WebSocketMain.swf +0 -0
  27. data/vendor/assets/javascripts/noVNC/web-socket-js/swfobject.js +4 -0
  28. data/vendor/assets/javascripts/noVNC/web-socket-js/web_socket.js +391 -0
  29. data/vendor/assets/javascripts/noVNC/websock.js +388 -0
  30. data/vendor/assets/javascripts/noVNC/webutil.js +239 -0
  31. metadata +86 -0
@@ -0,0 +1,1889 @@
1
+ /*
2
+ * noVNC: HTML5 VNC client
3
+ * Copyright (C) 2012 Joel Martin
4
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
5
+ * Licensed under MPL 2.0 (see LICENSE.txt)
6
+ *
7
+ * See README.md for usage and integration instructions.
8
+ *
9
+ * TIGHT decoder portion:
10
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
11
+ */
12
+
13
+ /*jslint white: false, browser: true */
14
+ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
15
+
16
+ var RFB;
17
+
18
+ (function () {
19
+ "use strict";
20
+ RFB = function (defaults) {
21
+ if (!defaults) {
22
+ defaults = {};
23
+ }
24
+
25
+ this._rfb_host = '';
26
+ this._rfb_port = 5900;
27
+ this._rfb_password = '';
28
+ this._rfb_path = '';
29
+
30
+ this._rfb_state = 'disconnected';
31
+ this._rfb_version = 0;
32
+ this._rfb_max_version = 3.8;
33
+ this._rfb_auth_scheme = '';
34
+
35
+ this._rfb_tightvnc = false;
36
+ this._rfb_xvp_ver = 0;
37
+
38
+ // In preference order
39
+ this._encodings = [
40
+ ['COPYRECT', 0x01 ],
41
+ ['TIGHT', 0x07 ],
42
+ ['TIGHT_PNG', -260 ],
43
+ ['HEXTILE', 0x05 ],
44
+ ['RRE', 0x02 ],
45
+ ['RAW', 0x00 ],
46
+ ['DesktopSize', -223 ],
47
+ ['Cursor', -239 ],
48
+
49
+ // Psuedo-encoding settings
50
+ //['JPEG_quality_lo', -32 ],
51
+ ['JPEG_quality_med', -26 ],
52
+ //['JPEG_quality_hi', -23 ],
53
+ //['compress_lo', -255 ],
54
+ ['compress_hi', -247 ],
55
+ ['last_rect', -224 ],
56
+ ['xvp', -309 ]
57
+ ];
58
+
59
+ this._encHandlers = {};
60
+ this._encNames = {};
61
+ this._encStats = {};
62
+
63
+ this._sock = null; // Websock object
64
+ this._display = null; // Display object
65
+ this._keyboard = null; // Keyboard input handler object
66
+ this._mouse = null; // Mouse input handler object
67
+ this._sendTimer = null; // Send Queue check timer
68
+ this._disconnTimer = null; // disconnection timer
69
+ this._msgTimer = null; // queued handle_msg timer
70
+
71
+ // Frame buffer update state
72
+ this._FBU = {
73
+ rects: 0,
74
+ subrects: 0, // RRE
75
+ lines: 0, // RAW
76
+ tiles: 0, // HEXTILE
77
+ bytes: 0,
78
+ x: 0,
79
+ y: 0,
80
+ width: 0,
81
+ height: 0,
82
+ encoding: 0,
83
+ subencoding: -1,
84
+ background: null,
85
+ zlib: [] // TIGHT zlib streams
86
+ };
87
+
88
+ this._fb_Bpp = 4;
89
+ this._fb_depth = 3;
90
+ this._fb_width = 0;
91
+ this._fb_height = 0;
92
+ this._fb_name = "";
93
+
94
+ this._rre_chunk_sz = 100;
95
+
96
+ this._timing = {
97
+ last_fbu: 0,
98
+ fbu_total: 0,
99
+ fbu_total_cnt: 0,
100
+ full_fbu_total: 0,
101
+ full_fbu_cnt: 0,
102
+
103
+ fbu_rt_start: 0,
104
+ fbu_rt_total: 0,
105
+ fbu_rt_cnt: 0,
106
+ pixels: 0
107
+ };
108
+
109
+ // Mouse state
110
+ this._mouse_buttonMask = 0;
111
+ this._mouse_arr = [];
112
+ this._viewportDragging = false;
113
+ this._viewportDragPos = {};
114
+
115
+ // set the default value on user-facing properties
116
+ Util.set_defaults(this, defaults, {
117
+ 'target': 'null', // VNC display rendering Canvas object
118
+ 'focusContainer': document, // DOM element that captures keyboard input
119
+ 'encrypt': false, // Use TLS/SSL/wss encryption
120
+ 'true_color': true, // Request true color pixel data
121
+ 'local_cursor': false, // Request locally rendered cursor
122
+ 'shared': true, // Request shared mode
123
+ 'view_only': false, // Disable client mouse/keyboard
124
+ 'xvp_password_sep': '@', // Separator for XVP password fields
125
+ 'disconnectTimeout': 3, // Time (s) to wait for disconnection
126
+ 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection
127
+ 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
128
+ 'viewportDrag': false, // Move the viewport on mouse drags
129
+
130
+ // Callback functions
131
+ 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
132
+ 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required
133
+ 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
134
+ 'onBell': function () { }, // onBell(rfb): RFB Bell message received
135
+ 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
136
+ 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
137
+ 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
138
+ 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
139
+ 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection
140
+ });
141
+
142
+ // main setup
143
+ Util.Debug(">> RFB.constructor");
144
+
145
+ // populate encHandlers with bound versions
146
+ Object.keys(RFB.encodingHandlers).forEach(function (encName) {
147
+ this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
148
+ }.bind(this));
149
+
150
+ // Create lookup tables based on encoding number
151
+ for (var i = 0; i < this._encodings.length; i++) {
152
+ this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
153
+ this._encNames[this._encodings[i][1]] = this._encodings[i][0];
154
+ this._encStats[this._encodings[i][1]] = [0, 0];
155
+ }
156
+
157
+ try {
158
+ this._display = new Display({target: this._target});
159
+ } catch (exc) {
160
+ Util.Error("Display exception: " + exc);
161
+ this._updateState('fatal', "No working Display");
162
+ }
163
+
164
+ this._keyboard = new Keyboard({target: this._focusContainer,
165
+ onKeyPress: this._handleKeyPress.bind(this)});
166
+
167
+ this._mouse = new Mouse({target: this._target,
168
+ onMouseButton: this._handleMouseButton.bind(this),
169
+ onMouseMove: this._handleMouseMove.bind(this),
170
+ notify: this._keyboard.sync.bind(this._keyboard)});
171
+
172
+ this._sock = new Websock();
173
+ this._sock.on('message', this._handle_message.bind(this));
174
+ this._sock.on('open', function () {
175
+ if (this._rfb_state === 'connect') {
176
+ this._updateState('ProtocolVersion', "Starting VNC handshake");
177
+ } else {
178
+ this._fail("Got unexpected WebSocket connection");
179
+ }
180
+ }.bind(this));
181
+ this._sock.on('close', function (e) {
182
+ Util.Warn("WebSocket on-close event");
183
+ var msg = "";
184
+ if (e.code) {
185
+ msg = " (code: " + e.code;
186
+ if (e.reason) {
187
+ msg += ", reason: " + e.reason;
188
+ }
189
+ msg += ")";
190
+ }
191
+ if (this._rfb_state === 'disconnect') {
192
+ this._updateState('disconnected', 'VNC disconnected' + msg);
193
+ } else if (this._rfb_state === 'ProtocolVersion') {
194
+ this._fail('Failed to connect to server' + msg);
195
+ } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) {
196
+ Util.Error("Received onclose while disconnected" + msg);
197
+ } else {
198
+ this._fail("Server disconnected" + msg);
199
+ }
200
+ this._sock.off('close');
201
+ }.bind(this));
202
+ this._sock.on('error', function (e) {
203
+ Util.Warn("WebSocket on-error event");
204
+ });
205
+
206
+ this._init_vars();
207
+
208
+ var rmode = this._display.get_render_mode();
209
+ if (Websock_native) {
210
+ Util.Info("Using native WebSockets");
211
+ this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
212
+ } else {
213
+ Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version);
214
+ if (!Util.Flash || Util.Flash.version < 9) {
215
+ this._updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required");
216
+ } else if (document.location.href.substr(0, 7) === 'file://') {
217
+ this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash");
218
+ } else {
219
+ this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
220
+ }
221
+ }
222
+
223
+ Util.Debug("<< RFB.constructor");
224
+ };
225
+
226
+ RFB.prototype = {
227
+ // Public methods
228
+ connect: function (host, port, password, path) {
229
+ this._rfb_host = host;
230
+ this._rfb_port = port;
231
+ this._rfb_password = (password !== undefined) ? password : "";
232
+ this._rfb_path = (path !== undefined) ? path : "";
233
+
234
+ if (!this._rfb_host || !this._rfb_port) {
235
+ return this._fail("Must set host and port");
236
+ }
237
+
238
+ this._updateState('connect');
239
+ },
240
+
241
+ disconnect: function () {
242
+ this._updateState('disconnect', 'Disconnecting');
243
+ this._sock.off('error');
244
+ this._sock.off('message');
245
+ this._sock.off('open');
246
+ },
247
+
248
+ sendPassword: function (passwd) {
249
+ this._rfb_password = passwd;
250
+ this._rfb_state = 'Authentication';
251
+ setTimeout(this._init_msg.bind(this), 1);
252
+ },
253
+
254
+ sendCtrlAltDel: function () {
255
+ if (this._rfb_state !== 'normal' || this._view_only) { return false; }
256
+ Util.Info("Sending Ctrl-Alt-Del");
257
+
258
+ var arr = [];
259
+ arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 1));
260
+ arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 1));
261
+ arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 1));
262
+ arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 0));
263
+ arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 0));
264
+ arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 0));
265
+ this._sock.send(arr);
266
+ },
267
+
268
+ xvpOp: function (ver, op) {
269
+ if (this._rfb_xvp_ver < ver) { return false; }
270
+ Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
271
+ this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
272
+ return true;
273
+ },
274
+
275
+ xvpShutdown: function () {
276
+ return this.xvpOp(1, 2);
277
+ },
278
+
279
+ xvpReboot: function () {
280
+ return this.xvpOp(1, 3);
281
+ },
282
+
283
+ xvpReset: function () {
284
+ return this.xvpOp(1, 4);
285
+ },
286
+
287
+ // Send a key press. If 'down' is not specified then send a down key
288
+ // followed by an up key.
289
+ sendKey: function (code, down) {
290
+ if (this._rfb_state !== "normal" || this._view_only) { return false; }
291
+ var arr = [];
292
+ if (typeof down !== 'undefined') {
293
+ Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
294
+ arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0));
295
+ } else {
296
+ Util.Info("Sending key code (down + up): " + code);
297
+ arr = arr.concat(RFB.messages.keyEvent(code, 1));
298
+ arr = arr.concat(RFB.messages.keyEvent(code, 0));
299
+ }
300
+ this._sock.send(arr);
301
+ },
302
+
303
+ clipboardPasteFrom: function (text) {
304
+ if (this._rfb_state !== 'normal') { return; }
305
+ this._sock.send(RFB.messages.clientCutText(text));
306
+ },
307
+
308
+ // Private methods
309
+
310
+ _connect: function () {
311
+ Util.Debug(">> RFB.connect");
312
+
313
+ var uri;
314
+ if (typeof UsingSocketIO !== 'undefined') {
315
+ uri = 'http';
316
+ } else {
317
+ uri = this._encrypt ? 'wss' : 'ws';
318
+ }
319
+
320
+ uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
321
+ Util.Info("connecting to " + uri);
322
+
323
+ this._sock.open(uri, this._wsProtocols);
324
+
325
+ Util.Debug("<< RFB.connect");
326
+ },
327
+
328
+ _init_vars: function () {
329
+ // reset state
330
+ this._sock.init();
331
+
332
+ this._FBU.rects = 0;
333
+ this._FBU.subrects = 0; // RRE and HEXTILE
334
+ this._FBU.lines = 0; // RAW
335
+ this._FBU.tiles = 0; // HEXTILE
336
+ this._FBU.zlibs = []; // TIGHT zlib encoders
337
+ this._mouse_buttonMask = 0;
338
+ this._mouse_arr = [];
339
+ this._rfb_tightvnc = false;
340
+
341
+ // Clear the per connection encoding stats
342
+ var i;
343
+ for (i = 0; i < this._encodings.length; i++) {
344
+ this._encStats[this._encodings[i][1]][0] = 0;
345
+ }
346
+
347
+ for (i = 0; i < 4; i++) {
348
+ this._FBU.zlibs[i] = new TINF();
349
+ this._FBU.zlibs[i].init();
350
+ }
351
+ },
352
+
353
+ _print_stats: function () {
354
+ Util.Info("Encoding stats for this connection:");
355
+ var i, s;
356
+ for (i = 0; i < this._encodings.length; i++) {
357
+ s = this._encStats[this._encodings[i][1]];
358
+ if (s[0] + s[1] > 0) {
359
+ Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
360
+ }
361
+ }
362
+
363
+ Util.Info("Encoding stats since page load:");
364
+ for (i = 0; i < this._encodings.length; i++) {
365
+ s = this._encStats[this._encodings[i][1]];
366
+ Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
367
+ }
368
+ },
369
+
370
+
371
+ /*
372
+ * Page states:
373
+ * loaded - page load, equivalent to disconnected
374
+ * disconnected - idle state
375
+ * connect - starting to connect (to ProtocolVersion)
376
+ * normal - connected
377
+ * disconnect - starting to disconnect
378
+ * failed - abnormal disconnect
379
+ * fatal - failed to load page, or fatal error
380
+ *
381
+ * RFB protocol initialization states:
382
+ * ProtocolVersion
383
+ * Security
384
+ * Authentication
385
+ * password - waiting for password, not part of RFB
386
+ * SecurityResult
387
+ * ClientInitialization - not triggered by server message
388
+ * ServerInitialization (to normal)
389
+ */
390
+ _updateState: function (state, statusMsg) {
391
+ var oldstate = this._rfb_state;
392
+
393
+ if (state === oldstate) {
394
+ // Already here, ignore
395
+ Util.Debug("Already in state '" + state + "', ignoring");
396
+ }
397
+
398
+ /*
399
+ * These are disconnected states. A previous connect may
400
+ * asynchronously cause a connection so make sure we are closed.
401
+ */
402
+ if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1,
403
+ 'disconnect': 1, 'failed': 1, 'fatal': 1}) {
404
+
405
+ if (this._sendTimer) {
406
+ clearInterval(this._sendTimer);
407
+ this._sendTimer = null;
408
+ }
409
+
410
+ if (this._msgTimer) {
411
+ clearInterval(this._msgTimer);
412
+ this._msgTimer = null;
413
+ }
414
+
415
+ if (this._display && this._display.get_context()) {
416
+ this._keyboard.ungrab();
417
+ this._mouse.ungrab();
418
+ if (state !== 'connect' && state !== 'loaded') {
419
+ this._display.defaultCursor();
420
+ }
421
+ if (Util.get_logging() !== 'debug' || state === 'loaded') {
422
+ // Show noVNC logo on load and when disconnected, unless in
423
+ // debug mode
424
+ this._display.clear();
425
+ }
426
+ }
427
+
428
+ this._sock.close();
429
+ }
430
+
431
+ if (oldstate === 'fatal') {
432
+ Util.Error('Fatal error, cannot continue');
433
+ }
434
+
435
+ var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
436
+ var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg;
437
+ if (state === 'failed' || state === 'fatal') {
438
+ Util.Error(cmsg);
439
+ } else {
440
+ Util.Warn(cmsg);
441
+ }
442
+
443
+ if (oldstate === 'failed' && state === 'disconnected') {
444
+ // do disconnect action, but stay in failed state
445
+ this._rfb_state = 'failed';
446
+ } else {
447
+ this._rfb_state = state;
448
+ }
449
+
450
+ if (this._disconnTimer && this._rfb_state !== 'disconnect') {
451
+ Util.Debug("Clearing disconnect timer");
452
+ clearTimeout(this._disconnTimer);
453
+ this._disconnTimer = null;
454
+ }
455
+
456
+ switch (state) {
457
+ case 'normal':
458
+ if (oldstate === 'disconnected' || oldstate === 'failed') {
459
+ Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
460
+ }
461
+ break;
462
+
463
+ case 'connect':
464
+ this._init_vars();
465
+ this._connect();
466
+ // WebSocket.onopen transitions to 'ProtocolVersion'
467
+ break;
468
+
469
+ case 'disconnect':
470
+ this._disconnTimer = setTimeout(function () {
471
+ this._fail("Disconnect timeout");
472
+ }.bind(this), this._disconnectTimeout * 1000);
473
+
474
+ this._print_stats();
475
+
476
+ // WebSocket.onclose transitions to 'disconnected'
477
+ break;
478
+
479
+ case 'failed':
480
+ if (oldstate === 'disconnected') {
481
+ Util.Error("Invalid transition from 'disconnected' to 'failed'");
482
+ } else if (oldstate === 'normal') {
483
+ Util.Error("Error while connected.");
484
+ } else if (oldstate === 'init') {
485
+ Util.Error("Error while initializing.");
486
+ }
487
+
488
+ // Make sure we transition to disconnected
489
+ setTimeout(function () {
490
+ this._updateState('disconnected');
491
+ }.bind(this), 50);
492
+
493
+ break;
494
+
495
+ default:
496
+ // No state change action to take
497
+ }
498
+
499
+ if (oldstate === 'failed' && state === 'disconnected') {
500
+ this._onUpdateState(this, state, oldstate);
501
+ } else {
502
+ this._onUpdateState(this, state, oldstate, statusMsg);
503
+ }
504
+ },
505
+
506
+ _fail: function (msg) {
507
+ this._updateState('failed', msg);
508
+ return false;
509
+ },
510
+
511
+ _handle_message: function () {
512
+ if (this._sock.rQlen() === 0) {
513
+ Util.Warn("handle_message called on an empty receive queue");
514
+ return;
515
+ }
516
+
517
+ switch (this._rfb_state) {
518
+ case 'disconnected':
519
+ case 'failed':
520
+ Util.Error("Got data while disconnected");
521
+ break;
522
+ case 'normal':
523
+ if (this._normal_msg() && this._sock.rQlen() > 0) {
524
+ // true means we can continue processing
525
+ // Give other events a chance to run
526
+ if (this._msgTimer === null) {
527
+ Util.Debug("More data to process, creating timer");
528
+ this._msgTimer = setTimeout(function () {
529
+ this._msgTimer = null;
530
+ this._handle_message();
531
+ }.bind(this), 10);
532
+ } else {
533
+ Util.Debug("More data to process, existing timer");
534
+ }
535
+ }
536
+ break;
537
+ default:
538
+ this._init_msg();
539
+ break;
540
+ }
541
+ },
542
+
543
+ _checkEvents: function () {
544
+ if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) {
545
+ this._sock.send(this._mouse_arr);
546
+ this._mouse_arr = [];
547
+ }
548
+ },
549
+
550
+ _handleKeyPress: function (keysym, down) {
551
+ if (this._view_only) { return; } // View only, skip keyboard, events
552
+ this._sock.send(RFB.messages.keyEvent(keysym, down));
553
+ },
554
+
555
+ _handleMouseButton: function (x, y, down, bmask) {
556
+ if (down) {
557
+ this._mouse_buttonMask |= bmask;
558
+ } else {
559
+ this._mouse_buttonMask ^= bmask;
560
+ }
561
+
562
+ if (this._viewportDrag) {
563
+ if (down && !this._viewportDragging) {
564
+ this._viewportDragging = true;
565
+ this._viewportDragPos = {'x': x, 'y': y};
566
+
567
+ // Skip sending mouse events
568
+ return;
569
+ } else {
570
+ this._viewportDragging = false;
571
+ }
572
+ }
573
+
574
+ if (this._view_only) { return; } // View only, skip mouse events
575
+
576
+ this._mouse_arr = this._mouse_arr.concat(
577
+ RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
578
+ this._sock.send(this._mouse_arr);
579
+ this._mouse_arr = [];
580
+ },
581
+
582
+ _handleMouseMove: function (x, y) {
583
+ if (this._viewportDragging) {
584
+ var deltaX = this._viewportDragPos.x - x;
585
+ var deltaY = this._viewportDragPos.y - y;
586
+ this._viewportDragPos = {'x': x, 'y': y};
587
+
588
+ this._display.viewportChange(deltaX, deltaY);
589
+
590
+ // Skip sending mouse events
591
+ return;
592
+ }
593
+
594
+ if (this._view_only) { return; } // View only, skip mouse events
595
+
596
+ this._mouse_arr = this._mouse_arr.concat(
597
+ RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
598
+
599
+ this._checkEvents();
600
+ },
601
+
602
+ // Message Handlers
603
+
604
+ _negotiate_protocol_version: function () {
605
+ if (this._sock.rQlen() < 12) {
606
+ return this._fail("Incomplete protocol version");
607
+ }
608
+
609
+ var sversion = this._sock.rQshiftStr(12).substr(4, 7);
610
+ Util.Info("Server ProtocolVersion: " + sversion);
611
+ var is_repeater = 0;
612
+ switch (sversion) {
613
+ case "000.000": // UltraVNC repeater
614
+ is_repeater = 1;
615
+ break;
616
+ case "003.003":
617
+ case "003.006": // UltraVNC
618
+ case "003.889": // Apple Remote Desktop
619
+ this._rfb_version = 3.3;
620
+ break;
621
+ case "003.007":
622
+ this._rfb_version = 3.7;
623
+ break;
624
+ case "003.008":
625
+ case "004.000": // Intel AMT KVM
626
+ case "004.001": // RealVNC 4.6
627
+ this._rfb_version = 3.8;
628
+ break;
629
+ default:
630
+ return this._fail("Invalid server version " + sversion);
631
+ }
632
+
633
+ if (is_repeater) {
634
+ var repeaterID = this._repeaterID;
635
+ while (repeaterID.length < 250) {
636
+ repeaterID += "\0";
637
+ }
638
+ this._sock.send_string(repeaterID);
639
+ return true;
640
+ }
641
+
642
+ if (this._rfb_version > this._rfb_max_version) {
643
+ this._rfb_version = this._rfb_max_version;
644
+ }
645
+
646
+ // Send updates either at a rate of 1 update per 50ms, or
647
+ // whatever slower rate the network can handle
648
+ this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
649
+
650
+ var cversion = "00" + parseInt(this._rfb_version, 10) +
651
+ ".00" + ((this._rfb_version * 10) % 10);
652
+ this._sock.send_string("RFB " + cversion + "\n");
653
+ this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
654
+ },
655
+
656
+ _negotiate_security: function () {
657
+ if (this._rfb_version >= 3.7) {
658
+ // Server sends supported list, client decides
659
+ var num_types = this._sock.rQshift8();
660
+ if (this._sock.rQwait("security type", num_types, 1)) { return false; }
661
+
662
+ if (num_types === 0) {
663
+ var strlen = this._sock.rQshift32();
664
+ var reason = this._sock.rQshiftStr(strlen);
665
+ return this._fail("Security failure: " + reason);
666
+ }
667
+
668
+ this._rfb_auth_scheme = 0;
669
+ var types = this._sock.rQshiftBytes(num_types);
670
+ Util.Debug("Server security types: " + types);
671
+ for (var i = 0; i < types.length; i++) {
672
+ if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
673
+ this._rfb_auth_scheme = types[i];
674
+ }
675
+ }
676
+
677
+ if (this._rfb_auth_scheme === 0) {
678
+ return this._fail("Unsupported security types: " + types);
679
+ }
680
+
681
+ this._sock.send([this._rfb_auth_scheme]);
682
+ } else {
683
+ // Server decides
684
+ if (this._sock.rQwait("security scheme", 4)) { return false; }
685
+ this._rfb_auth_scheme = this._sock.rQshift32();
686
+ }
687
+
688
+ this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
689
+ return this._init_msg(); // jump to authentication
690
+ },
691
+
692
+ // authentication
693
+ _negotiate_xvp_auth: function () {
694
+ var xvp_sep = this._xvp_password_sep;
695
+ var xvp_auth = this._rfb_password.split(xvp_sep);
696
+ if (xvp_auth.length < 3) {
697
+ this._updateState('password', 'XVP credentials required (user' + xvp_sep +
698
+ 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
699
+ this._onPasswordRequired(this);
700
+ return false;
701
+ }
702
+
703
+ var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
704
+ String.fromCharCode(xvp_auth[1].length) +
705
+ xvp_auth[0] +
706
+ xvp_auth[1];
707
+ this._sock.send_string(xvp_auth_str);
708
+ this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
709
+ this._rfb_auth_scheme = 2;
710
+ return this._negotiate_authentication();
711
+ },
712
+
713
+ _negotiate_std_vnc_auth: function () {
714
+ if (this._rfb_password.length === 0) {
715
+ // Notify via both callbacks since it's kind of
716
+ // an RFB state change and a UI interface issue
717
+ this._updateState('password', "Password Required");
718
+ this._onPasswordRequired(this);
719
+ }
720
+
721
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
722
+
723
+ var challenge = this._sock.rQshiftBytes(16);
724
+ var response = RFB.genDES(this._rfb_password, challenge);
725
+ this._sock.send(response);
726
+ this._updateState("SecurityResult");
727
+ return true;
728
+ },
729
+
730
+ _negotiate_tight_tunnels: function (numTunnels) {
731
+ var clientSupportedTunnelTypes = {
732
+ 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
733
+ };
734
+ var serverSupportedTunnelTypes = {};
735
+ // receive tunnel capabilities
736
+ for (var i = 0; i < numTunnels; i++) {
737
+ var cap_code = this._sock.rQshift32();
738
+ var cap_vendor = this._sock.rQshiftStr(4);
739
+ var cap_signature = this._sock.rQshiftStr(8);
740
+ serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
741
+ }
742
+
743
+ // choose the notunnel type
744
+ if (serverSupportedTunnelTypes[0]) {
745
+ if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
746
+ serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
747
+ return this._fail("Client's tunnel type had the incorrect vendor or signature");
748
+ }
749
+ this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
750
+ return false; // wait until we receive the sub auth count to continue
751
+ } else {
752
+ return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
753
+ }
754
+ },
755
+
756
+ _negotiate_tight_auth: function () {
757
+ if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
758
+ if (this._sock.rQwait("num tunnels", 4)) { return false; }
759
+ var numTunnels = this._sock.rQshift32();
760
+ if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
761
+
762
+ this._rfb_tightvnc = true;
763
+
764
+ if (numTunnels > 0) {
765
+ this._negotiate_tight_tunnels(numTunnels);
766
+ return false; // wait until we receive the sub auth to continue
767
+ }
768
+ }
769
+
770
+ // second pass, do the sub-auth negotiation
771
+ if (this._sock.rQwait("sub auth count", 4)) { return false; }
772
+ var subAuthCount = this._sock.rQshift32();
773
+ if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
774
+
775
+ var clientSupportedTypes = {
776
+ 'STDVNOAUTH__': 1,
777
+ 'STDVVNCAUTH_': 2
778
+ };
779
+
780
+ var serverSupportedTypes = [];
781
+
782
+ for (var i = 0; i < subAuthCount; i++) {
783
+ var capNum = this._sock.rQshift32();
784
+ var capabilities = this._sock.rQshiftStr(12);
785
+ serverSupportedTypes.push(capabilities);
786
+ }
787
+
788
+ for (var authType in clientSupportedTypes) {
789
+ if (serverSupportedTypes.indexOf(authType) != -1) {
790
+ this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
791
+
792
+ switch (authType) {
793
+ case 'STDVNOAUTH__': // no auth
794
+ this._updateState('SecurityResult');
795
+ return true;
796
+ case 'STDVVNCAUTH_': // VNC auth
797
+ this._rfb_auth_scheme = 2;
798
+ return this._init_msg();
799
+ default:
800
+ return this._fail("Unsupported tiny auth scheme: " + authType);
801
+ }
802
+ }
803
+ }
804
+
805
+ this._fail("No supported sub-auth types!");
806
+ },
807
+
808
+ _negotiate_authentication: function () {
809
+ switch (this._rfb_auth_scheme) {
810
+ case 0: // connection failed
811
+ if (this._sock.rQwait("auth reason", 4)) { return false; }
812
+ var strlen = this._sock.rQshift32();
813
+ var reason = this._sock.rQshiftStr(strlen);
814
+ return this._fail("Auth failure: " + reason);
815
+
816
+ case 1: // no auth
817
+ if (this._rfb_version >= 3.8) {
818
+ this._updateState('SecurityResult');
819
+ return true;
820
+ }
821
+ this._updateState('ClientInitialisation', "No auth required");
822
+ return this._init_msg();
823
+
824
+ case 22: // XVP auth
825
+ return this._negotiate_xvp_auth();
826
+
827
+ case 2: // VNC authentication
828
+ return this._negotiate_std_vnc_auth();
829
+
830
+ case 16: // TightVNC Security Type
831
+ return this._negotiate_tight_auth();
832
+
833
+ default:
834
+ return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
835
+ }
836
+ },
837
+
838
+ _handle_security_result: function () {
839
+ if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
840
+ switch (this._sock.rQshift32()) {
841
+ case 0: // OK
842
+ this._updateState('ClientInitialisation', 'Authentication OK');
843
+ return this._init_msg();
844
+ case 1: // failed
845
+ if (this._rfb_version >= 3.8) {
846
+ var length = this._sock.rQshift32();
847
+ if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
848
+ var reason = this._sock.rQshiftStr(length);
849
+ return this._fail(reason);
850
+ } else {
851
+ return this._fail("Authentication failure");
852
+ }
853
+ return false;
854
+ case 2:
855
+ return this._fail("Too many auth attempts");
856
+ }
857
+ },
858
+
859
+ _negotiate_server_init: function () {
860
+ if (this._sock.rQwait("server initialization", 24)) { return false; }
861
+
862
+ /* Screen size */
863
+ this._fb_width = this._sock.rQshift16();
864
+ this._fb_height = this._sock.rQshift16();
865
+
866
+ /* PIXEL_FORMAT */
867
+ var bpp = this._sock.rQshift8();
868
+ var depth = this._sock.rQshift8();
869
+ var big_endian = this._sock.rQshift8();
870
+ var true_color = this._sock.rQshift8();
871
+
872
+ var red_max = this._sock.rQshift16();
873
+ var green_max = this._sock.rQshift16();
874
+ var blue_max = this._sock.rQshift16();
875
+ var red_shift = this._sock.rQshift8();
876
+ var green_shift = this._sock.rQshift8();
877
+ var blue_shift = this._sock.rQshift8();
878
+ this._sock.rQskipBytes(3); // padding
879
+
880
+ // NB(directxman12): we don't want to call any callbacks or print messages until
881
+ // *after* we're past the point where we could backtrack
882
+
883
+ /* Connection name/title */
884
+ var name_length = this._sock.rQshift32();
885
+ if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
886
+ this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
887
+
888
+ if (this._rfb_tightvnc) {
889
+ if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
890
+ // In TightVNC mode, ServerInit message is extended
891
+ var numServerMessages = this._sock.rQshift16();
892
+ var numClientMessages = this._sock.rQshift16();
893
+ var numEncodings = this._sock.rQshift16();
894
+ this._sock.rQskipBytes(2); // padding
895
+
896
+ var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
897
+ if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
898
+
899
+ var i;
900
+ for (i = 0; i < numServerMessages; i++) {
901
+ var srvMsg = this._sock.rQshiftStr(16);
902
+ }
903
+
904
+ for (i = 0; i < numClientMessages; i++) {
905
+ var clientMsg = this._sock.rQshiftStr(16);
906
+ }
907
+
908
+ for (i = 0; i < numEncodings; i++) {
909
+ var encoding = this._sock.rQshiftStr(16);
910
+ }
911
+ }
912
+
913
+ // NB(directxman12): these are down here so that we don't run them multiple times
914
+ // if we backtrack
915
+ Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
916
+ ", bpp: " + bpp + ", depth: " + depth +
917
+ ", big_endian: " + big_endian +
918
+ ", true_color: " + true_color +
919
+ ", red_max: " + red_max +
920
+ ", green_max: " + green_max +
921
+ ", blue_max: " + blue_max +
922
+ ", red_shift: " + red_shift +
923
+ ", green_shift: " + green_shift +
924
+ ", blue_shift: " + blue_shift);
925
+
926
+ if (big_endian !== 0) {
927
+ Util.Warn("Server native endian is not little endian");
928
+ }
929
+
930
+ if (red_shift !== 16) {
931
+ Util.Warn("Server native red-shift is not 16");
932
+ }
933
+
934
+ if (blue_shift !== 0) {
935
+ Util.Warn("Server native blue-shift is not 0");
936
+ }
937
+
938
+ // we're past the point where we could backtrack, so it's safe to call this
939
+ this._onDesktopName(this, this._fb_name);
940
+
941
+ if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
942
+ Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
943
+ this._true_color = false;
944
+ }
945
+
946
+ this._display.set_true_color(this._true_color);
947
+ this._onFBResize(this, this._fb_width, this._fb_height);
948
+ this._display.resize(this._fb_width, this._fb_height);
949
+ this._keyboard.grab();
950
+ this._mouse.grab();
951
+
952
+ if (this._true_color) {
953
+ this._fb_Bpp = 4;
954
+ this._fb_depth = 3;
955
+ } else {
956
+ this._fb_Bpp = 1;
957
+ this._fb_depth = 1;
958
+ }
959
+
960
+ var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
961
+ response = response.concat(
962
+ RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
963
+ response = response.concat(
964
+ RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
965
+ this._fb_width, this._fb_height));
966
+
967
+ this._timing.fbu_rt_start = (new Date()).getTime();
968
+ this._timing.pixels = 0;
969
+ this._sock.send(response);
970
+
971
+ this._checkEvents();
972
+
973
+ if (this._encrypt) {
974
+ this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
975
+ } else {
976
+ this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
977
+ }
978
+ },
979
+
980
+ _init_msg: function () {
981
+ switch (this._rfb_state) {
982
+ case 'ProtocolVersion':
983
+ return this._negotiate_protocol_version();
984
+
985
+ case 'Security':
986
+ return this._negotiate_security();
987
+
988
+ case 'Authentication':
989
+ return this._negotiate_authentication();
990
+
991
+ case 'SecurityResult':
992
+ return this._handle_security_result();
993
+
994
+ case 'ClientInitialisation':
995
+ this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
996
+ this._updateState('ServerInitialisation', "Authentication OK");
997
+ return true;
998
+
999
+ case 'ServerInitialisation':
1000
+ return this._negotiate_server_init();
1001
+ }
1002
+ },
1003
+
1004
+ _handle_set_colour_map_msg: function () {
1005
+ Util.Debug("SetColorMapEntries");
1006
+ this._sock.rQskip8(); // Padding
1007
+
1008
+ var first_colour = this._sock.rQshift16();
1009
+ var num_colours = this._sock.rQshift16();
1010
+ if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
1011
+
1012
+ for (var c = 0; c < num_colours; c++) {
1013
+ var red = parseInt(this._sock.rQshift16() / 256, 10);
1014
+ var green = parseInt(this._sock.rQshift16() / 256, 10);
1015
+ var blue = parseInt(this._sock.rQshift16() / 256, 10);
1016
+ this._display.set_colourMap([blue, green, red], first_colour + c);
1017
+ }
1018
+ Util.Debug("colourMap: " + this._display.get_colourMap());
1019
+ Util.Info("Registered " + num_colours + " colourMap entries");
1020
+
1021
+ return true;
1022
+ },
1023
+
1024
+ _handle_server_cut_text: function () {
1025
+ Util.Debug("ServerCutText");
1026
+ if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1027
+ this._sock.rQskipBytes(3); // Padding
1028
+ var length = this._sock.rQshift32();
1029
+ if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
1030
+
1031
+ var text = this._sock.rQshiftStr(length);
1032
+ this._onClipboard(this, text);
1033
+
1034
+ return true;
1035
+ },
1036
+
1037
+ _handle_xvp_msg: function () {
1038
+ if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
1039
+ this._sock.rQskip8(); // Padding
1040
+ var xvp_ver = this._sock.rQshift8();
1041
+ var xvp_msg = this._sock.rQshift8();
1042
+
1043
+ switch (xvp_msg) {
1044
+ case 0: // XVP_FAIL
1045
+ this._updateState(this._rfb_state, "Operation Failed");
1046
+ break;
1047
+ case 1: // XVP_INIT
1048
+ this._rfb_xvp_ver = xvp_ver;
1049
+ Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
1050
+ this._onXvpInit(this._rfb_xvp_ver);
1051
+ break;
1052
+ default:
1053
+ this._fail("Disconnected: illegal server XVP message " + xvp_msg);
1054
+ break;
1055
+ }
1056
+
1057
+ return true;
1058
+ },
1059
+
1060
+ _normal_msg: function () {
1061
+ var msg_type;
1062
+
1063
+ if (this._FBU.rects > 0) {
1064
+ msg_type = 0;
1065
+ } else {
1066
+ msg_type = this._sock.rQshift8();
1067
+ }
1068
+
1069
+ switch (msg_type) {
1070
+ case 0: // FramebufferUpdate
1071
+ var ret = this._framebufferUpdate();
1072
+ if (ret) {
1073
+ this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
1074
+ this._fb_width, this._fb_height));
1075
+ }
1076
+ return ret;
1077
+
1078
+ case 1: // SetColorMapEntries
1079
+ return this._handle_set_colour_map_msg();
1080
+
1081
+ case 2: // Bell
1082
+ Util.Debug("Bell");
1083
+ this._onBell(this);
1084
+ return true;
1085
+
1086
+ case 3: // ServerCutText
1087
+ return this._handle_server_cut_text();
1088
+
1089
+ case 250: // XVP
1090
+ return this._handle_xvp_msg();
1091
+
1092
+ default:
1093
+ this._fail("Disconnected: illegal server message type " + msg_type);
1094
+ Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1095
+ return true;
1096
+ }
1097
+ },
1098
+
1099
+ _framebufferUpdate: function () {
1100
+ var ret = true;
1101
+ var now;
1102
+
1103
+ if (this._FBU.rects === 0) {
1104
+ if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
1105
+ this._sock.rQskip8(); // Padding
1106
+ this._FBU.rects = this._sock.rQshift16();
1107
+ this._FBU.bytes = 0;
1108
+ this._timing.cur_fbu = 0;
1109
+ if (this._timing.fbu_rt_start > 0) {
1110
+ now = (new Date()).getTime();
1111
+ Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
1112
+ }
1113
+ }
1114
+
1115
+ while (this._FBU.rects > 0) {
1116
+ if (this._rfb_state !== "normal") { return false; }
1117
+
1118
+ if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
1119
+ if (this._FBU.bytes === 0) {
1120
+ if (this._sock.rQwait("rect header", 12)) { return false; }
1121
+ /* New FramebufferUpdate */
1122
+
1123
+ var hdr = this._sock.rQshiftBytes(12);
1124
+ this._FBU.x = (hdr[0] << 8) + hdr[1];
1125
+ this._FBU.y = (hdr[2] << 8) + hdr[3];
1126
+ this._FBU.width = (hdr[4] << 8) + hdr[5];
1127
+ this._FBU.height = (hdr[6] << 8) + hdr[7];
1128
+ this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1129
+ (hdr[10] << 8) + hdr[11], 10);
1130
+
1131
+ this._onFBUReceive(this,
1132
+ {'x': this._FBU.x, 'y': this._FBU.y,
1133
+ 'width': this._FBU.width, 'height': this._FBU.height,
1134
+ 'encoding': this._FBU.encoding,
1135
+ 'encodingName': this._encNames[this._FBU.encoding]});
1136
+
1137
+ if (!this._encNames[this._FBU.encoding]) {
1138
+ this._fail("Disconnected: unsupported encoding " +
1139
+ this._FBU.encoding);
1140
+ return false;
1141
+ }
1142
+ }
1143
+
1144
+ this._timing.last_fbu = (new Date()).getTime();
1145
+
1146
+ ret = this._encHandlers[this._FBU.encoding]();
1147
+
1148
+ now = (new Date()).getTime();
1149
+ this._timing.cur_fbu += (now - this._timing.last_fbu);
1150
+
1151
+ if (ret) {
1152
+ this._encStats[this._FBU.encoding][0]++;
1153
+ this._encStats[this._FBU.encoding][1]++;
1154
+ this._timing.pixels += this._FBU.width * this._FBU.height;
1155
+ }
1156
+
1157
+ if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
1158
+ if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
1159
+ this._timing.fbu_rt_start > 0) {
1160
+ this._timing.full_fbu_total += this._timing.cur_fbu;
1161
+ this._timing.full_fbu_cnt++;
1162
+ Util.Info("Timing of full FBU, curr: " +
1163
+ this._timing.cur_fbu + ", total: " +
1164
+ this._timing.full_fbu_total + ", cnt: " +
1165
+ this._timing.full_fbu_cnt + ", avg: " +
1166
+ (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
1167
+ }
1168
+
1169
+ if (this._timing.fbu_rt_start > 0) {
1170
+ var fbu_rt_diff = now - this._timing.fbu_rt_start;
1171
+ this._timing.fbu_rt_total += fbu_rt_diff;
1172
+ this._timing.fbu_rt_cnt++;
1173
+ Util.Info("full FBU round-trip, cur: " +
1174
+ fbu_rt_diff + ", total: " +
1175
+ this._timing.fbu_rt_total + ", cnt: " +
1176
+ this._timing.fbu_rt_cnt + ", avg: " +
1177
+ (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
1178
+ this._timing.fbu_rt_start = 0;
1179
+ }
1180
+ }
1181
+
1182
+ if (!ret) { return ret; } // need more data
1183
+ }
1184
+
1185
+ this._onFBUComplete(this,
1186
+ {'x': this._FBU.x, 'y': this._FBU.y,
1187
+ 'width': this._FBU.width, 'height': this._FBU.height,
1188
+ 'encoding': this._FBU.encoding,
1189
+ 'encodingName': this._encNames[this._FBU.encoding]});
1190
+
1191
+ return true; // We finished this FBU
1192
+ },
1193
+ };
1194
+
1195
+ Util.make_properties(RFB, [
1196
+ ['target', 'wo', 'dom'], // VNC display rendering Canvas object
1197
+ ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
1198
+ ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
1199
+ ['true_color', 'rw', 'bool'], // Request true color pixel data
1200
+ ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
1201
+ ['shared', 'rw', 'bool'], // Request shared mode
1202
+ ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
1203
+ ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
1204
+ ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
1205
+ ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
1206
+ ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
1207
+ ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
1208
+
1209
+ // Callback functions
1210
+ ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
1211
+ ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
1212
+ ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
1213
+ ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
1214
+ ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
1215
+ ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
1216
+ ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
1217
+ ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
1218
+ ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
1219
+ ]);
1220
+
1221
+ RFB.prototype.set_local_cursor = function (cursor) {
1222
+ if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
1223
+ this._local_cursor = false;
1224
+ this._display.disableLocalCursor(); //Only show server-side cursor
1225
+ } else {
1226
+ if (this._display.get_cursor_uri()) {
1227
+ this._local_cursor = true;
1228
+ } else {
1229
+ Util.Warn("Browser does not support local cursor");
1230
+ }
1231
+ }
1232
+ };
1233
+
1234
+ RFB.prototype.get_display = function () { return this._display; };
1235
+ RFB.prototype.get_keyboard = function () { return this._keyboard; };
1236
+ RFB.prototype.get_mouse = function () { return this._mouse; };
1237
+
1238
+ // Class Methods
1239
+ RFB.messages = {
1240
+ keyEvent: function (keysym, down) {
1241
+ var arr = [4];
1242
+ arr.push8(down);
1243
+ arr.push16(0);
1244
+ arr.push32(keysym);
1245
+ return arr;
1246
+ },
1247
+
1248
+ pointerEvent: function (x, y, mask) {
1249
+ var arr = [5]; // msg-type
1250
+ arr.push8(mask);
1251
+ arr.push16(x);
1252
+ arr.push16(y);
1253
+ return arr;
1254
+ },
1255
+
1256
+ // TODO(directxman12): make this unicode compatible?
1257
+ clientCutText: function (text) {
1258
+ var arr = [6]; // msg-type
1259
+ arr.push8(0); // padding
1260
+ arr.push8(0); // padding
1261
+ arr.push8(0); // padding
1262
+ arr.push32(text.length);
1263
+ var n = text.length;
1264
+ for (var i = 0; i < n; i++) {
1265
+ arr.push(text.charCodeAt(i));
1266
+ }
1267
+
1268
+ return arr;
1269
+ },
1270
+
1271
+ pixelFormat: function (bpp, depth, true_color) {
1272
+ var arr = [0]; // msg-type
1273
+ arr.push8(0); // padding
1274
+ arr.push8(0); // padding
1275
+ arr.push8(0); // padding
1276
+
1277
+ arr.push8(bpp * 8); // bits-per-pixel
1278
+ arr.push8(depth * 8); // depth
1279
+ arr.push8(0); // little-endian
1280
+ arr.push8(true_color ? 1 : 0); // true-color
1281
+
1282
+ arr.push16(255); // red-max
1283
+ arr.push16(255); // green-max
1284
+ arr.push16(255); // blue-max
1285
+ arr.push8(16); // red-shift
1286
+ arr.push8(8); // green-shift
1287
+ arr.push8(0); // blue-shift
1288
+
1289
+ arr.push8(0); // padding
1290
+ arr.push8(0); // padding
1291
+ arr.push8(0); // padding
1292
+ return arr;
1293
+ },
1294
+
1295
+ clientEncodings: function (encodings, local_cursor, true_color) {
1296
+ var i, encList = [];
1297
+
1298
+ for (i = 0; i < encodings.length; i++) {
1299
+ if (encodings[i][0] === "Cursor" && !local_cursor) {
1300
+ Util.Debug("Skipping Cursor pseudo-encoding");
1301
+ } else if (encodings[i][0] === "TIGHT" && !true_color) {
1302
+ // TODO: remove this when we have tight+non-true-color
1303
+ Util.Warn("Skipping tight as it is only supported with true color");
1304
+ } else {
1305
+ encList.push(encodings[i][1]);
1306
+ }
1307
+ }
1308
+
1309
+ var arr = [2]; // msg-type
1310
+ arr.push8(0); // padding
1311
+
1312
+ arr.push16(encList.length); // encoding count
1313
+ for (i = 0; i < encList.length; i++) {
1314
+ arr.push32(encList[i]);
1315
+ }
1316
+
1317
+ return arr;
1318
+ },
1319
+
1320
+ fbUpdateRequests: function (cleanDirty, fb_width, fb_height) {
1321
+ var arr = [];
1322
+
1323
+ var cb = cleanDirty.cleanBox;
1324
+ var w, h;
1325
+ if (cb.w > 0 && cb.h > 0) {
1326
+ w = typeof cb.w === "undefined" ? fb_width : cb.w;
1327
+ h = typeof cb.h === "undefined" ? fb_height : cb.h;
1328
+ // Request incremental for clean box
1329
+ arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h));
1330
+ }
1331
+
1332
+ for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
1333
+ var db = cleanDirty.dirtyBoxes[i];
1334
+ // Force all (non-incremental) for dirty box
1335
+ w = typeof db.w === "undefined" ? fb_width : db.w;
1336
+ h = typeof db.h === "undefined" ? fb_height : db.h;
1337
+ arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h));
1338
+ }
1339
+
1340
+ return arr;
1341
+ },
1342
+
1343
+ fbUpdateRequest: function (incremental, x, y, w, h) {
1344
+ if (typeof(x) === "undefined") { x = 0; }
1345
+ if (typeof(y) === "undefined") { y = 0; }
1346
+
1347
+ var arr = [3]; // msg-type
1348
+ arr.push8(incremental);
1349
+ arr.push16(x);
1350
+ arr.push16(y);
1351
+ arr.push16(w);
1352
+ arr.push16(h);
1353
+
1354
+ return arr;
1355
+ }
1356
+ };
1357
+
1358
+ RFB.genDES = function (password, challenge) {
1359
+ var passwd = [];
1360
+ for (var i = 0; i < password.length; i++) {
1361
+ passwd.push(password.charCodeAt(i));
1362
+ }
1363
+ return (new DES(passwd)).encrypt(challenge);
1364
+ };
1365
+
1366
+ RFB.extract_data_uri = function (arr) {
1367
+ return ";base64," + Base64.encode(arr);
1368
+ };
1369
+
1370
+ RFB.encodingHandlers = {
1371
+ RAW: function () {
1372
+ if (this._FBU.lines === 0) {
1373
+ this._FBU.lines = this._FBU.height;
1374
+ }
1375
+
1376
+ this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
1377
+ if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
1378
+ var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
1379
+ var curr_height = Math.min(this._FBU.lines,
1380
+ Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
1381
+ this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
1382
+ curr_height, this._sock.get_rQ(),
1383
+ this._sock.get_rQi());
1384
+ this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
1385
+ this._FBU.lines -= curr_height;
1386
+
1387
+ if (this._FBU.lines > 0) {
1388
+ this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
1389
+ } else {
1390
+ this._FBU.rects--;
1391
+ this._FBU.bytes = 0;
1392
+ }
1393
+
1394
+ return true;
1395
+ },
1396
+
1397
+ COPYRECT: function () {
1398
+ this._FBU.bytes = 4;
1399
+ if (this._sock.rQwait("COPYRECT", 4)) { return false; }
1400
+ this._display.renderQ_push({
1401
+ 'type': 'copy',
1402
+ 'old_x': this._sock.rQshift16(),
1403
+ 'old_y': this._sock.rQshift16(),
1404
+ 'x': this._FBU.x,
1405
+ 'y': this._FBU.y,
1406
+ 'width': this._FBU.width,
1407
+ 'height': this._FBU.height
1408
+ });
1409
+ this._FBU.rects--;
1410
+ this._FBU.bytes = 0;
1411
+ return true;
1412
+ },
1413
+
1414
+ RRE: function () {
1415
+ var color;
1416
+ if (this._FBU.subrects === 0) {
1417
+ this._FBU.bytes = 4 + this._fb_Bpp;
1418
+ if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
1419
+ this._FBU.subrects = this._sock.rQshift32();
1420
+ color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
1421
+ this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
1422
+ }
1423
+
1424
+ while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
1425
+ color = this._sock.rQshiftBytes(this._fb_Bpp);
1426
+ var x = this._sock.rQshift16();
1427
+ var y = this._sock.rQshift16();
1428
+ var width = this._sock.rQshift16();
1429
+ var height = this._sock.rQshift16();
1430
+ this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
1431
+ this._FBU.subrects--;
1432
+ }
1433
+
1434
+ if (this._FBU.subrects > 0) {
1435
+ var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
1436
+ this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
1437
+ } else {
1438
+ this._FBU.rects--;
1439
+ this._FBU.bytes = 0;
1440
+ }
1441
+
1442
+ return true;
1443
+ },
1444
+
1445
+ HEXTILE: function () {
1446
+ var rQ = this._sock.get_rQ();
1447
+ var rQi = this._sock.get_rQi();
1448
+
1449
+ if (this._FBU.tiles === 0) {
1450
+ this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
1451
+ this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
1452
+ this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
1453
+ this._FBU.tiles = this._FBU.total_tiles;
1454
+ }
1455
+
1456
+ while (this._FBU.tiles > 0) {
1457
+ this._FBU.bytes = 1;
1458
+ if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
1459
+ var subencoding = rQ[rQi]; // Peek
1460
+ if (subencoding > 30) { // Raw
1461
+ this._fail("Disconnected: illegal hextile subencoding " + subencoding);
1462
+ return false;
1463
+ }
1464
+
1465
+ var subrects = 0;
1466
+ var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
1467
+ var tile_x = curr_tile % this._FBU.tiles_x;
1468
+ var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
1469
+ var x = this._FBU.x + tile_x * 16;
1470
+ var y = this._FBU.y + tile_y * 16;
1471
+ var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
1472
+ var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
1473
+
1474
+ // Figure out how much we are expecting
1475
+ if (subencoding & 0x01) { // Raw
1476
+ this._FBU.bytes += w * h * this._fb_Bpp;
1477
+ } else {
1478
+ if (subencoding & 0x02) { // Background
1479
+ this._FBU.bytes += this._fb_Bpp;
1480
+ }
1481
+ if (subencoding & 0x04) { // Foreground
1482
+ this._FBU.bytes += this._fb_Bpp;
1483
+ }
1484
+ if (subencoding & 0x08) { // AnySubrects
1485
+ this._FBU.bytes++; // Since we aren't shifting it off
1486
+ if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
1487
+ subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
1488
+ if (subencoding & 0x10) { // SubrectsColoured
1489
+ this._FBU.bytes += subrects * (this._fb_Bpp + 2);
1490
+ } else {
1491
+ this._FBU.bytes += subrects * 2;
1492
+ }
1493
+ }
1494
+ }
1495
+
1496
+ if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
1497
+
1498
+ // We know the encoding and have a whole tile
1499
+ this._FBU.subencoding = rQ[rQi];
1500
+ rQi++;
1501
+ if (this._FBU.subencoding === 0) {
1502
+ if (this._FBU.lastsubencoding & 0x01) {
1503
+ // Weird: ignore blanks are RAW
1504
+ Util.Debug(" Ignoring blank after RAW");
1505
+ } else {
1506
+ this._display.fillRect(x, y, w, h, this._FBU.background);
1507
+ }
1508
+ } else if (this._FBU.subencoding & 0x01) { // Raw
1509
+ this._display.blitImage(x, y, w, h, rQ, rQi);
1510
+ rQi += this._FBU.bytes - 1;
1511
+ } else {
1512
+ if (this._FBU.subencoding & 0x02) { // Background
1513
+ this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
1514
+ rQi += this._fb_Bpp;
1515
+ }
1516
+ if (this._FBU.subencoding & 0x04) { // Foreground
1517
+ this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
1518
+ rQi += this._fb_Bpp;
1519
+ }
1520
+
1521
+ this._display.startTile(x, y, w, h, this._FBU.background);
1522
+ if (this._FBU.subencoding & 0x08) { // AnySubrects
1523
+ subrects = rQ[rQi];
1524
+ rQi++;
1525
+
1526
+ for (var s = 0; s < subrects; s++) {
1527
+ var color;
1528
+ if (this._FBU.subencoding & 0x10) { // SubrectsColoured
1529
+ color = rQ.slice(rQi, rQi + this._fb_Bpp);
1530
+ rQi += this._fb_Bpp;
1531
+ } else {
1532
+ color = this._FBU.foreground;
1533
+ }
1534
+ var xy = rQ[rQi];
1535
+ rQi++;
1536
+ var sx = (xy >> 4);
1537
+ var sy = (xy & 0x0f);
1538
+
1539
+ var wh = rQ[rQi];
1540
+ rQi++;
1541
+ var sw = (wh >> 4) + 1;
1542
+ var sh = (wh & 0x0f) + 1;
1543
+
1544
+ this._display.subTile(sx, sy, sw, sh, color);
1545
+ }
1546
+ }
1547
+ this._display.finishTile();
1548
+ }
1549
+ this._sock.set_rQi(rQi);
1550
+ this._FBU.lastsubencoding = this._FBU.subencoding;
1551
+ this._FBU.bytes = 0;
1552
+ this._FBU.tiles--;
1553
+ }
1554
+
1555
+ if (this._FBU.tiles === 0) {
1556
+ this._FBU.rects--;
1557
+ }
1558
+
1559
+ return true;
1560
+ },
1561
+
1562
+ getTightCLength: function (arr) {
1563
+ var header = 1, data = 0;
1564
+ data += arr[0] & 0x7f;
1565
+ if (arr[0] & 0x80) {
1566
+ header++;
1567
+ data += (arr[1] & 0x7f) << 7;
1568
+ if (arr[1] & 0x80) {
1569
+ header++;
1570
+ data += arr[2] << 14;
1571
+ }
1572
+ }
1573
+ return [header, data];
1574
+ },
1575
+
1576
+ display_tight: function (isTightPNG) {
1577
+ if (this._fb_depth === 1) {
1578
+ this._fail("Tight protocol handler only implements true color mode");
1579
+ }
1580
+
1581
+ this._FBU.bytes = 1; // compression-control byte
1582
+ if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
1583
+
1584
+ var checksum = function (data) {
1585
+ var sum = 0;
1586
+ for (var i = 0; i < data.length; i++) {
1587
+ sum += data[i];
1588
+ if (sum > 65536) sum -= 65536;
1589
+ }
1590
+ return sum;
1591
+ };
1592
+
1593
+ var resetStreams = 0;
1594
+ var streamId = -1;
1595
+ var decompress = function (data) {
1596
+ for (var i = 0; i < 4; i++) {
1597
+ if ((resetStreams >> i) & 1) {
1598
+ this._FBU.zlibs[i].reset();
1599
+ Util.Info("Reset zlib stream " + i);
1600
+ }
1601
+ }
1602
+
1603
+ var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
1604
+ if (uncompressed.status !== 0) {
1605
+ Util.Error("Invalid data in zlib stream");
1606
+ }
1607
+
1608
+ return uncompressed.data;
1609
+ }.bind(this);
1610
+
1611
+ var indexedToRGB = function (data, numColors, palette, width, height) {
1612
+ // Convert indexed (palette based) image data to RGB
1613
+ // TODO: reduce number of calculations inside loop
1614
+ var dest = [];
1615
+ var x, y, dp, sp;
1616
+ if (numColors === 2) {
1617
+ var w = Math.floor((width + 7) / 8);
1618
+ var w1 = Math.floor(width / 8);
1619
+
1620
+ for (y = 0; y < height; y++) {
1621
+ var b;
1622
+ for (x = 0; x < w1; x++) {
1623
+ for (b = 7; b >= 0; b--) {
1624
+ dp = (y * width + x * 8 + 7 - b) * 3;
1625
+ sp = (data[y * w + x] >> b & 1) * 3;
1626
+ dest[dp] = palette[sp];
1627
+ dest[dp + 1] = palette[sp + 1];
1628
+ dest[dp + 2] = palette[sp + 2];
1629
+ }
1630
+ }
1631
+
1632
+ for (b = 7; b >= 8 - width % 8; b--) {
1633
+ dp = (y * width + x * 8 + 7 - b) * 3;
1634
+ sp = (data[y * w + x] >> b & 1) * 3;
1635
+ dest[dp] = palette[sp];
1636
+ dest[dp + 1] = palette[sp + 1];
1637
+ dest[dp + 2] = palette[sp + 2];
1638
+ }
1639
+ }
1640
+ } else {
1641
+ for (y = 0; y < height; y++) {
1642
+ for (x = 0; x < width; x++) {
1643
+ dp = (y * width + x) * 3;
1644
+ sp = data[y * width + x] * 3;
1645
+ dest[dp] = palette[sp];
1646
+ dest[dp + 1] = palette[sp + 1];
1647
+ dest[dp + 2] = palette[sp + 2];
1648
+ }
1649
+ }
1650
+ }
1651
+
1652
+ return dest;
1653
+ }.bind(this);
1654
+
1655
+ var rQ = this._sock.get_rQ();
1656
+ var rQi = this._sock.get_rQi();
1657
+ var cmode, clength, data;
1658
+
1659
+ var handlePalette = function () {
1660
+ var numColors = rQ[rQi + 2] + 1;
1661
+ var paletteSize = numColors * this._fb_depth;
1662
+ this._FBU.bytes += paletteSize;
1663
+ if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
1664
+
1665
+ var bpp = (numColors <= 2) ? 1 : 8;
1666
+ var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
1667
+ var raw = false;
1668
+ if (rowSize * this._FBU.height < 12) {
1669
+ raw = true;
1670
+ clength = [0, rowSize * this._FBU.height];
1671
+ } else {
1672
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
1673
+ 3 + paletteSize + 3));
1674
+ }
1675
+
1676
+ this._FBU.bytes += clength[0] + clength[1];
1677
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1678
+
1679
+ // Shift ctl, filter id, num colors, palette entries, and clength off
1680
+ this._sock.rQskipBytes(3);
1681
+ var palette = this._sock.rQshiftBytes(paletteSize);
1682
+ this._sock.rQskipBytes(clength[0]);
1683
+
1684
+ if (raw) {
1685
+ data = this._sock.rQshiftBytes(clength[1]);
1686
+ } else {
1687
+ data = decompress(this._sock.rQshiftBytes(clength[1]));
1688
+ }
1689
+
1690
+ // Convert indexed (palette based) image data to RGB
1691
+ var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
1692
+
1693
+ this._display.renderQ_push({
1694
+ 'type': 'blitRgb',
1695
+ 'data': rgb,
1696
+ 'x': this._FBU.x,
1697
+ 'y': this._FBU.y,
1698
+ 'width': this._FBU.width,
1699
+ 'height': this._FBU.height
1700
+ });
1701
+
1702
+ return true;
1703
+ }.bind(this);
1704
+
1705
+ var handleCopy = function () {
1706
+ var raw = false;
1707
+ var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
1708
+ if (uncompressedSize < 12) {
1709
+ raw = true;
1710
+ clength = [0, uncompressedSize];
1711
+ } else {
1712
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
1713
+ }
1714
+ this._FBU.bytes = 1 + clength[0] + clength[1];
1715
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1716
+
1717
+ // Shift ctl, clength off
1718
+ this._sock.rQshiftBytes(1 + clength[0]);
1719
+
1720
+ if (raw) {
1721
+ data = this._sock.rQshiftBytes(clength[1]);
1722
+ } else {
1723
+ data = decompress(this._sock.rQshiftBytes(clength[1]));
1724
+ }
1725
+
1726
+ this._display.renderQ_push({
1727
+ 'type': 'blitRgb',
1728
+ 'data': data,
1729
+ 'x': this._FBU.x,
1730
+ 'y': this._FBU.y,
1731
+ 'width': this._FBU.width,
1732
+ 'height': this._FBU.height
1733
+ });
1734
+
1735
+ return true;
1736
+ }.bind(this);
1737
+
1738
+ var ctl = this._sock.rQpeek8();
1739
+
1740
+ // Keep tight reset bits
1741
+ resetStreams = ctl & 0xF;
1742
+
1743
+ // Figure out filter
1744
+ ctl = ctl >> 4;
1745
+ streamId = ctl & 0x3;
1746
+
1747
+ if (ctl === 0x08) cmode = "fill";
1748
+ else if (ctl === 0x09) cmode = "jpeg";
1749
+ else if (ctl === 0x0A) cmode = "png";
1750
+ else if (ctl & 0x04) cmode = "filter";
1751
+ else if (ctl < 0x04) cmode = "copy";
1752
+ else return this._fail("Illegal tight compression received, ctl: " + ctl);
1753
+
1754
+ if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
1755
+ return this._fail("filter/copy received in tightPNG mode");
1756
+ }
1757
+
1758
+ switch (cmode) {
1759
+ // fill use fb_depth because TPIXELs drop the padding byte
1760
+ case "fill": // TPIXEL
1761
+ this._FBU.bytes += this._fb_depth;
1762
+ break;
1763
+ case "jpeg": // max clength
1764
+ this._FBU.bytes += 3;
1765
+ break;
1766
+ case "png": // max clength
1767
+ this._FBU.bytes += 3;
1768
+ break;
1769
+ case "filter": // filter id + num colors if palette
1770
+ this._FBU.bytes += 2;
1771
+ break;
1772
+ case "copy":
1773
+ break;
1774
+ }
1775
+
1776
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1777
+
1778
+ // Determine FBU.bytes
1779
+ switch (cmode) {
1780
+ case "fill":
1781
+ this._sock.rQskip8(); // shift off ctl
1782
+ var color = this._sock.rQshiftBytes(this._fb_depth);
1783
+ this._display.renderQ_push({
1784
+ 'type': 'fill',
1785
+ 'x': this._FBU.x,
1786
+ 'y': this._FBU.y,
1787
+ 'width': this._FBU.width,
1788
+ 'height': this._FBU.height,
1789
+ 'color': [color[2], color[1], color[0]]
1790
+ });
1791
+ break;
1792
+ case "png":
1793
+ case "jpeg":
1794
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
1795
+ this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
1796
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1797
+
1798
+ // We have everything, render it
1799
+ this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length
1800
+ var img = new Image();
1801
+ img.src = "data: image/" + cmode +
1802
+ RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
1803
+ this._display.renderQ_push({
1804
+ 'type': 'img',
1805
+ 'img': img,
1806
+ 'x': this._FBU.x,
1807
+ 'y': this._FBU.y
1808
+ });
1809
+ img = null;
1810
+ break;
1811
+ case "filter":
1812
+ var filterId = rQ[rQi + 1];
1813
+ if (filterId === 1) {
1814
+ if (!handlePalette()) { return false; }
1815
+ } else {
1816
+ // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
1817
+ // Filter 2, Gradient is valid but not use if jpeg is enabled
1818
+ // TODO(directxman12): why aren't we just calling '_fail' here
1819
+ throw new Error("Unsupported tight subencoding received, filter: " + filterId);
1820
+ }
1821
+ break;
1822
+ case "copy":
1823
+ if (!handleCopy()) { return false; }
1824
+ break;
1825
+ }
1826
+
1827
+
1828
+ this._FBU.bytes = 0;
1829
+ this._FBU.rects--;
1830
+
1831
+ return true;
1832
+ },
1833
+
1834
+ TIGHT: function () { return this._encHandlers.display_tight(false); },
1835
+ TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
1836
+
1837
+ last_rect: function () {
1838
+ this._FBU.rects = 0;
1839
+ return true;
1840
+ },
1841
+
1842
+ DesktopSize: function () {
1843
+ Util.Debug(">> set_desktopsize");
1844
+ this._fb_width = this._FBU.width;
1845
+ this._fb_height = this._FBU.height;
1846
+ this._onFBResize(this, this._fb_width, this._fb_height);
1847
+ this._display.resize(this._fb_width, this._fb_height);
1848
+ this._timing.fbu_rt_start = (new Date()).getTime();
1849
+
1850
+ this._FBU.bytes = 0;
1851
+ this._FBU.rects--;
1852
+
1853
+ Util.Debug("<< set_desktopsize");
1854
+ return true;
1855
+ },
1856
+
1857
+ Cursor: function () {
1858
+ Util.Debug(">> set_cursor");
1859
+ var x = this._FBU.x; // hotspot-x
1860
+ var y = this._FBU.y; // hotspot-y
1861
+ var w = this._FBU.width;
1862
+ var h = this._FBU.height;
1863
+
1864
+ var pixelslength = w * h * this._fb_Bpp;
1865
+ var masklength = Math.floor((w + 7) / 8) * h;
1866
+
1867
+ this._FBU.bytes = pixelslength + masklength;
1868
+ if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
1869
+
1870
+ this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
1871
+ this._sock.rQshiftBytes(masklength),
1872
+ x, y, w, h);
1873
+
1874
+ this._FBU.bytes = 0;
1875
+ this._FBU.rects--;
1876
+
1877
+ Util.Debug("<< set_cursor");
1878
+ return true;
1879
+ },
1880
+
1881
+ JPEG_quality_lo: function () {
1882
+ Util.Error("Server sent jpeg_quality pseudo-encoding");
1883
+ },
1884
+
1885
+ compress_lo: function () {
1886
+ Util.Error("Server sent compress level pseudo-encoding");
1887
+ }
1888
+ };
1889
+ })();