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