]> git.proxmox.com Git - mirror_novnc.git/blob - core/rfb.js
Process entire WebSocket message at once
[mirror_novnc.git] / core / rfb.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Copyright (C) 2016 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 /* [module]
14 * import Util from "./util";
15 * import Display from "./display";
16 * import { Keyboard, Mouse } from "./input/devices"
17 * import Websock from "./websock"
18 * import Base64 from "./base64";
19 * import DES from "./des";
20 * import KeyTable from "./input/keysym";
21 * import XtScancode from "./input/xtscancodes";
22 * import Inflator from "./inflator.mod";
23 */
24 /*jslint white: false, browser: true */
25 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
26
27 /* [module] export default */ function RFB(defaults) {
28 "use strict";
29 if (!defaults) {
30 defaults = {};
31 }
32
33 this._rfb_host = '';
34 this._rfb_port = 5900;
35 this._rfb_password = '';
36 this._rfb_path = '';
37
38 this._rfb_connection_state = '';
39 this._rfb_init_state = '';
40 this._rfb_version = 0;
41 this._rfb_max_version = 3.8;
42 this._rfb_auth_scheme = '';
43 this._rfb_disconnect_reason = "";
44
45 this._rfb_tightvnc = false;
46 this._rfb_xvp_ver = 0;
47
48 // In preference order
49 this._encodings = [
50 ['COPYRECT', 0x01 ],
51 ['TIGHT', 0x07 ],
52 ['TIGHT_PNG', -260 ],
53 ['HEXTILE', 0x05 ],
54 ['RRE', 0x02 ],
55 ['RAW', 0x00 ],
56
57 // Psuedo-encoding settings
58
59 //['JPEG_quality_lo', -32 ],
60 ['JPEG_quality_med', -26 ],
61 //['JPEG_quality_hi', -23 ],
62 //['compress_lo', -255 ],
63 ['compress_hi', -247 ],
64
65 ['DesktopSize', -223 ],
66 ['last_rect', -224 ],
67 ['Cursor', -239 ],
68 ['QEMUExtendedKeyEvent', -258 ],
69 ['ExtendedDesktopSize', -308 ],
70 ['xvp', -309 ],
71 ['Fence', -312 ],
72 ['ContinuousUpdates', -313 ]
73 ];
74
75 this._encHandlers = {};
76 this._encNames = {};
77 this._encStats = {};
78
79 this._sock = null; // Websock object
80 this._display = null; // Display object
81 this._keyboard = null; // Keyboard input handler object
82 this._mouse = null; // Mouse input handler object
83 this._disconnTimer = null; // disconnection timer
84
85 this._supportsFence = false;
86
87 this._supportsContinuousUpdates = false;
88 this._enabledContinuousUpdates = false;
89
90 // Frame buffer update state
91 this._FBU = {
92 rects: 0,
93 subrects: 0, // RRE
94 lines: 0, // RAW
95 tiles: 0, // HEXTILE
96 bytes: 0,
97 x: 0,
98 y: 0,
99 width: 0,
100 height: 0,
101 encoding: 0,
102 subencoding: -1,
103 background: null,
104 zlib: [] // TIGHT zlib streams
105 };
106
107 this._fb_Bpp = 4;
108 this._fb_depth = 3;
109 this._fb_width = 0;
110 this._fb_height = 0;
111 this._fb_name = "";
112
113 this._destBuff = null;
114 this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
115
116 this._rre_chunk_sz = 100;
117
118 this._timing = {
119 last_fbu: 0,
120 fbu_total: 0,
121 fbu_total_cnt: 0,
122 full_fbu_total: 0,
123 full_fbu_cnt: 0,
124
125 fbu_rt_start: 0,
126 fbu_rt_total: 0,
127 fbu_rt_cnt: 0,
128 pixels: 0
129 };
130
131 this._supportsSetDesktopSize = false;
132 this._screen_id = 0;
133 this._screen_flags = 0;
134
135 // Mouse state
136 this._mouse_buttonMask = 0;
137 this._mouse_arr = [];
138 this._viewportDragging = false;
139 this._viewportDragPos = {};
140 this._viewportHasMoved = false;
141
142 // QEMU Extended Key Event support - default to false
143 this._qemuExtKeyEventSupported = false;
144
145 // set the default value on user-facing properties
146 Util.set_defaults(this, defaults, {
147 'target': 'null', // VNC display rendering Canvas object
148 'focusContainer': document, // DOM element that captures keyboard input
149 'encrypt': false, // Use TLS/SSL/wss encryption
150 'true_color': true, // Request true color pixel data
151 'local_cursor': false, // Request locally rendered cursor
152 'shared': true, // Request shared mode
153 'view_only': false, // Disable client mouse/keyboard
154 'xvp_password_sep': '@', // Separator for XVP password fields
155 'disconnectTimeout': 3, // Time (s) to wait for disconnection
156 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection
157 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
158 'viewportDrag': false, // Move the viewport on mouse drags
159
160 // Callback functions
161 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change
162 'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI
163 'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished
164 'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required
165 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
166 'onBell': function () { }, // onBell(rfb): RFB Bell message received
167 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
168 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
169 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
170 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
171 'onXvpInit': function () { } // onXvpInit(version): XVP extensions active for this connection
172 });
173
174 // main setup
175 Util.Debug(">> RFB.constructor");
176
177 // populate encHandlers with bound versions
178 Object.keys(RFB.encodingHandlers).forEach(function (encName) {
179 this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
180 }.bind(this));
181
182 // Create lookup tables based on encoding number
183 for (var i = 0; i < this._encodings.length; i++) {
184 this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
185 this._encNames[this._encodings[i][1]] = this._encodings[i][0];
186 this._encStats[this._encodings[i][1]] = [0, 0];
187 }
188
189 // NB: nothing that needs explicit teardown should be done
190 // before this point, since this can throw an exception
191 try {
192 this._display = new Display({target: this._target});
193 } catch (exc) {
194 Util.Error("Display exception: " + exc);
195 throw exc;
196 }
197
198 this._keyboard = new Keyboard({target: this._focusContainer,
199 onKeyPress: this._handleKeyPress.bind(this)});
200
201 this._mouse = new Mouse({target: this._target,
202 onMouseButton: this._handleMouseButton.bind(this),
203 onMouseMove: this._handleMouseMove.bind(this),
204 notify: this._keyboard.sync.bind(this._keyboard)});
205
206 this._sock = new Websock();
207 this._sock.on('message', this._handle_message.bind(this));
208 this._sock.on('open', function () {
209 if ((this._rfb_connection_state === 'connecting') &&
210 (this._rfb_init_state === '')) {
211 this._rfb_init_state = 'ProtocolVersion';
212 Util.Debug("Starting VNC handshake");
213 } else {
214 this._fail("Got unexpected WebSocket connection");
215 }
216 }.bind(this));
217 this._sock.on('close', function (e) {
218 Util.Warn("WebSocket on-close event");
219 var msg = "";
220 if (e.code) {
221 msg = " (code: " + e.code;
222 if (e.reason) {
223 msg += ", reason: " + e.reason;
224 }
225 msg += ")";
226 }
227 switch (this._rfb_connection_state) {
228 case 'disconnecting':
229 this._updateConnectionState('disconnected');
230 break;
231 case 'connecting':
232 this._fail('Failed to connect to server' + msg);
233 break;
234 case 'disconnected':
235 Util.Error("Received onclose while disconnected" + msg);
236 break;
237 default:
238 this._fail("Server disconnected" + msg);
239 break;
240 }
241 this._sock.off('close');
242 }.bind(this));
243 this._sock.on('error', function (e) {
244 Util.Warn("WebSocket on-error event");
245 });
246
247 this._init_vars();
248 this._cleanup();
249
250 var rmode = this._display.get_render_mode();
251 Util.Info("Using native WebSockets, render mode: " + rmode);
252
253 Util.Debug("<< RFB.constructor");
254 };
255
256 (function() {
257 RFB.prototype = {
258 // Public methods
259 connect: function (host, port, password, path) {
260 this._rfb_host = host;
261 this._rfb_port = port;
262 this._rfb_password = (password !== undefined) ? password : "";
263 this._rfb_path = (path !== undefined) ? path : "";
264
265 if (!this._rfb_host || !this._rfb_port) {
266 return this._fail("Must set host and port");
267 }
268
269 this._rfb_init_state = '';
270 this._updateConnectionState('connecting');
271 return true;
272 },
273
274 disconnect: function () {
275 this._updateConnectionState('disconnecting');
276 this._sock.off('error');
277 this._sock.off('message');
278 this._sock.off('open');
279 },
280
281 sendPassword: function (passwd) {
282 this._rfb_password = passwd;
283 setTimeout(this._init_msg.bind(this), 0);
284 },
285
286 sendCtrlAltDel: function () {
287 if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
288 Util.Info("Sending Ctrl-Alt-Del");
289
290 RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1);
291 RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1);
292 RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 1);
293 RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 0);
294 RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 0);
295 RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 0);
296 return true;
297 },
298
299 xvpOp: function (ver, op) {
300 if (this._rfb_xvp_ver < ver) { return false; }
301 Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
302 this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
303 return true;
304 },
305
306 xvpShutdown: function () {
307 return this.xvpOp(1, 2);
308 },
309
310 xvpReboot: function () {
311 return this.xvpOp(1, 3);
312 },
313
314 xvpReset: function () {
315 return this.xvpOp(1, 4);
316 },
317
318 // Send a key press. If 'down' is not specified then send a down key
319 // followed by an up key.
320 sendKey: function (code, down) {
321 if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
322 if (typeof down !== 'undefined') {
323 Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
324 RFB.messages.keyEvent(this._sock, code, down ? 1 : 0);
325 } else {
326 Util.Info("Sending key code (down + up): " + code);
327 RFB.messages.keyEvent(this._sock, code, 1);
328 RFB.messages.keyEvent(this._sock, code, 0);
329 }
330 return true;
331 },
332
333 clipboardPasteFrom: function (text) {
334 if (this._rfb_connection_state !== 'connected') { return; }
335 RFB.messages.clientCutText(this._sock, text);
336 },
337
338 // Requests a change of remote desktop size. This message is an extension
339 // and may only be sent if we have received an ExtendedDesktopSize message
340 requestDesktopSize: function (width, height) {
341 if (this._rfb_connection_state !== 'connected' ||
342 this._view_only) {
343 return;
344 }
345
346 if (this._supportsSetDesktopSize) {
347 RFB.messages.setDesktopSize(this._sock, width, height,
348 this._screen_id, this._screen_flags);
349 this._sock.flush();
350 return true;
351 } else {
352 return false;
353 }
354 },
355
356
357 // Private methods
358
359 _connect: function () {
360 Util.Debug(">> RFB.connect");
361
362 var uri;
363 if (typeof UsingSocketIO !== 'undefined') {
364 uri = 'http';
365 } else {
366 uri = this._encrypt ? 'wss' : 'ws';
367 }
368
369 uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
370 Util.Info("connecting to " + uri);
371
372 this._sock.open(uri, this._wsProtocols);
373
374 Util.Debug("<< RFB.connect");
375 },
376
377 _init_vars: function () {
378 // reset state
379 this._FBU.rects = 0;
380 this._FBU.subrects = 0; // RRE and HEXTILE
381 this._FBU.lines = 0; // RAW
382 this._FBU.tiles = 0; // HEXTILE
383 this._FBU.zlibs = []; // TIGHT zlib encoders
384 this._mouse_buttonMask = 0;
385 this._mouse_arr = [];
386 this._rfb_tightvnc = false;
387
388 // Clear the per connection encoding stats
389 var i;
390 for (i = 0; i < this._encodings.length; i++) {
391 this._encStats[this._encodings[i][1]][0] = 0;
392 }
393
394 for (i = 0; i < 4; i++) {
395 this._FBU.zlibs[i] = new Inflator.Inflate();
396 }
397 },
398
399 _print_stats: function () {
400 Util.Info("Encoding stats for this connection:");
401 var i, s;
402 for (i = 0; i < this._encodings.length; i++) {
403 s = this._encStats[this._encodings[i][1]];
404 if (s[0] + s[1] > 0) {
405 Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
406 }
407 }
408
409 Util.Info("Encoding stats since page load:");
410 for (i = 0; i < this._encodings.length; i++) {
411 s = this._encStats[this._encodings[i][1]];
412 Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
413 }
414 },
415
416 _cleanup: function () {
417 if (this._display && this._display.get_context()) {
418 if (!this._view_only) { this._keyboard.ungrab(); }
419 if (!this._view_only) { this._mouse.ungrab(); }
420 this._display.defaultCursor();
421 if (Util.get_logging() !== 'debug') {
422 // Show noVNC logo on load and when disconnected, unless in
423 // debug mode
424 this._display.clear();
425 }
426 }
427 },
428
429 /*
430 * Connection states:
431 * connecting
432 * connected
433 * disconnecting
434 * disconnected - permanent state
435 */
436 _updateConnectionState: function (state) {
437 var oldstate = this._rfb_connection_state;
438
439 if (state === oldstate) {
440 Util.Debug("Already in state '" + state + "', ignoring");
441 return;
442 }
443
444 // The 'disconnected' state is permanent for each RFB object
445 if (oldstate === 'disconnected') {
446 Util.Error("Tried changing state of a disconnected RFB object");
447 return;
448 }
449
450 this._rfb_connection_state = state;
451
452 var smsg = "New state '" + state + "', was '" + oldstate + "'.";
453 Util.Debug(smsg);
454
455 if (this._disconnTimer && state !== 'disconnecting') {
456 Util.Debug("Clearing disconnect timer");
457 clearTimeout(this._disconnTimer);
458 this._disconnTimer = null;
459 this._sock.off('close'); // make sure we don't get a double event
460 }
461
462 this._onUpdateState(this, state, oldstate);
463 switch (state) {
464 case 'connected':
465 if (oldstate !== 'connecting') {
466 Util.Error("Bad transition to connected state, " +
467 "previous connection state: " + oldstate);
468 return;
469 }
470 break;
471
472 case 'disconnected':
473 if (oldstate !== 'disconnecting') {
474 Util.Error("Bad transition to disconnected state, " +
475 "previous connection state: " + oldstate);
476 return;
477 }
478
479 if (this._rfb_disconnect_reason !== "") {
480 this._onDisconnected(this, this._rfb_disconnect_reason);
481 } else {
482 // No reason means clean disconnect
483 this._onDisconnected(this);
484 }
485 break;
486
487 case 'connecting':
488 this._init_vars();
489
490 // WebSocket.onopen transitions to the RFB init states
491 this._connect();
492 break;
493
494 case 'disconnecting':
495 this._cleanup();
496 this._sock.close(); // transitions to 'disconnected'
497
498 this._disconnTimer = setTimeout(function () {
499 this._rfb_disconnect_reason = "Disconnect timeout";
500 this._updateConnectionState('disconnected');
501 }.bind(this), this._disconnectTimeout * 1000);
502
503 this._print_stats();
504 break;
505
506 default:
507 Util.Error("Unknown connection state: " + state);
508 return;
509 }
510 },
511
512 _fail: function (msg) {
513 switch (this._rfb_connection_state) {
514 case 'disconnecting':
515 Util.Error("Error while disconnecting: " + msg);
516 break;
517 case 'connected':
518 Util.Error("Error while connected: " + msg);
519 break;
520 case 'connecting':
521 Util.Error("Error while connecting: " + msg);
522 break;
523 default:
524 Util.Error("RFB error: " + msg);
525 break;
526 }
527 this._rfb_disconnect_reason = msg;
528 this._updateConnectionState('disconnecting');
529 return false;
530 },
531
532 /*
533 * Send a notification to the UI. Valid levels are:
534 * 'normal'|'warn'|'error'
535 *
536 * NOTE: Options could be added in the future.
537 * NOTE: If this function is called multiple times, remember that the
538 * interface could be only showing the latest notification.
539 */
540 _notification: function(msg, level, options) {
541 switch (level) {
542 case 'normal':
543 case 'warn':
544 case 'error':
545 Util.Debug("Notification[" + level + "]:" + msg);
546 break;
547 default:
548 Util.Error("Invalid notification level: " + level);
549 return;
550 }
551
552 if (options) {
553 this._onNotification(this, msg, level, options);
554 } else {
555 this._onNotification(this, msg, level);
556 }
557 },
558
559 _handle_message: function () {
560 if (this._sock.rQlen() === 0) {
561 Util.Warn("handle_message called on an empty receive queue");
562 return;
563 }
564
565 switch (this._rfb_connection_state) {
566 case 'disconnected':
567 Util.Error("Got data while disconnected");
568 break;
569 case 'connected':
570 while (this._normal_msg() && this._sock.rQlen() > 0);
571 break;
572 default:
573 this._init_msg();
574 break;
575 }
576 },
577
578 _handleKeyPress: function (keyevent) {
579 if (this._view_only) { return; } // View only, skip keyboard, events
580
581 var down = (keyevent.type == 'keydown');
582 if (this._qemuExtKeyEventSupported) {
583 var scancode = XtScancode[keyevent.code];
584 if (scancode) {
585 var keysym = keyevent.keysym;
586 RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
587 } else {
588 Util.Error('Unable to find a xt scancode for code = ' + keyevent.code);
589 }
590 } else {
591 keysym = keyevent.keysym.keysym;
592 RFB.messages.keyEvent(this._sock, keysym, down);
593 }
594 },
595
596 _handleMouseButton: function (x, y, down, bmask) {
597 if (down) {
598 this._mouse_buttonMask |= bmask;
599 } else {
600 this._mouse_buttonMask ^= bmask;
601 }
602
603 if (this._viewportDrag) {
604 if (down && !this._viewportDragging) {
605 this._viewportDragging = true;
606 this._viewportDragPos = {'x': x, 'y': y};
607
608 // Skip sending mouse events
609 return;
610 } else {
611 this._viewportDragging = false;
612
613 // If the viewport didn't actually move, then treat as a mouse click event
614 // Send the button down event here, as the button up event is sent at the end of this function
615 if (!this._viewportHasMoved && !this._view_only) {
616 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask);
617 }
618 this._viewportHasMoved = false;
619 }
620 }
621
622 if (this._view_only) { return; } // View only, skip mouse events
623
624 if (this._rfb_connection_state !== 'connected') { return; }
625 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
626 },
627
628 _handleMouseMove: function (x, y) {
629 if (this._viewportDragging) {
630 var deltaX = this._viewportDragPos.x - x;
631 var deltaY = this._viewportDragPos.y - y;
632
633 // The goal is to trigger on a certain physical width, the
634 // devicePixelRatio brings us a bit closer but is not optimal.
635 var dragThreshold = 10 * (window.devicePixelRatio || 1);
636
637 if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
638 Math.abs(deltaY) > dragThreshold)) {
639 this._viewportHasMoved = true;
640
641 this._viewportDragPos = {'x': x, 'y': y};
642 this._display.viewportChangePos(deltaX, deltaY);
643 }
644
645 // Skip sending mouse events
646 return;
647 }
648
649 if (this._view_only) { return; } // View only, skip mouse events
650
651 if (this._rfb_connection_state !== 'connected') { return; }
652 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
653 },
654
655 // Message Handlers
656
657 _negotiate_protocol_version: function () {
658 if (this._sock.rQlen() < 12) {
659 return this._fail("Incomplete protocol version");
660 }
661
662 var sversion = this._sock.rQshiftStr(12).substr(4, 7);
663 Util.Info("Server ProtocolVersion: " + sversion);
664 var is_repeater = 0;
665 switch (sversion) {
666 case "000.000": // UltraVNC repeater
667 is_repeater = 1;
668 break;
669 case "003.003":
670 case "003.006": // UltraVNC
671 case "003.889": // Apple Remote Desktop
672 this._rfb_version = 3.3;
673 break;
674 case "003.007":
675 this._rfb_version = 3.7;
676 break;
677 case "003.008":
678 case "004.000": // Intel AMT KVM
679 case "004.001": // RealVNC 4.6
680 case "005.000": // RealVNC 5.3
681 this._rfb_version = 3.8;
682 break;
683 default:
684 return this._fail("Invalid server version " + sversion);
685 }
686
687 if (is_repeater) {
688 var repeaterID = this._repeaterID;
689 while (repeaterID.length < 250) {
690 repeaterID += "\0";
691 }
692 this._sock.send_string(repeaterID);
693 return true;
694 }
695
696 if (this._rfb_version > this._rfb_max_version) {
697 this._rfb_version = this._rfb_max_version;
698 }
699
700 var cversion = "00" + parseInt(this._rfb_version, 10) +
701 ".00" + ((this._rfb_version * 10) % 10);
702 this._sock.send_string("RFB " + cversion + "\n");
703 Util.Debug('Sent ProtocolVersion: ' + cversion);
704
705 this._rfb_init_state = 'Security';
706 },
707
708 _negotiate_security: function () {
709 if (this._rfb_version >= 3.7) {
710 // Server sends supported list, client decides
711 var num_types = this._sock.rQshift8();
712 if (this._sock.rQwait("security type", num_types, 1)) { return false; }
713
714 if (num_types === 0) {
715 var strlen = this._sock.rQshift32();
716 var reason = this._sock.rQshiftStr(strlen);
717 return this._fail("Security failure: " + reason);
718 }
719
720 this._rfb_auth_scheme = 0;
721 var types = this._sock.rQshiftBytes(num_types);
722 Util.Debug("Server security types: " + types);
723 for (var i = 0; i < types.length; i++) {
724 switch (types[i]) {
725 case 1: // None
726 case 2: // VNC Authentication
727 case 16: // Tight
728 case 22: // XVP
729 if (types[i] > this._rfb_auth_scheme) {
730 this._rfb_auth_scheme = types[i];
731 }
732 break;
733 default:
734 break;
735 }
736 }
737
738 if (this._rfb_auth_scheme === 0) {
739 return this._fail("Unsupported security types: " + types);
740 }
741
742 this._sock.send([this._rfb_auth_scheme]);
743 } else {
744 // Server decides
745 if (this._sock.rQwait("security scheme", 4)) { return false; }
746 this._rfb_auth_scheme = this._sock.rQshift32();
747 }
748
749 this._rfb_init_state = 'Authentication';
750 Util.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
751
752 return this._init_msg(); // jump to authentication
753 },
754
755 // authentication
756 _negotiate_xvp_auth: function () {
757 var xvp_sep = this._xvp_password_sep;
758 var xvp_auth = this._rfb_password.split(xvp_sep);
759 if (xvp_auth.length < 3) {
760 var msg = 'XVP credentials required (user' + xvp_sep +
761 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
762 this._onPasswordRequired(this, msg);
763 return false;
764 }
765
766 var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
767 String.fromCharCode(xvp_auth[1].length) +
768 xvp_auth[0] +
769 xvp_auth[1];
770 this._sock.send_string(xvp_auth_str);
771 this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
772 this._rfb_auth_scheme = 2;
773 return this._negotiate_authentication();
774 },
775
776 _negotiate_std_vnc_auth: function () {
777 if (this._rfb_password.length === 0) {
778 this._onPasswordRequired(this);
779 return false;
780 }
781
782 if (this._sock.rQwait("auth challenge", 16)) { return false; }
783
784 // TODO(directxman12): make genDES not require an Array
785 var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
786 var response = RFB.genDES(this._rfb_password, challenge);
787 this._sock.send(response);
788 this._rfb_init_state = "SecurityResult";
789 return true;
790 },
791
792 _negotiate_tight_tunnels: function (numTunnels) {
793 var clientSupportedTunnelTypes = {
794 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
795 };
796 var serverSupportedTunnelTypes = {};
797 // receive tunnel capabilities
798 for (var i = 0; i < numTunnels; i++) {
799 var cap_code = this._sock.rQshift32();
800 var cap_vendor = this._sock.rQshiftStr(4);
801 var cap_signature = this._sock.rQshiftStr(8);
802 serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
803 }
804
805 // choose the notunnel type
806 if (serverSupportedTunnelTypes[0]) {
807 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
808 serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
809 return this._fail("Client's tunnel type had the incorrect vendor or signature");
810 }
811 this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
812 return false; // wait until we receive the sub auth count to continue
813 } else {
814 return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
815 }
816 },
817
818 _negotiate_tight_auth: function () {
819 if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
820 if (this._sock.rQwait("num tunnels", 4)) { return false; }
821 var numTunnels = this._sock.rQshift32();
822 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
823
824 this._rfb_tightvnc = true;
825
826 if (numTunnels > 0) {
827 this._negotiate_tight_tunnels(numTunnels);
828 return false; // wait until we receive the sub auth to continue
829 }
830 }
831
832 // second pass, do the sub-auth negotiation
833 if (this._sock.rQwait("sub auth count", 4)) { return false; }
834 var subAuthCount = this._sock.rQshift32();
835 if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
836 this._updateState('SecurityResult');
837 return true;
838 }
839
840 if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
841
842 var clientSupportedTypes = {
843 'STDVNOAUTH__': 1,
844 'STDVVNCAUTH_': 2
845 };
846
847 var serverSupportedTypes = [];
848
849 for (var i = 0; i < subAuthCount; i++) {
850 var capNum = this._sock.rQshift32();
851 var capabilities = this._sock.rQshiftStr(12);
852 serverSupportedTypes.push(capabilities);
853 }
854
855 for (var authType in clientSupportedTypes) {
856 if (serverSupportedTypes.indexOf(authType) != -1) {
857 this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
858
859 switch (authType) {
860 case 'STDVNOAUTH__': // no auth
861 this._rfb_init_state = 'SecurityResult';
862 return true;
863 case 'STDVVNCAUTH_': // VNC auth
864 this._rfb_auth_scheme = 2;
865 return this._init_msg();
866 default:
867 return this._fail("Unsupported tiny auth scheme: " + authType);
868 }
869 }
870 }
871
872 return this._fail("No supported sub-auth types!");
873 },
874
875 _negotiate_authentication: function () {
876 switch (this._rfb_auth_scheme) {
877 case 0: // connection failed
878 if (this._sock.rQwait("auth reason", 4)) { return false; }
879 var strlen = this._sock.rQshift32();
880 var reason = this._sock.rQshiftStr(strlen);
881 return this._fail("Auth failure: " + reason);
882
883 case 1: // no auth
884 if (this._rfb_version >= 3.8) {
885 this._rfb_init_state = 'SecurityResult';
886 return true;
887 }
888 this._rfb_init_state = 'ClientInitialisation';
889 return this._init_msg();
890
891 case 22: // XVP auth
892 return this._negotiate_xvp_auth();
893
894 case 2: // VNC authentication
895 return this._negotiate_std_vnc_auth();
896
897 case 16: // TightVNC Security Type
898 return this._negotiate_tight_auth();
899
900 default:
901 return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
902 }
903 },
904
905 _handle_security_result: function () {
906 if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
907 switch (this._sock.rQshift32()) {
908 case 0: // OK
909 this._rfb_init_state = 'ClientInitialisation';
910 Util.Debug('Authentication OK');
911 return this._init_msg();
912 case 1: // failed
913 if (this._rfb_version >= 3.8) {
914 var length = this._sock.rQshift32();
915 if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
916 var reason = this._sock.rQshiftStr(length);
917 return this._fail(reason);
918 } else {
919 return this._fail("Authentication failure");
920 }
921 return false;
922 case 2:
923 return this._fail("Too many auth attempts");
924 default:
925 return this._fail("Unknown SecurityResult");
926 }
927 },
928
929 _negotiate_server_init: function () {
930 if (this._sock.rQwait("server initialization", 24)) { return false; }
931
932 /* Screen size */
933 this._fb_width = this._sock.rQshift16();
934 this._fb_height = this._sock.rQshift16();
935 this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
936
937 /* PIXEL_FORMAT */
938 var bpp = this._sock.rQshift8();
939 var depth = this._sock.rQshift8();
940 var big_endian = this._sock.rQshift8();
941 var true_color = this._sock.rQshift8();
942
943 var red_max = this._sock.rQshift16();
944 var green_max = this._sock.rQshift16();
945 var blue_max = this._sock.rQshift16();
946 var red_shift = this._sock.rQshift8();
947 var green_shift = this._sock.rQshift8();
948 var blue_shift = this._sock.rQshift8();
949 this._sock.rQskipBytes(3); // padding
950
951 // NB(directxman12): we don't want to call any callbacks or print messages until
952 // *after* we're past the point where we could backtrack
953
954 /* Connection name/title */
955 var name_length = this._sock.rQshift32();
956 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
957 this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
958
959 if (this._rfb_tightvnc) {
960 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
961 // In TightVNC mode, ServerInit message is extended
962 var numServerMessages = this._sock.rQshift16();
963 var numClientMessages = this._sock.rQshift16();
964 var numEncodings = this._sock.rQshift16();
965 this._sock.rQskipBytes(2); // padding
966
967 var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
968 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
969
970 // we don't actually do anything with the capability information that TIGHT sends,
971 // so we just skip the all of this.
972
973 // TIGHT server message capabilities
974 this._sock.rQskipBytes(16 * numServerMessages);
975
976 // TIGHT client message capabilities
977 this._sock.rQskipBytes(16 * numClientMessages);
978
979 // TIGHT encoding capabilities
980 this._sock.rQskipBytes(16 * numEncodings);
981 }
982
983 // NB(directxman12): these are down here so that we don't run them multiple times
984 // if we backtrack
985 Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
986 ", bpp: " + bpp + ", depth: " + depth +
987 ", big_endian: " + big_endian +
988 ", true_color: " + true_color +
989 ", red_max: " + red_max +
990 ", green_max: " + green_max +
991 ", blue_max: " + blue_max +
992 ", red_shift: " + red_shift +
993 ", green_shift: " + green_shift +
994 ", blue_shift: " + blue_shift);
995
996 if (big_endian !== 0) {
997 Util.Warn("Server native endian is not little endian");
998 }
999
1000 if (red_shift !== 16) {
1001 Util.Warn("Server native red-shift is not 16");
1002 }
1003
1004 if (blue_shift !== 0) {
1005 Util.Warn("Server native blue-shift is not 0");
1006 }
1007
1008 // we're past the point where we could backtrack, so it's safe to call this
1009 this._onDesktopName(this, this._fb_name);
1010
1011 if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
1012 Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
1013 this._true_color = false;
1014 }
1015
1016 this._display.set_true_color(this._true_color);
1017 this._display.resize(this._fb_width, this._fb_height);
1018 this._onFBResize(this, this._fb_width, this._fb_height);
1019
1020 if (!this._view_only) { this._keyboard.grab(); }
1021 if (!this._view_only) { this._mouse.grab(); }
1022
1023 if (this._true_color) {
1024 this._fb_Bpp = 4;
1025 this._fb_depth = 3;
1026 } else {
1027 this._fb_Bpp = 1;
1028 this._fb_depth = 1;
1029 }
1030
1031 RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color);
1032 RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color);
1033 RFB.messages.fbUpdateRequests(this._sock, false, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height);
1034
1035 this._timing.fbu_rt_start = (new Date()).getTime();
1036 this._timing.pixels = 0;
1037
1038 this._updateConnectionState('connected');
1039 return true;
1040 },
1041
1042 /* RFB protocol initialization states:
1043 * ProtocolVersion
1044 * Security
1045 * Authentication
1046 * SecurityResult
1047 * ClientInitialization - not triggered by server message
1048 * ServerInitialization
1049 */
1050 _init_msg: function () {
1051 switch (this._rfb_init_state) {
1052 case 'ProtocolVersion':
1053 return this._negotiate_protocol_version();
1054
1055 case 'Security':
1056 return this._negotiate_security();
1057
1058 case 'Authentication':
1059 return this._negotiate_authentication();
1060
1061 case 'SecurityResult':
1062 return this._handle_security_result();
1063
1064 case 'ClientInitialisation':
1065 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
1066 this._rfb_init_state = 'ServerInitialisation';
1067 return true;
1068
1069 case 'ServerInitialisation':
1070 return this._negotiate_server_init();
1071
1072 default:
1073 return this._fail("Unknown init state: " +
1074 this._rfb_init_state);
1075 }
1076 },
1077
1078 _handle_set_colour_map_msg: function () {
1079 Util.Debug("SetColorMapEntries");
1080 this._sock.rQskip8(); // Padding
1081
1082 var first_colour = this._sock.rQshift16();
1083 var num_colours = this._sock.rQshift16();
1084 if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
1085
1086 for (var c = 0; c < num_colours; c++) {
1087 var red = parseInt(this._sock.rQshift16() / 256, 10);
1088 var green = parseInt(this._sock.rQshift16() / 256, 10);
1089 var blue = parseInt(this._sock.rQshift16() / 256, 10);
1090 this._display.set_colourMap([blue, green, red], first_colour + c);
1091 }
1092 Util.Debug("colourMap: " + this._display.get_colourMap());
1093 Util.Info("Registered " + num_colours + " colourMap entries");
1094
1095 return true;
1096 },
1097
1098 _handle_server_cut_text: function () {
1099 Util.Debug("ServerCutText");
1100 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1101 this._sock.rQskipBytes(3); // Padding
1102 var length = this._sock.rQshift32();
1103 if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
1104
1105 var text = this._sock.rQshiftStr(length);
1106 this._onClipboard(this, text);
1107
1108 return true;
1109 },
1110
1111 _handle_server_fence_msg: function() {
1112 if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
1113 this._sock.rQskipBytes(3); // Padding
1114 var flags = this._sock.rQshift32();
1115 var length = this._sock.rQshift8();
1116
1117 if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
1118
1119 if (length > 64) {
1120 Util.Warn("Bad payload length (" + length + ") in fence response");
1121 length = 64;
1122 }
1123
1124 var payload = this._sock.rQshiftStr(length);
1125
1126 this._supportsFence = true;
1127
1128 /*
1129 * Fence flags
1130 *
1131 * (1<<0) - BlockBefore
1132 * (1<<1) - BlockAfter
1133 * (1<<2) - SyncNext
1134 * (1<<31) - Request
1135 */
1136
1137 if (!(flags & (1<<31))) {
1138 return this._fail("Unexpected fence response");
1139 }
1140
1141 // Filter out unsupported flags
1142 // FIXME: support syncNext
1143 flags &= (1<<0) | (1<<1);
1144
1145 // BlockBefore and BlockAfter are automatically handled by
1146 // the fact that we process each incoming message
1147 // synchronuosly.
1148 RFB.messages.clientFence(this._sock, flags, payload);
1149
1150 return true;
1151 },
1152
1153 _handle_xvp_msg: function () {
1154 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
1155 this._sock.rQskip8(); // Padding
1156 var xvp_ver = this._sock.rQshift8();
1157 var xvp_msg = this._sock.rQshift8();
1158
1159 switch (xvp_msg) {
1160 case 0: // XVP_FAIL
1161 Util.Error("Operation Failed");
1162 this._notification("XVP Operation Failed", 'error');
1163 break;
1164 case 1: // XVP_INIT
1165 this._rfb_xvp_ver = xvp_ver;
1166 Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
1167 this._onXvpInit(this._rfb_xvp_ver);
1168 break;
1169 default:
1170 this._fail("Disconnected: illegal server XVP message " + xvp_msg);
1171 break;
1172 }
1173
1174 return true;
1175 },
1176
1177 _normal_msg: function () {
1178 var msg_type;
1179
1180 if (this._FBU.rects > 0) {
1181 msg_type = 0;
1182 } else {
1183 msg_type = this._sock.rQshift8();
1184 }
1185
1186 switch (msg_type) {
1187 case 0: // FramebufferUpdate
1188 var ret = this._framebufferUpdate();
1189 if (ret) {
1190 RFB.messages.fbUpdateRequests(this._sock,
1191 this._enabledContinuousUpdates,
1192 this._display.getCleanDirtyReset(),
1193 this._fb_width, this._fb_height);
1194 }
1195 return ret;
1196
1197 case 1: // SetColorMapEntries
1198 return this._handle_set_colour_map_msg();
1199
1200 case 2: // Bell
1201 Util.Debug("Bell");
1202 this._onBell(this);
1203 return true;
1204
1205 case 3: // ServerCutText
1206 return this._handle_server_cut_text();
1207
1208 case 150: // EndOfContinuousUpdates
1209 var first = !(this._supportsContinuousUpdates);
1210 this._supportsContinuousUpdates = true;
1211 this._enabledContinuousUpdates = false;
1212 if (first) {
1213 this._enabledContinuousUpdates = true;
1214 this._updateContinuousUpdates();
1215 Util.Info("Enabling continuous updates.");
1216 } else {
1217 // FIXME: We need to send a framebufferupdaterequest here
1218 // if we add support for turning off continuous updates
1219 }
1220 return true;
1221
1222 case 248: // ServerFence
1223 return this._handle_server_fence_msg();
1224
1225 case 250: // XVP
1226 return this._handle_xvp_msg();
1227
1228 default:
1229 this._fail("Disconnected: illegal server message type " + msg_type);
1230 Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1231 return true;
1232 }
1233 },
1234
1235 _framebufferUpdate: function () {
1236 var ret = true;
1237 var now;
1238
1239 if (this._FBU.rects === 0) {
1240 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
1241 this._sock.rQskip8(); // Padding
1242 this._FBU.rects = this._sock.rQshift16();
1243 this._FBU.bytes = 0;
1244 this._timing.cur_fbu = 0;
1245 if (this._timing.fbu_rt_start > 0) {
1246 now = (new Date()).getTime();
1247 Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
1248 }
1249 }
1250
1251 while (this._FBU.rects > 0) {
1252 if (this._rfb_connection_state !== 'connected') { return false; }
1253
1254 if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
1255 if (this._FBU.bytes === 0) {
1256 if (this._sock.rQwait("rect header", 12)) { return false; }
1257 /* New FramebufferUpdate */
1258
1259 var hdr = this._sock.rQshiftBytes(12);
1260 this._FBU.x = (hdr[0] << 8) + hdr[1];
1261 this._FBU.y = (hdr[2] << 8) + hdr[3];
1262 this._FBU.width = (hdr[4] << 8) + hdr[5];
1263 this._FBU.height = (hdr[6] << 8) + hdr[7];
1264 this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1265 (hdr[10] << 8) + hdr[11], 10);
1266
1267 this._onFBUReceive(this,
1268 {'x': this._FBU.x, 'y': this._FBU.y,
1269 'width': this._FBU.width, 'height': this._FBU.height,
1270 'encoding': this._FBU.encoding,
1271 'encodingName': this._encNames[this._FBU.encoding]});
1272
1273 if (!this._encNames[this._FBU.encoding]) {
1274 this._fail("Disconnected: unsupported encoding " +
1275 this._FBU.encoding);
1276 return false;
1277 }
1278 }
1279
1280 this._timing.last_fbu = (new Date()).getTime();
1281
1282 ret = this._encHandlers[this._FBU.encoding]();
1283
1284 now = (new Date()).getTime();
1285 this._timing.cur_fbu += (now - this._timing.last_fbu);
1286
1287 if (ret) {
1288 this._encStats[this._FBU.encoding][0]++;
1289 this._encStats[this._FBU.encoding][1]++;
1290 this._timing.pixels += this._FBU.width * this._FBU.height;
1291 }
1292
1293 if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
1294 if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
1295 this._timing.fbu_rt_start > 0) {
1296 this._timing.full_fbu_total += this._timing.cur_fbu;
1297 this._timing.full_fbu_cnt++;
1298 Util.Info("Timing of full FBU, curr: " +
1299 this._timing.cur_fbu + ", total: " +
1300 this._timing.full_fbu_total + ", cnt: " +
1301 this._timing.full_fbu_cnt + ", avg: " +
1302 (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
1303 }
1304
1305 if (this._timing.fbu_rt_start > 0) {
1306 var fbu_rt_diff = now - this._timing.fbu_rt_start;
1307 this._timing.fbu_rt_total += fbu_rt_diff;
1308 this._timing.fbu_rt_cnt++;
1309 Util.Info("full FBU round-trip, cur: " +
1310 fbu_rt_diff + ", total: " +
1311 this._timing.fbu_rt_total + ", cnt: " +
1312 this._timing.fbu_rt_cnt + ", avg: " +
1313 (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
1314 this._timing.fbu_rt_start = 0;
1315 }
1316 }
1317
1318 if (!ret) { return ret; } // need more data
1319 }
1320
1321 this._onFBUComplete(this,
1322 {'x': this._FBU.x, 'y': this._FBU.y,
1323 'width': this._FBU.width, 'height': this._FBU.height,
1324 'encoding': this._FBU.encoding,
1325 'encodingName': this._encNames[this._FBU.encoding]});
1326
1327 return true; // We finished this FBU
1328 },
1329
1330 _updateContinuousUpdates: function() {
1331 if (!this._enabledContinuousUpdates) { return; }
1332
1333 RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
1334 this._fb_width, this._fb_height);
1335 }
1336 };
1337
1338 Util.make_properties(RFB, [
1339 ['target', 'wo', 'dom'], // VNC display rendering Canvas object
1340 ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
1341 ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
1342 ['true_color', 'rw', 'bool'], // Request true color pixel data
1343 ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
1344 ['shared', 'rw', 'bool'], // Request shared mode
1345 ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
1346 ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
1347 ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
1348 ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
1349 ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
1350 ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
1351
1352 // Callback functions
1353 ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change
1354 ['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI
1355 ['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished
1356 ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required
1357 ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
1358 ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
1359 ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
1360 ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
1361 ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
1362 ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
1363 ['onXvpInit', 'rw', 'func'] // onXvpInit(version): XVP extensions active for this connection
1364 ]);
1365
1366 RFB.prototype.set_local_cursor = function (cursor) {
1367 if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
1368 this._local_cursor = false;
1369 this._display.disableLocalCursor(); //Only show server-side cursor
1370 } else {
1371 if (this._display.get_cursor_uri()) {
1372 this._local_cursor = true;
1373 } else {
1374 Util.Warn("Browser does not support local cursor");
1375 this._display.disableLocalCursor();
1376 }
1377 }
1378 };
1379
1380 RFB.prototype.get_display = function () { return this._display; };
1381 RFB.prototype.get_keyboard = function () { return this._keyboard; };
1382 RFB.prototype.get_mouse = function () { return this._mouse; };
1383
1384 // Class Methods
1385 RFB.messages = {
1386 keyEvent: function (sock, keysym, down) {
1387 var buff = sock._sQ;
1388 var offset = sock._sQlen;
1389
1390 buff[offset] = 4; // msg-type
1391 buff[offset + 1] = down;
1392
1393 buff[offset + 2] = 0;
1394 buff[offset + 3] = 0;
1395
1396 buff[offset + 4] = (keysym >> 24);
1397 buff[offset + 5] = (keysym >> 16);
1398 buff[offset + 6] = (keysym >> 8);
1399 buff[offset + 7] = keysym;
1400
1401 sock._sQlen += 8;
1402 sock.flush();
1403 },
1404
1405 QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) {
1406 function getRFBkeycode(xt_scancode) {
1407 var upperByte = (keycode >> 8);
1408 var lowerByte = (keycode & 0x00ff);
1409 if (upperByte === 0xe0 && lowerByte < 0x7f) {
1410 lowerByte = lowerByte | 0x80;
1411 return lowerByte;
1412 }
1413 return xt_scancode;
1414 }
1415
1416 var buff = sock._sQ;
1417 var offset = sock._sQlen;
1418
1419 buff[offset] = 255; // msg-type
1420 buff[offset + 1] = 0; // sub msg-type
1421
1422 buff[offset + 2] = (down >> 8);
1423 buff[offset + 3] = down;
1424
1425 buff[offset + 4] = (keysym >> 24);
1426 buff[offset + 5] = (keysym >> 16);
1427 buff[offset + 6] = (keysym >> 8);
1428 buff[offset + 7] = keysym;
1429
1430 var RFBkeycode = getRFBkeycode(keycode);
1431
1432 buff[offset + 8] = (RFBkeycode >> 24);
1433 buff[offset + 9] = (RFBkeycode >> 16);
1434 buff[offset + 10] = (RFBkeycode >> 8);
1435 buff[offset + 11] = RFBkeycode;
1436
1437 sock._sQlen += 12;
1438 sock.flush();
1439 },
1440
1441 pointerEvent: function (sock, x, y, mask) {
1442 var buff = sock._sQ;
1443 var offset = sock._sQlen;
1444
1445 buff[offset] = 5; // msg-type
1446
1447 buff[offset + 1] = mask;
1448
1449 buff[offset + 2] = x >> 8;
1450 buff[offset + 3] = x;
1451
1452 buff[offset + 4] = y >> 8;
1453 buff[offset + 5] = y;
1454
1455 sock._sQlen += 6;
1456 sock.flush();
1457 },
1458
1459 // TODO(directxman12): make this unicode compatible?
1460 clientCutText: function (sock, text) {
1461 var buff = sock._sQ;
1462 var offset = sock._sQlen;
1463
1464 buff[offset] = 6; // msg-type
1465
1466 buff[offset + 1] = 0; // padding
1467 buff[offset + 2] = 0; // padding
1468 buff[offset + 3] = 0; // padding
1469
1470 var n = text.length;
1471
1472 buff[offset + 4] = n >> 24;
1473 buff[offset + 5] = n >> 16;
1474 buff[offset + 6] = n >> 8;
1475 buff[offset + 7] = n;
1476
1477 for (var i = 0; i < n; i++) {
1478 buff[offset + 8 + i] = text.charCodeAt(i);
1479 }
1480
1481 sock._sQlen += 8 + n;
1482 sock.flush();
1483 },
1484
1485 setDesktopSize: function (sock, width, height, id, flags) {
1486 var buff = sock._sQ;
1487 var offset = sock._sQlen;
1488
1489 buff[offset] = 251; // msg-type
1490 buff[offset + 1] = 0; // padding
1491 buff[offset + 2] = width >> 8; // width
1492 buff[offset + 3] = width;
1493 buff[offset + 4] = height >> 8; // height
1494 buff[offset + 5] = height;
1495
1496 buff[offset + 6] = 1; // number-of-screens
1497 buff[offset + 7] = 0; // padding
1498
1499 // screen array
1500 buff[offset + 8] = id >> 24; // id
1501 buff[offset + 9] = id >> 16;
1502 buff[offset + 10] = id >> 8;
1503 buff[offset + 11] = id;
1504 buff[offset + 12] = 0; // x-position
1505 buff[offset + 13] = 0;
1506 buff[offset + 14] = 0; // y-position
1507 buff[offset + 15] = 0;
1508 buff[offset + 16] = width >> 8; // width
1509 buff[offset + 17] = width;
1510 buff[offset + 18] = height >> 8; // height
1511 buff[offset + 19] = height;
1512 buff[offset + 20] = flags >> 24; // flags
1513 buff[offset + 21] = flags >> 16;
1514 buff[offset + 22] = flags >> 8;
1515 buff[offset + 23] = flags;
1516
1517 sock._sQlen += 24;
1518 sock.flush();
1519 },
1520
1521 clientFence: function (sock, flags, payload) {
1522 var buff = sock._sQ;
1523 var offset = sock._sQlen;
1524
1525 buff[offset] = 248; // msg-type
1526
1527 buff[offset + 1] = 0; // padding
1528 buff[offset + 2] = 0; // padding
1529 buff[offset + 3] = 0; // padding
1530
1531 buff[offset + 4] = flags >> 24; // flags
1532 buff[offset + 5] = flags >> 16;
1533 buff[offset + 6] = flags >> 8;
1534 buff[offset + 7] = flags;
1535
1536 var n = payload.length;
1537
1538 buff[offset + 8] = n; // length
1539
1540 for (var i = 0; i < n; i++) {
1541 buff[offset + 9 + i] = payload.charCodeAt(i);
1542 }
1543
1544 sock._sQlen += 9 + n;
1545 sock.flush();
1546 },
1547
1548 enableContinuousUpdates: function (sock, enable, x, y, width, height) {
1549 var buff = sock._sQ;
1550 var offset = sock._sQlen;
1551
1552 buff[offset] = 150; // msg-type
1553 buff[offset + 1] = enable; // enable-flag
1554
1555 buff[offset + 2] = x >> 8; // x
1556 buff[offset + 3] = x;
1557 buff[offset + 4] = y >> 8; // y
1558 buff[offset + 5] = y;
1559 buff[offset + 6] = width >> 8; // width
1560 buff[offset + 7] = width;
1561 buff[offset + 8] = height >> 8; // height
1562 buff[offset + 9] = height;
1563
1564 sock._sQlen += 10;
1565 sock.flush();
1566 },
1567
1568 pixelFormat: function (sock, bpp, depth, true_color) {
1569 var buff = sock._sQ;
1570 var offset = sock._sQlen;
1571
1572 buff[offset] = 0; // msg-type
1573
1574 buff[offset + 1] = 0; // padding
1575 buff[offset + 2] = 0; // padding
1576 buff[offset + 3] = 0; // padding
1577
1578 buff[offset + 4] = bpp * 8; // bits-per-pixel
1579 buff[offset + 5] = depth * 8; // depth
1580 buff[offset + 6] = 0; // little-endian
1581 buff[offset + 7] = true_color ? 1 : 0; // true-color
1582
1583 buff[offset + 8] = 0; // red-max
1584 buff[offset + 9] = 255; // red-max
1585
1586 buff[offset + 10] = 0; // green-max
1587 buff[offset + 11] = 255; // green-max
1588
1589 buff[offset + 12] = 0; // blue-max
1590 buff[offset + 13] = 255; // blue-max
1591
1592 buff[offset + 14] = 16; // red-shift
1593 buff[offset + 15] = 8; // green-shift
1594 buff[offset + 16] = 0; // blue-shift
1595
1596 buff[offset + 17] = 0; // padding
1597 buff[offset + 18] = 0; // padding
1598 buff[offset + 19] = 0; // padding
1599
1600 sock._sQlen += 20;
1601 sock.flush();
1602 },
1603
1604 clientEncodings: function (sock, encodings, local_cursor, true_color) {
1605 var buff = sock._sQ;
1606 var offset = sock._sQlen;
1607
1608 buff[offset] = 2; // msg-type
1609 buff[offset + 1] = 0; // padding
1610
1611 // offset + 2 and offset + 3 are encoding count
1612
1613 var i, j = offset + 4, cnt = 0;
1614 for (i = 0; i < encodings.length; i++) {
1615 if (encodings[i][0] === "Cursor" && !local_cursor) {
1616 Util.Debug("Skipping Cursor pseudo-encoding");
1617 } else if (encodings[i][0] === "TIGHT" && !true_color) {
1618 // TODO: remove this when we have tight+non-true-color
1619 Util.Warn("Skipping tight as it is only supported with true color");
1620 } else {
1621 var enc = encodings[i][1];
1622 buff[j] = enc >> 24;
1623 buff[j + 1] = enc >> 16;
1624 buff[j + 2] = enc >> 8;
1625 buff[j + 3] = enc;
1626
1627 j += 4;
1628 cnt++;
1629 }
1630 }
1631
1632 buff[offset + 2] = cnt >> 8;
1633 buff[offset + 3] = cnt;
1634
1635 sock._sQlen += j - offset;
1636 sock.flush();
1637 },
1638
1639 fbUpdateRequests: function (sock, onlyNonInc, cleanDirty, fb_width, fb_height) {
1640 var offsetIncrement = 0;
1641
1642 var cb = cleanDirty.cleanBox;
1643 var w, h;
1644 if (!onlyNonInc && (cb.w > 0 && cb.h > 0)) {
1645 w = typeof cb.w === "undefined" ? fb_width : cb.w;
1646 h = typeof cb.h === "undefined" ? fb_height : cb.h;
1647 // Request incremental for clean box
1648 RFB.messages.fbUpdateRequest(sock, 1, cb.x, cb.y, w, h);
1649 }
1650
1651 for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
1652 var db = cleanDirty.dirtyBoxes[i];
1653 // Force all (non-incremental) for dirty box
1654 w = typeof db.w === "undefined" ? fb_width : db.w;
1655 h = typeof db.h === "undefined" ? fb_height : db.h;
1656 RFB.messages.fbUpdateRequest(sock, 0, db.x, db.y, w, h);
1657 }
1658 },
1659
1660 fbUpdateRequest: function (sock, incremental, x, y, w, h) {
1661 var buff = sock._sQ;
1662 var offset = sock._sQlen;
1663
1664 if (typeof(x) === "undefined") { x = 0; }
1665 if (typeof(y) === "undefined") { y = 0; }
1666
1667 buff[offset] = 3; // msg-type
1668 buff[offset + 1] = incremental;
1669
1670 buff[offset + 2] = (x >> 8) & 0xFF;
1671 buff[offset + 3] = x & 0xFF;
1672
1673 buff[offset + 4] = (y >> 8) & 0xFF;
1674 buff[offset + 5] = y & 0xFF;
1675
1676 buff[offset + 6] = (w >> 8) & 0xFF;
1677 buff[offset + 7] = w & 0xFF;
1678
1679 buff[offset + 8] = (h >> 8) & 0xFF;
1680 buff[offset + 9] = h & 0xFF;
1681
1682 sock._sQlen += 10;
1683 sock.flush();
1684 }
1685 };
1686
1687 RFB.genDES = function (password, challenge) {
1688 var passwd = [];
1689 for (var i = 0; i < password.length; i++) {
1690 passwd.push(password.charCodeAt(i));
1691 }
1692 return (new DES(passwd)).encrypt(challenge);
1693 };
1694
1695 RFB.extract_data_uri = function (arr) {
1696 return ";base64," + Base64.encode(arr);
1697 };
1698
1699 RFB.encodingHandlers = {
1700 RAW: function () {
1701 if (this._FBU.lines === 0) {
1702 this._FBU.lines = this._FBU.height;
1703 }
1704
1705 this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
1706 if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
1707 var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
1708 var curr_height = Math.min(this._FBU.lines,
1709 Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
1710 this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
1711 curr_height, this._sock.get_rQ(),
1712 this._sock.get_rQi());
1713 this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
1714 this._FBU.lines -= curr_height;
1715
1716 if (this._FBU.lines > 0) {
1717 this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
1718 } else {
1719 this._FBU.rects--;
1720 this._FBU.bytes = 0;
1721 }
1722
1723 return true;
1724 },
1725
1726 COPYRECT: function () {
1727 this._FBU.bytes = 4;
1728 if (this._sock.rQwait("COPYRECT", 4)) { return false; }
1729 this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
1730 this._FBU.x, this._FBU.y, this._FBU.width,
1731 this._FBU.height);
1732
1733 this._FBU.rects--;
1734 this._FBU.bytes = 0;
1735 return true;
1736 },
1737
1738 RRE: function () {
1739 var color;
1740 if (this._FBU.subrects === 0) {
1741 this._FBU.bytes = 4 + this._fb_Bpp;
1742 if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
1743 this._FBU.subrects = this._sock.rQshift32();
1744 color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
1745 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
1746 }
1747
1748 while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
1749 color = this._sock.rQshiftBytes(this._fb_Bpp);
1750 var x = this._sock.rQshift16();
1751 var y = this._sock.rQshift16();
1752 var width = this._sock.rQshift16();
1753 var height = this._sock.rQshift16();
1754 this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
1755 this._FBU.subrects--;
1756 }
1757
1758 if (this._FBU.subrects > 0) {
1759 var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
1760 this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
1761 } else {
1762 this._FBU.rects--;
1763 this._FBU.bytes = 0;
1764 }
1765
1766 return true;
1767 },
1768
1769 HEXTILE: function () {
1770 var rQ = this._sock.get_rQ();
1771 var rQi = this._sock.get_rQi();
1772
1773 if (this._FBU.tiles === 0) {
1774 this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
1775 this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
1776 this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
1777 this._FBU.tiles = this._FBU.total_tiles;
1778 }
1779
1780 while (this._FBU.tiles > 0) {
1781 this._FBU.bytes = 1;
1782 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
1783 var subencoding = rQ[rQi]; // Peek
1784 if (subencoding > 30) { // Raw
1785 this._fail("Disconnected: illegal hextile subencoding " + subencoding);
1786 return false;
1787 }
1788
1789 var subrects = 0;
1790 var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
1791 var tile_x = curr_tile % this._FBU.tiles_x;
1792 var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
1793 var x = this._FBU.x + tile_x * 16;
1794 var y = this._FBU.y + tile_y * 16;
1795 var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
1796 var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
1797
1798 // Figure out how much we are expecting
1799 if (subencoding & 0x01) { // Raw
1800 this._FBU.bytes += w * h * this._fb_Bpp;
1801 } else {
1802 if (subencoding & 0x02) { // Background
1803 this._FBU.bytes += this._fb_Bpp;
1804 }
1805 if (subencoding & 0x04) { // Foreground
1806 this._FBU.bytes += this._fb_Bpp;
1807 }
1808 if (subencoding & 0x08) { // AnySubrects
1809 this._FBU.bytes++; // Since we aren't shifting it off
1810 if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
1811 subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
1812 if (subencoding & 0x10) { // SubrectsColoured
1813 this._FBU.bytes += subrects * (this._fb_Bpp + 2);
1814 } else {
1815 this._FBU.bytes += subrects * 2;
1816 }
1817 }
1818 }
1819
1820 if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
1821
1822 // We know the encoding and have a whole tile
1823 this._FBU.subencoding = rQ[rQi];
1824 rQi++;
1825 if (this._FBU.subencoding === 0) {
1826 if (this._FBU.lastsubencoding & 0x01) {
1827 // Weird: ignore blanks are RAW
1828 Util.Debug(" Ignoring blank after RAW");
1829 } else {
1830 this._display.fillRect(x, y, w, h, this._FBU.background);
1831 }
1832 } else if (this._FBU.subencoding & 0x01) { // Raw
1833 this._display.blitImage(x, y, w, h, rQ, rQi);
1834 rQi += this._FBU.bytes - 1;
1835 } else {
1836 if (this._FBU.subencoding & 0x02) { // Background
1837 if (this._fb_Bpp == 1) {
1838 this._FBU.background = rQ[rQi];
1839 } else {
1840 // fb_Bpp is 4
1841 this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1842 }
1843 rQi += this._fb_Bpp;
1844 }
1845 if (this._FBU.subencoding & 0x04) { // Foreground
1846 if (this._fb_Bpp == 1) {
1847 this._FBU.foreground = rQ[rQi];
1848 } else {
1849 // this._fb_Bpp is 4
1850 this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1851 }
1852 rQi += this._fb_Bpp;
1853 }
1854
1855 this._display.startTile(x, y, w, h, this._FBU.background);
1856 if (this._FBU.subencoding & 0x08) { // AnySubrects
1857 subrects = rQ[rQi];
1858 rQi++;
1859
1860 for (var s = 0; s < subrects; s++) {
1861 var color;
1862 if (this._FBU.subencoding & 0x10) { // SubrectsColoured
1863 if (this._fb_Bpp === 1) {
1864 color = rQ[rQi];
1865 } else {
1866 // _fb_Bpp is 4
1867 color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1868 }
1869 rQi += this._fb_Bpp;
1870 } else {
1871 color = this._FBU.foreground;
1872 }
1873 var xy = rQ[rQi];
1874 rQi++;
1875 var sx = (xy >> 4);
1876 var sy = (xy & 0x0f);
1877
1878 var wh = rQ[rQi];
1879 rQi++;
1880 var sw = (wh >> 4) + 1;
1881 var sh = (wh & 0x0f) + 1;
1882
1883 this._display.subTile(sx, sy, sw, sh, color);
1884 }
1885 }
1886 this._display.finishTile();
1887 }
1888 this._sock.set_rQi(rQi);
1889 this._FBU.lastsubencoding = this._FBU.subencoding;
1890 this._FBU.bytes = 0;
1891 this._FBU.tiles--;
1892 }
1893
1894 if (this._FBU.tiles === 0) {
1895 this._FBU.rects--;
1896 }
1897
1898 return true;
1899 },
1900
1901 getTightCLength: function (arr) {
1902 var header = 1, data = 0;
1903 data += arr[0] & 0x7f;
1904 if (arr[0] & 0x80) {
1905 header++;
1906 data += (arr[1] & 0x7f) << 7;
1907 if (arr[1] & 0x80) {
1908 header++;
1909 data += arr[2] << 14;
1910 }
1911 }
1912 return [header, data];
1913 },
1914
1915 display_tight: function (isTightPNG) {
1916 if (this._fb_depth === 1) {
1917 this._fail("Tight protocol handler only implements true color mode");
1918 }
1919
1920 this._FBU.bytes = 1; // compression-control byte
1921 if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
1922
1923 var checksum = function (data) {
1924 var sum = 0;
1925 for (var i = 0; i < data.length; i++) {
1926 sum += data[i];
1927 if (sum > 65536) sum -= 65536;
1928 }
1929 return sum;
1930 };
1931
1932 var resetStreams = 0;
1933 var streamId = -1;
1934 var decompress = function (data, expected) {
1935 for (var i = 0; i < 4; i++) {
1936 if ((resetStreams >> i) & 1) {
1937 this._FBU.zlibs[i].reset();
1938 Util.Info("Reset zlib stream " + i);
1939 }
1940 }
1941
1942 //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
1943 var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
1944 /*if (uncompressed.status !== 0) {
1945 Util.Error("Invalid data in zlib stream");
1946 }*/
1947
1948 //return uncompressed.data;
1949 return uncompressed;
1950 }.bind(this);
1951
1952 var indexedToRGBX2Color = function (data, palette, width, height) {
1953 // Convert indexed (palette based) image data to RGB
1954 // TODO: reduce number of calculations inside loop
1955 var dest = this._destBuff;
1956 var w = Math.floor((width + 7) / 8);
1957 var w1 = Math.floor(width / 8);
1958
1959 /*for (var y = 0; y < height; y++) {
1960 var b, x, dp, sp;
1961 var yoffset = y * width;
1962 var ybitoffset = y * w;
1963 var xoffset, targetbyte;
1964 for (x = 0; x < w1; x++) {
1965 xoffset = yoffset + x * 8;
1966 targetbyte = data[ybitoffset + x];
1967 for (b = 7; b >= 0; b--) {
1968 dp = (xoffset + 7 - b) * 3;
1969 sp = (targetbyte >> b & 1) * 3;
1970 dest[dp] = palette[sp];
1971 dest[dp + 1] = palette[sp + 1];
1972 dest[dp + 2] = palette[sp + 2];
1973 }
1974 }
1975
1976 xoffset = yoffset + x * 8;
1977 targetbyte = data[ybitoffset + x];
1978 for (b = 7; b >= 8 - width % 8; b--) {
1979 dp = (xoffset + 7 - b) * 3;
1980 sp = (targetbyte >> b & 1) * 3;
1981 dest[dp] = palette[sp];
1982 dest[dp + 1] = palette[sp + 1];
1983 dest[dp + 2] = palette[sp + 2];
1984 }
1985 }*/
1986
1987 for (var y = 0; y < height; y++) {
1988 var b, x, dp, sp;
1989 for (x = 0; x < w1; x++) {
1990 for (b = 7; b >= 0; b--) {
1991 dp = (y * width + x * 8 + 7 - b) * 4;
1992 sp = (data[y * w + x] >> b & 1) * 3;
1993 dest[dp] = palette[sp];
1994 dest[dp + 1] = palette[sp + 1];
1995 dest[dp + 2] = palette[sp + 2];
1996 dest[dp + 3] = 255;
1997 }
1998 }
1999
2000 for (b = 7; b >= 8 - width % 8; b--) {
2001 dp = (y * width + x * 8 + 7 - b) * 4;
2002 sp = (data[y * w + x] >> b & 1) * 3;
2003 dest[dp] = palette[sp];
2004 dest[dp + 1] = palette[sp + 1];
2005 dest[dp + 2] = palette[sp + 2];
2006 dest[dp + 3] = 255;
2007 }
2008 }
2009
2010 return dest;
2011 }.bind(this);
2012
2013 var indexedToRGBX = function (data, palette, width, height) {
2014 // Convert indexed (palette based) image data to RGB
2015 var dest = this._destBuff;
2016 var total = width * height * 4;
2017 for (var i = 0, j = 0; i < total; i += 4, j++) {
2018 var sp = data[j] * 3;
2019 dest[i] = palette[sp];
2020 dest[i + 1] = palette[sp + 1];
2021 dest[i + 2] = palette[sp + 2];
2022 dest[i + 3] = 255;
2023 }
2024
2025 return dest;
2026 }.bind(this);
2027
2028 var rQi = this._sock.get_rQi();
2029 var rQ = this._sock.rQwhole();
2030 var cmode, data;
2031 var cl_header, cl_data;
2032
2033 var handlePalette = function () {
2034 var numColors = rQ[rQi + 2] + 1;
2035 var paletteSize = numColors * this._fb_depth;
2036 this._FBU.bytes += paletteSize;
2037 if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
2038
2039 var bpp = (numColors <= 2) ? 1 : 8;
2040 var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
2041 var raw = false;
2042 if (rowSize * this._FBU.height < 12) {
2043 raw = true;
2044 cl_header = 0;
2045 cl_data = rowSize * this._FBU.height;
2046 //clength = [0, rowSize * this._FBU.height];
2047 } else {
2048 // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
2049 var cl_offset = rQi + 3 + paletteSize;
2050 cl_header = 1;
2051 cl_data = 0;
2052 cl_data += rQ[cl_offset] & 0x7f;
2053 if (rQ[cl_offset] & 0x80) {
2054 cl_header++;
2055 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2056 if (rQ[cl_offset + 1] & 0x80) {
2057 cl_header++;
2058 cl_data += rQ[cl_offset + 2] << 14;
2059 }
2060 }
2061 // end inline getTightCLength
2062 }
2063
2064 this._FBU.bytes += cl_header + cl_data;
2065 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2066
2067 // Shift ctl, filter id, num colors, palette entries, and clength off
2068 this._sock.rQskipBytes(3);
2069 //var palette = this._sock.rQshiftBytes(paletteSize);
2070 this._sock.rQshiftTo(this._paletteBuff, paletteSize);
2071 this._sock.rQskipBytes(cl_header);
2072
2073 if (raw) {
2074 data = this._sock.rQshiftBytes(cl_data);
2075 } else {
2076 data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
2077 }
2078
2079 // Convert indexed (palette based) image data to RGB
2080 var rgbx;
2081 if (numColors == 2) {
2082 rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
2083 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
2084 } else {
2085 rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
2086 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
2087 }
2088
2089
2090 return true;
2091 }.bind(this);
2092
2093 var handleCopy = function () {
2094 var raw = false;
2095 var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
2096 if (uncompressedSize < 12) {
2097 raw = true;
2098 cl_header = 0;
2099 cl_data = uncompressedSize;
2100 } else {
2101 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
2102 var cl_offset = rQi + 1;
2103 cl_header = 1;
2104 cl_data = 0;
2105 cl_data += rQ[cl_offset] & 0x7f;
2106 if (rQ[cl_offset] & 0x80) {
2107 cl_header++;
2108 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2109 if (rQ[cl_offset + 1] & 0x80) {
2110 cl_header++;
2111 cl_data += rQ[cl_offset + 2] << 14;
2112 }
2113 }
2114 // end inline getTightCLength
2115 }
2116 this._FBU.bytes = 1 + cl_header + cl_data;
2117 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2118
2119 // Shift ctl, clength off
2120 this._sock.rQshiftBytes(1 + cl_header);
2121
2122 if (raw) {
2123 data = this._sock.rQshiftBytes(cl_data);
2124 } else {
2125 data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
2126 }
2127
2128 this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
2129
2130 return true;
2131 }.bind(this);
2132
2133 var ctl = this._sock.rQpeek8();
2134
2135 // Keep tight reset bits
2136 resetStreams = ctl & 0xF;
2137
2138 // Figure out filter
2139 ctl = ctl >> 4;
2140 streamId = ctl & 0x3;
2141
2142 if (ctl === 0x08) cmode = "fill";
2143 else if (ctl === 0x09) cmode = "jpeg";
2144 else if (ctl === 0x0A) cmode = "png";
2145 else if (ctl & 0x04) cmode = "filter";
2146 else if (ctl < 0x04) cmode = "copy";
2147 else return this._fail("Illegal tight compression received, ctl: " + ctl);
2148
2149 if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
2150 return this._fail("filter/copy received in tightPNG mode");
2151 }
2152
2153 switch (cmode) {
2154 // fill use fb_depth because TPIXELs drop the padding byte
2155 case "fill": // TPIXEL
2156 this._FBU.bytes += this._fb_depth;
2157 break;
2158 case "jpeg": // max clength
2159 this._FBU.bytes += 3;
2160 break;
2161 case "png": // max clength
2162 this._FBU.bytes += 3;
2163 break;
2164 case "filter": // filter id + num colors if palette
2165 this._FBU.bytes += 2;
2166 break;
2167 case "copy":
2168 break;
2169 }
2170
2171 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2172
2173 // Determine FBU.bytes
2174 switch (cmode) {
2175 case "fill":
2176 // skip ctl byte
2177 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false);
2178 this._sock.rQskipBytes(4);
2179 break;
2180 case "png":
2181 case "jpeg":
2182 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
2183 var cl_offset = rQi + 1;
2184 cl_header = 1;
2185 cl_data = 0;
2186 cl_data += rQ[cl_offset] & 0x7f;
2187 if (rQ[cl_offset] & 0x80) {
2188 cl_header++;
2189 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2190 if (rQ[cl_offset + 1] & 0x80) {
2191 cl_header++;
2192 cl_data += rQ[cl_offset + 2] << 14;
2193 }
2194 }
2195 // end inline getTightCLength
2196 this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
2197 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
2198
2199 // We have everything, render it
2200 this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
2201 var img = new Image();
2202 img.src = "data: image/" + cmode +
2203 RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data));
2204 this._display.renderQ_push({
2205 'type': 'img',
2206 'img': img,
2207 'x': this._FBU.x,
2208 'y': this._FBU.y
2209 });
2210 img = null;
2211 break;
2212 case "filter":
2213 var filterId = rQ[rQi + 1];
2214 if (filterId === 1) {
2215 if (!handlePalette()) { return false; }
2216 } else {
2217 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
2218 // Filter 2, Gradient is valid but not use if jpeg is enabled
2219 this._fail("Unsupported tight subencoding received, filter: " + filterId);
2220 }
2221 break;
2222 case "copy":
2223 if (!handleCopy()) { return false; }
2224 break;
2225 }
2226
2227
2228 this._FBU.bytes = 0;
2229 this._FBU.rects--;
2230
2231 return true;
2232 },
2233
2234 TIGHT: function () { return this._encHandlers.display_tight(false); },
2235 TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
2236
2237 last_rect: function () {
2238 this._FBU.rects = 0;
2239 return true;
2240 },
2241
2242 handle_FB_resize: function () {
2243 this._fb_width = this._FBU.width;
2244 this._fb_height = this._FBU.height;
2245 this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
2246 this._display.resize(this._fb_width, this._fb_height);
2247 this._onFBResize(this, this._fb_width, this._fb_height);
2248 this._timing.fbu_rt_start = (new Date()).getTime();
2249 this._updateContinuousUpdates();
2250
2251 this._FBU.bytes = 0;
2252 this._FBU.rects -= 1;
2253 return true;
2254 },
2255
2256 ExtendedDesktopSize: function () {
2257 this._FBU.bytes = 1;
2258 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
2259
2260 this._supportsSetDesktopSize = true;
2261 var number_of_screens = this._sock.rQpeek8();
2262
2263 this._FBU.bytes = 4 + (number_of_screens * 16);
2264 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
2265
2266 this._sock.rQskipBytes(1); // number-of-screens
2267 this._sock.rQskipBytes(3); // padding
2268
2269 for (var i = 0; i < number_of_screens; i += 1) {
2270 // Save the id and flags of the first screen
2271 if (i === 0) {
2272 this._screen_id = this._sock.rQshiftBytes(4); // id
2273 this._sock.rQskipBytes(2); // x-position
2274 this._sock.rQskipBytes(2); // y-position
2275 this._sock.rQskipBytes(2); // width
2276 this._sock.rQskipBytes(2); // height
2277 this._screen_flags = this._sock.rQshiftBytes(4); // flags
2278 } else {
2279 this._sock.rQskipBytes(16);
2280 }
2281 }
2282
2283 /*
2284 * The x-position indicates the reason for the change:
2285 *
2286 * 0 - server resized on its own
2287 * 1 - this client requested the resize
2288 * 2 - another client requested the resize
2289 */
2290
2291 // We need to handle errors when we requested the resize.
2292 if (this._FBU.x === 1 && this._FBU.y !== 0) {
2293 var msg = "";
2294 // The y-position indicates the status code from the server
2295 switch (this._FBU.y) {
2296 case 1:
2297 msg = "Resize is administratively prohibited";
2298 break;
2299 case 2:
2300 msg = "Out of resources";
2301 break;
2302 case 3:
2303 msg = "Invalid screen layout";
2304 break;
2305 default:
2306 msg = "Unknown reason";
2307 break;
2308 }
2309 this._notification("Server did not accept the resize request: "
2310 + msg, 'normal');
2311 return true;
2312 }
2313
2314 this._encHandlers.handle_FB_resize();
2315 return true;
2316 },
2317
2318 DesktopSize: function () {
2319 this._encHandlers.handle_FB_resize();
2320 return true;
2321 },
2322
2323 Cursor: function () {
2324 Util.Debug(">> set_cursor");
2325 var x = this._FBU.x; // hotspot-x
2326 var y = this._FBU.y; // hotspot-y
2327 var w = this._FBU.width;
2328 var h = this._FBU.height;
2329
2330 var pixelslength = w * h * this._fb_Bpp;
2331 var masklength = Math.floor((w + 7) / 8) * h;
2332
2333 this._FBU.bytes = pixelslength + masklength;
2334 if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
2335
2336 this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
2337 this._sock.rQshiftBytes(masklength),
2338 x, y, w, h);
2339
2340 this._FBU.bytes = 0;
2341 this._FBU.rects--;
2342
2343 Util.Debug("<< set_cursor");
2344 return true;
2345 },
2346
2347 QEMUExtendedKeyEvent: function () {
2348 this._FBU.rects--;
2349
2350 var keyboardEvent = document.createEvent("keyboardEvent");
2351 if (keyboardEvent.code !== undefined) {
2352 this._qemuExtKeyEventSupported = true;
2353 this._keyboard.setQEMUVNCKeyboardHandler();
2354 }
2355 },
2356
2357 JPEG_quality_lo: function () {
2358 Util.Error("Server sent jpeg_quality pseudo-encoding");
2359 },
2360
2361 compress_lo: function () {
2362 Util.Error("Server sent compress level pseudo-encoding");
2363 }
2364 };
2365 })();