novnc-rails 0.1

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.
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
+ })();