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