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