]> git.proxmox.com Git - mirror_novnc.git/blame - include/vnc.js
Opera fixes and big Opera performance boost.
[mirror_novnc.git] / include / vnc.js
CommitLineData
97bfe5ba 1/*
71d2426a 2 * noVNC: HTML5 VNC client
af6b17ce
JM
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.LGPL-3)
ded9dfae
JM
5 *
6 * See README.md for usage and integration instructions.
97bfe5ba 7 */
c4164bda 8
15046f00
JM
9"use strict";
10/*jslint white: false, nomen: false, browser: true, bitwise: false */
11/*global window, console, WebSocket, Util, Canvas, VNC_uri_prefix, Base64, DES */
c4164bda
JM
12
13// Globals defined here
56ec48be 14var VNC_native_ws, RFB;
97bfe5ba 15
97bfe5ba
JM
16/*
17 * Load supporting scripts
18 */
15046f00
JM
19function get_VNC_uri_prefix() {
20 return (typeof VNC_uri_prefix !== "undefined") ? VNC_uri_prefix : "include/";
21}
22
c4164bda 23(function () {
15046f00 24 var extra = "", start, end;
91308399 25
15046f00 26 start = "<script src='" + get_VNC_uri_prefix();
c4164bda
JM
27 end = "'><\/script>";
28
29 // Uncomment to activate firebug lite
3915e536
JM
30 //extra += "<script src='http://getfirebug.com/releases/lite/1.2/" +
31 // "firebug-lite-compressed.js'><\/script>";
c4164bda 32
15046f00 33 extra += start + "util.js" + end;
96a6eaad
JM
34 extra += start + "base64.js" + end;
35 extra += start + "des.js" + end;
96a6eaad 36 extra += start + "canvas.js" + end;
c4164bda
JM
37
38 /* If no builtin websockets then load web_socket.js */
39 if (window.WebSocket) {
40 VNC_native_ws = true;
41 } else {
42 VNC_native_ws = false;
96a6eaad
JM
43 extra += start + "web-socket-js/swfobject.js" + end;
44 extra += start + "web-socket-js/FABridge.js" + end;
45 extra += start + "web-socket-js/web_socket.js" + end;
c4164bda
JM
46 }
47 document.write(extra);
48}());
97bfe5ba 49
65e27ddd 50/*
cc0410a3 51 * RFB namespace
65e27ddd
JM
52 */
53
64ab5c4d
JM
54RFB = {
55
91308399
JM
56/*
57 * External interface variables and methods
58 */
6a52558d
JM
59host : '',
60port : 5900,
61password : '',
62encrypt : true,
63true_color : false,
64
65b64encode : true, // false means UTF-8 on the wire
66//b64encode : false, // false means UTF-8 on the wire
dfa8db8f 67connectTimeout : 2000, // time to wait for connection
6a52558d 68
91308399 69
8a837006 70// In preference order
29cb15f9 71encodings : [
4b4496ad
JM
72 ['COPYRECT', 0x01, 'display_copy_rect'],
73 ['TIGHT_PNG', 0x17, 'display_tight_png'],
74 ['HEXTILE', 0x05, 'display_hextile'],
75 ['RRE', 0x02, 'display_rre'],
76 ['RAW', 0x00, 'display_raw'],
77 ['DesktopSize', -223, 'set_desktopsize'],
78
79 // Psuedo-encoding settings
80 ['JPEG_quality_lo', -32, 'set_jpeg_quality'],
81// ['JPEG_quality_hi', -23, 'set_jpeg_quality'],
82 ['compress_lo', -255, 'set_compress_level']
83// ['compress_hi', -247, 'set_compress_level']
84 ],
29cb15f9 85
91308399
JM
86setUpdateState: function(externalUpdateState) {
87 RFB.externalUpdateState = externalUpdateState;
88},
89
90setClipboardReceive: function(clipReceive) {
91 RFB.clipboardCopyTo = clipReceive;
92},
93
94setCanvasID: function(canvasID) {
95 RFB.canvasID = canvasID;
96},
97
63708ff5 98sendPassword: function(passwd) {
91308399
JM
99 RFB.password = passwd;
100 RFB.state = "Authentication";
101 setTimeout(RFB.init_msg, 1);
102},
103
63708ff5
JM
104sendCtrlAltDel: function() {
105 if (RFB.state !== "normal") { return false; }
106 console.log("Sending Ctrl-Alt-Del");
107 var arr = [];
108 arr = arr.concat(RFB.keyEvent(0xFFE3, 1)); // Control
109 arr = arr.concat(RFB.keyEvent(0xFFE9, 1)); // Alt
110 arr = arr.concat(RFB.keyEvent(0xFFFF, 1)); // Delete
111 arr = arr.concat(RFB.keyEvent(0xFFFF, 0)); // Delete
112 arr = arr.concat(RFB.keyEvent(0xFFE9, 0)); // Alt
113 arr = arr.concat(RFB.keyEvent(0xFFE3, 0)); // Control
114 arr = arr.concat(RFB.fbUpdateRequest(1));
115 RFB.send_array(arr);
116},
117
91308399 118load: function () {
8a837006 119 var i;
91308399
JM
120 //console.log(">> load");
121
122 /* Load web-socket-js if no builtin WebSocket support */
123 if (VNC_native_ws) {
124 console.log("Using native WebSockets");
125 RFB.updateState('disconnected', 'Disconnected');
126 } else {
30e53963 127 console.warn("Using web-socket-js flash bridge");
15046f00
JM
128 if ((! Util.Flash) ||
129 (Util.Flash.version < 9)) {
91308399 130 RFB.updateState('failed', "WebSockets or Adobe Flash is required");
15046f00 131 } else if (document.location.href.substr(0, 7) === "file://") {
91308399
JM
132 RFB.updateState('failed',
133 "'file://' URL is incompatible with Adobe Flash");
134 } else {
15046f00 135 WebSocket.__swfLocation = get_VNC_uri_prefix() +
96a6eaad 136 "web-socket-js/WebSocketMain.swf";
91308399
JM
137 WebSocket.__initialize();
138 RFB.use_seq = true;
139 RFB.updateState('disconnected', 'Disconnected');
140 }
141 }
142
d93d3e09
JM
143 // Initialize canvas/fxcanvas
144 Canvas.init(RFB.canvasID);
145
8a837006
JM
146 // Populate encoding lookup tables
147 RFB.encHandlers = {};
148 RFB.encNames = {};
15046f00 149 for (i=0; i < RFB.encodings.length; i+=1) {
8a837006
JM
150 RFB.encHandlers[RFB.encodings[i][1]] = RFB[RFB.encodings[i][2]];
151 RFB.encNames[RFB.encodings[i][1]] = RFB.encodings[i][0];
152 }
4b4496ad
JM
153 RFB.encHandlers[0x07] = RFB.display_tight_png;
154 RFB.encNames[0x07] = 'TIGHT';
91308399
JM
155 //console.log("<< load");
156},
157
158connect: function (host, port, password, encrypt, true_color) {
30e53963 159 //console.log(">> connect");
91308399
JM
160
161 RFB.host = host;
162 RFB.port = port;
163 RFB.password = (password !== undefined) ? password : "";
164 RFB.encrypt = (encrypt !== undefined) ? encrypt : true;
165 if ((RFB.encrypt === "0") ||
166 (RFB.encrypt === "no") ||
167 (RFB.encrypt === "false")) {
168 RFB.encrypt = false;
169 }
170 RFB.true_color = (true_color !== undefined) ? true_color: true;
171 if ((RFB.true_color === "0") ||
172 (RFB.true_color === "no") ||
173 (RFB.true_color === "false")) {
174 RFB.true_color = false;
175 }
176
177 if ((!RFB.host) || (!RFB.port)) {
15046f00 178 RFB.updateState('disconnected', "Must set host and port");
91308399
JM
179 return;
180 }
181
182 RFB.init_vars();
183
184 if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) {
185 RFB.ws.close();
186 }
187 RFB.init_ws();
188
189 RFB.updateState('ProtocolVersion');
30e53963 190 //console.log("<< connect");
91308399
JM
191
192},
193
194disconnect: function () {
30e53963 195 //console.log(">> disconnect");
91308399 196 if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) {
bc8e3d4d 197 RFB.ws.close();
91308399
JM
198 RFB.updateState('closed');
199 RFB.ws.onmessage = function (e) { return; };
91308399
JM
200 }
201 if (Canvas.ctx) {
202 Canvas.stop();
203 if (! /__debug__$/i.test(document.location.href)) {
204 Canvas.clear();
205 }
206 }
207
208 RFB.updateState('disconnected', 'Disconnected');
30e53963 209 //console.log("<< disconnect");
91308399
JM
210},
211
212clipboardPasteFrom: function (text) {
213 if (RFB.state !== "normal") { return; }
214 //console.log(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
215 RFB.send_array(RFB.clientCutText(text));
216 //console.log("<< clipboardPasteFrom");
217},
218
219
220/*
221 * Private variables and methods
222 */
223
56ec48be
JM
224ws : null, // Web Socket object
225sendID : null,
6a52558d 226scanID : null, // TIGHT_PNG render image scanner
56ec48be
JM
227use_seq : false,
228
229// Receive and send queues
230RQ : [], // Receive Queue
231RQ_reorder : [], // Receive Queue re-order list
232RQ_seq_num : 0, // Expected sequence number
233SQ : "", // Send Queue
234
8a837006
JM
235encHandlers : {},
236encNames : {},
237
56ec48be
JM
238// Frame buffer update state
239FBU : {
240 rects : 0,
241 subrects : 0, // RRE and HEXTILE
242 lines : 0, // RAW
243 tiles : 0, // HEXTILE
244 bytes : 0,
245 x : 0,
246 y : 0,
247 width : 0,
248 height : 0,
249 encoding : 0,
250 subencoding : -1,
6a52558d
JM
251 background : null,
252 imgs : [] // TIGHT_PNG image queue
56ec48be 253},
cc0410a3 254
d41c33e4
JM
255fb_Bpp : 4,
256fb_depth : 3,
257
56ec48be
JM
258max_version : 3.8,
259version : 0,
260auth_scheme : '',
261state : 'disconnected',
262cuttext : 'none', // ServerCutText wait state
263ct_length : 0,
30059bdf 264
56ec48be
JM
265shared : 1,
266check_rate : 217,
6a52558d 267scan_imgs_rate : 100,
56ec48be
JM
268req_rate : 1413,
269last_req : 0,
64ab5c4d 270
91308399 271canvasID : 'VNC_canvas',
56ec48be
JM
272fb_width : 0,
273fb_height : 0,
274fb_name : "",
56ec48be 275rre_chunk : 100,
cc0410a3 276
56ec48be
JM
277timing : {
278 last_fbu : 0,
279 fbu_total : 0,
753bde8f
JM
280 fbu_total_cnt : 0,
281 full_fbu_total : 0,
c3996e24
JM
282 full_fbu_cnt : 0,
283
284 fbu_rt_start : 0,
285 fbu_rt_total : 0,
286 fbu_rt_cnt : 0,
2152a88f
JM
287
288 history : [],
289 history_start : 0,
290 h_time : 0,
291 h_rects : 0,
292 h_fbus : 0,
293 h_bytes : 0,
294 h_pixels : 0
753bde8f
JM
295},
296
97bfe5ba
JM
297/* Mouse state */
298mouse_buttonmask : 0,
299mouse_arr : [],
300
cc0410a3
JM
301/*
302 * Server message handlers
303 */
304
65e27ddd 305/* RFB/VNC initialisation */
484a4696 306init_msg: function () {
30e53963 307 //console.log(">> init_msg [RFB.state '" + RFB.state + "']");
ef764d3b 308
dfa8db8f
JM
309 var RQ = RFB.RQ, strlen, reason, reason_len, sversion, cversion,
310 i, types, num_types, challenge, response, bpp, depth,
311 big_endian, true_color, name_length;
c4164bda 312
56ec48be 313 //console.log("RQ (" + RQ.length + ") " + RQ);
64ab5c4d 314 switch (RFB.state) {
65e27ddd
JM
315
316 case 'ProtocolVersion' :
ef764d3b 317 if (RQ.length < 12) {
c4164bda
JM
318 RFB.updateState('failed',
319 "Disconnected: incomplete protocol version");
ef764d3b
JM
320 return;
321 }
67b24a90
JM
322 sversion = RQ.shiftStr(12).substr(4,7);
323 console.log("Server ProtocolVersion: " + sversion);
324 switch (sversion) {
325 case "003.003": RFB.version = 3.3; break;
326 case "003.007": RFB.version = 3.7; break;
327 case "003.008": RFB.version = 3.8; break;
328 default:
329 RFB.updateState('failed',
330 "Invalid server version " + sversion);
331 return;
332 }
333 if (RFB.version > RFB.max_version) {
334 RFB.version = RFB.max_version;
65e27ddd 335 }
67b24a90
JM
336
337 cversion = "00" + parseInt(RFB.version,10) +
338 ".00" + ((RFB.version * 10) % 10);
339 RFB.send_string("RFB " + cversion + "\n");
340 RFB.updateState('Security', "Sent ProtocolVersion: " + sversion);
65e27ddd
JM
341 break;
342
ef764d3b 343 case 'Security' :
67b24a90 344 if (RFB.version >= 3.7) {
c4164bda
JM
345 num_types = RQ.shift8();
346 if (num_types === 0) {
347 strlen = RQ.shift32();
348 reason = RQ.shiftStr(strlen);
349 RFB.updateState('failed',
350 "Disconnected: security failure: " + reason);
ef764d3b
JM
351 return;
352 }
dfa8db8f 353 RFB.auth_scheme = 0;
c4164bda 354 types = RQ.shiftBytes(num_types);
dfa8db8f
JM
355 for (i=0; i < types.length; i+=1) {
356 if ((types[i] > RFB.auth_scheme) && (types[i] < 3)) {
357 RFB.auth_scheme = types[i];
358 }
359 }
360 if (RFB.auth_scheme === 0) {
c4164bda 361 RFB.updateState('failed',
dfa8db8f 362 "Disconnected: unsupported security types: " + types);
ef764d3b
JM
363 return;
364 }
dfa8db8f 365
ef764d3b 366 RFB.send_array([RFB.auth_scheme]);
67b24a90 367 } else {
c539e4dc
JM
368 if (RQ.length < 4) {
369 RFB.updateState('failed', "Invalid security frame");
370 return;
371 }
372 RFB.auth_scheme = RQ.shift32();
65e27ddd 373 }
c4164bda
JM
374 RFB.updateState('Authentication',
375 "Authenticating using scheme: " + RFB.auth_scheme);
ef764d3b
JM
376 // Fall through
377
378 case 'Authentication' :
30e53963 379 //console.log("Security auth scheme: " + RFB.auth_scheme);
ef764d3b 380 switch (RFB.auth_scheme) {
65e27ddd 381 case 0: // connection failed
ef764d3b 382 if (RQ.length < 4) {
30e53963 383 //console.log(" waiting for auth reason bytes");
ef764d3b
JM
384 return;
385 }
c4164bda
JM
386 strlen = RQ.shift32();
387 reason = RQ.shiftStr(strlen);
388 RFB.updateState('failed',
389 "Disconnected: auth failure: " + reason);
65e27ddd
JM
390 return;
391 case 1: // no authentication
2cec49d4
KC
392 // RFB.send_array([RFB.shared]); // ClientInitialisation
393 RFB.updateState('SecurityResult');
65e27ddd
JM
394 break;
395 case 2: // VNC authentication
91308399
JM
396 if (RFB.password.length === 0) {
397 RFB.updateState('password', "Password Required");
398 return;
399 }
ef764d3b 400 if (RQ.length < 16) {
30e53963 401 //console.log(" waiting for auth challenge bytes");
ef764d3b
JM
402 return;
403 }
c4164bda 404 challenge = RQ.shiftBytes(16);
753bde8f 405 //console.log("Password: " + RFB.password);
c4164bda
JM
406 //console.log("Challenge: " + challenge +
407 // " (" + challenge.length + ")");
408 response = RFB.DES(RFB.password, challenge);
409 //console.log("Response: " + response +
410 // " (" + response.length + ")");
753bde8f 411
30e53963 412 //console.log("Sending DES encrypted auth response");
8580b989 413 RFB.send_array(response);
8759ea6f 414 RFB.updateState('SecurityResult');
65e27ddd 415 break;
484a4696 416 default:
c4164bda
JM
417 RFB.updateState('failed',
418 "Disconnected: unsupported auth scheme: " +
419 RFB.auth_scheme);
484a4696 420 return;
65e27ddd
JM
421 }
422 break;
423
64ab5c4d 424 case 'SecurityResult' :
c539e4dc
JM
425 if (RQ.length < 4) {
426 RFB.updateState('failed', "Invalid VNC auth response");
65e27ddd
JM
427 return;
428 }
c4164bda 429 switch (RQ.shift32()) {
65e27ddd 430 case 0: // OK
8759ea6f 431 RFB.updateState('ServerInitialisation', "Authentication OK");
65e27ddd
JM
432 break;
433 case 1: // failed
67b24a90 434 if (RFB.version >= 3.8) {
c4164bda
JM
435 reason_len = RQ.shift32();
436 reason = RQ.shiftStr(reason_len);
c539e4dc 437 RFB.updateState('failed', reason);
67b24a90 438 } else {
c539e4dc
JM
439 RFB.updateState('failed', "Authentication failed");
440 }
65e27ddd
JM
441 return;
442 case 2: // too-many
c4164bda
JM
443 RFB.updateState('failed',
444 "Disconnected: too many auth attempts");
65e27ddd
JM
445 return;
446 }
64ab5c4d 447 RFB.send_array([RFB.shared]); // ClientInitialisation
65e27ddd
JM
448 break;
449
450 case 'ServerInitialisation' :
5d8e7ec0 451 if (RQ.length < 24) {
c539e4dc 452 RFB.updateState('failed', "Invalid server initialisation");
65e27ddd
JM
453 return;
454 }
489d1676
JM
455
456 /* Screen size */
5d8e7ec0
JM
457 RFB.fb_width = RQ.shift16();
458 RFB.fb_height = RQ.shift16();
489d1676 459
489d1676 460 /* PIXEL_FORMAT */
c4164bda
JM
461 bpp = RQ.shift8();
462 depth = RQ.shift8();
463 big_endian = RQ.shift8();
464 true_color = RQ.shift8();
489d1676 465
753bde8f
JM
466 console.log("Screen: " + RFB.fb_width + "x" + RFB.fb_height +
467 ", bpp: " + bpp + ", depth: " + depth +
468 ", big_endian: " + big_endian +
469 ", true_color: " + true_color);
489d1676
JM
470
471 /* Connection name/title */
5d8e7ec0 472 RQ.shiftStr(12);
c4164bda 473 name_length = RQ.shift32();
5d8e7ec0 474 RFB.fb_name = RQ.shiftStr(name_length);
489d1676 475
d93d3e09
JM
476 Canvas.resize(RFB.fb_width, RFB.fb_height, RFB.true_color);
477 Canvas.start(RFB.keyPress, RFB.mouseButton, RFB.mouseMove);
64ab5c4d 478
d41c33e4
JM
479 if (RFB.true_color) {
480 RFB.fb_Bpp = 4;
481 RFB.fb_depth = 3;
482 } else {
483 RFB.fb_Bpp = 1;
484 RFB.fb_depth = 1;
485 }
486
c4164bda 487 response = RFB.pixelFormat();
29cb15f9 488 response = response.concat(RFB.clientEncodings());
c4164bda 489 response = response.concat(RFB.fbUpdateRequest(0));
c3996e24 490 RFB.timing.fbu_rt_start = (new Date()).getTime();
c4164bda 491 RFB.send_array(response);
9f4af5a7 492
d064769c 493 /* Start pushing/polling */
15046f00
JM
494 setTimeout(RFB.checkEvents, RFB.check_rate);
495 setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate);
2152a88f 496 RFB.timing.history_start = (new Date()).getTime();
15046f00 497 setTimeout(RFB.update_timings, 1000);
489d1676 498
8759ea6f 499 RFB.updateState('normal', "Connected to: " + RFB.fb_name);
65e27ddd
JM
500 break;
501 }
753bde8f 502 //console.log("<< init_msg");
64ab5c4d 503},
65e27ddd 504
28a5f293
JM
505
506/* Normal RFB/VNC server messages */
507normal_msg: function () {
508 //console.log(">> normal_msg");
c4164bda 509
2152a88f 510 var RQ = RFB.RQ, ret = true, msg_type,
d41c33e4 511 c, first_colour, num_colours, red, green, blue;
56ec48be 512
2152a88f 513 if (RFB.FBU.rects > 0) {
c4164bda
JM
514 msg_type = 0;
515 } else if (RFB.cuttext !== 'none') {
516 msg_type = 3;
28a5f293 517 } else {
c4164bda 518 msg_type = RQ.shift8();
28a5f293
JM
519 }
520 switch (msg_type) {
521 case 0: // FramebufferUpdate
2152a88f 522 ret = RFB.framebufferUpdate();
28a5f293
JM
523 break;
524 case 1: // SetColourMapEntries
d41c33e4 525 console.log("SetColourMapEntries");
5d8e7ec0 526 RQ.shift8(); // Padding
d41c33e4 527 first_colour = RQ.shift16(); // First colour
c4164bda 528 num_colours = RQ.shift16();
15046f00 529 for (c=0; c < num_colours; c+=1) {
d41c33e4
JM
530 red = RQ.shift16();
531 //console.log("red before: " + red);
532 red = parseInt(red / 256, 10);
533 //console.log("red after: " + red);
534 green = parseInt(RQ.shift16() / 256, 10);
535 blue = parseInt(RQ.shift16() / 256, 10);
536 Canvas.colourMap[first_colour + c] = [red, green, blue];
537 }
538 console.log("Registered " + num_colours + " colourMap entries");
539 //console.log("colourMap: " + Canvas.colourMap);
28a5f293
JM
540 break;
541 case 2: // Bell
542 console.log("Bell (unsupported)");
543 break;
544 case 3: // ServerCutText
545 console.log("ServerCutText");
5d8e7ec0 546 console.log("RQ:" + RQ.slice(0,20));
c4164bda 547 if (RFB.cuttext === 'none') {
30059bdf
JM
548 RFB.cuttext = 'header';
549 }
c4164bda 550 if (RFB.cuttext === 'header') {
5d8e7ec0 551 if (RQ.length < 7) {
30e53963 552 //console.log("waiting for ServerCutText header");
5d8e7ec0 553 return false;
30059bdf 554 }
5d8e7ec0
JM
555 RQ.shiftBytes(3); // Padding
556 RFB.ct_length = RQ.shift32();
30059bdf
JM
557 }
558 RFB.cuttext = 'bytes';
5d8e7ec0 559 if (RQ.length < RFB.ct_length) {
30e53963 560 //console.log("waiting for ServerCutText bytes");
5d8e7ec0 561 return false;
30059bdf 562 }
5d8e7ec0 563 RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length));
30059bdf 564 RFB.cuttext = 'none';
28a5f293
JM
565 break;
566 default:
c4164bda
JM
567 RFB.updateState('failed',
568 "Disconnected: illegal server message type " + msg_type);
5d8e7ec0 569 console.log("RQ.slice(0,30):" + RQ.slice(0,30));
28a5f293
JM
570 break;
571 }
572 //console.log("<< normal_msg");
5d8e7ec0 573 return ret;
28a5f293
JM
574},
575
2152a88f
JM
576framebufferUpdate: function() {
577 var RQ = RFB.RQ, FBU = RFB.FBU, timing = RFB.timing,
15046f00 578 now, fbu_rt_diff, last_bytes, last_rects,
2152a88f
JM
579 ret = true, msg;
580
581 if (FBU.rects === 0) {
582 if (RQ.length < 3) {
583 RQ.unshift(0); // FBU msg_type
584 //console.log(" waiting for FBU header bytes");
585 return false;
586 }
587 RQ.shift8();
588 FBU.rects = RQ.shift16();
589 //console.log("FramebufferUpdate, rects:" + FBU.rects);
590 FBU.bytes = 0;
591 timing.cur_fbu = 0;
592 timing.h_fbus += 1;
593 if (timing.fbu_rt_start > 0) {
594 now = (new Date()).getTime();
595 console.log("First FBU latency: " + (now - timing.fbu_rt_start));
596 }
597 }
598
599 while ((FBU.rects > 0) && (RQ.length >= FBU.bytes)) {
600 if (FBU.bytes === 0) {
601 if (RQ.length < 12) {
602 //console.log(" waiting for rect header bytes");
603 return false;
604 }
605 /* New FramebufferUpdate */
606 FBU.x = RQ.shift16();
607 FBU.y = RQ.shift16();
608 FBU.width = RQ.shift16();
609 FBU.height = RQ.shift16();
610 FBU.encoding = parseInt(RQ.shift32(), 10);
611 timing.h_bytes += 12;
612
613 // Debug:
614 /*
615 if (RFB.encNames[FBU.encoding]) {
616 msg = "FramebufferUpdate rects:" + FBU.rects;
617 msg += " encoding:" + FBU.encoding;
618 msg += "(" + RFB.encNames[FBU.encoding] + ")";
619 msg += ", RQ.length: " + RQ.length;
620 console.log(msg);
621 } else {
622 RFB.updateState('failed',
623 "Disconnected: unsupported encoding " +
624 FBU.encoding);
625 return false;
626 }
627 */
628 }
629
630 timing.last_fbu = (new Date()).getTime();
2152a88f 631 last_bytes = RQ.length;
15046f00 632 last_rects = FBU.rects;
2152a88f
JM
633
634 ret = RFB.encHandlers[FBU.encoding]();
635
636 now = (new Date()).getTime();
637 timing.cur_fbu += (now - timing.last_fbu);
638 timing.h_bytes += last_bytes-RQ.length;
639
640 if (FBU.rects < last_rects) {
641 // Some work was done
642 timing.h_rects += last_rects-FBU.rects;
643 timing.h_pixels += FBU.width*FBU.height;
644 }
645
646 if (FBU.rects === 0) {
647 if (((FBU.width === RFB.fb_width) &&
648 (FBU.height === RFB.fb_height)) ||
649 (timing.fbu_rt_start > 0)) {
650 timing.full_fbu_total += timing.cur_fbu;
651 timing.full_fbu_cnt += 1;
652 console.log("Timing of full FBU, cur: " +
653 timing.cur_fbu + ", total: " +
654 timing.full_fbu_total + ", cnt: " +
655 timing.full_fbu_cnt + ", avg: " +
656 (timing.full_fbu_total /
657 timing.full_fbu_cnt));
658 }
659 if (timing.fbu_rt_start > 0) {
660 fbu_rt_diff = now - timing.fbu_rt_start;
661 timing.fbu_rt_total += fbu_rt_diff;
662 timing.fbu_rt_cnt += 1;
663 console.log("full FBU round-trip, cur: " +
664 fbu_rt_diff + ", total: " +
665 timing.fbu_rt_total + ", cnt: " +
666 timing.fbu_rt_cnt + ", avg: " +
667 (timing.fbu_rt_total /
668 timing.fbu_rt_cnt));
669 timing.fbu_rt_start = 0;
670 }
671 }
672
673 if (RFB.state !== "normal") { return true; }
674 }
675 return ret;
676},
28a5f293
JM
677
678/*
679 * FramebufferUpdate encodings
680 */
681
ed7e776d 682display_raw: function () {
1098b5bf 683 //console.log(">> display_raw");
c4164bda 684
56ec48be 685 var RQ = RFB.RQ, FBU = RFB.FBU, cur_y, cur_height;
c4164bda
JM
686
687 if (FBU.lines === 0) {
1098b5bf
JM
688 FBU.lines = FBU.height;
689 }
690 FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line
5d8e7ec0 691 if (RQ.length < FBU.bytes) {
c4164bda
JM
692 //console.log(" waiting for " +
693 // (FBU.bytes - RQ.length) + " RAW bytes");
1098b5bf
JM
694 return;
695 }
c4164bda
JM
696 cur_y = FBU.y + (FBU.height - FBU.lines);
697 cur_height = Math.min(FBU.lines,
698 Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp)));
d41c33e4 699 Canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0);
5d8e7ec0 700 RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp);
1098b5bf
JM
701 FBU.lines -= cur_height;
702
703 if (FBU.lines > 0) {
704 FBU.bytes = FBU.width * RFB.fb_Bpp; // At least another line
705 } else {
15046f00 706 FBU.rects -= 1;
1098b5bf
JM
707 FBU.bytes = 0;
708 }
ed7e776d
JM
709},
710
711display_copy_rect: function () {
1098b5bf 712 //console.log(">> display_copy_rect");
c4164bda 713
56ec48be 714 var RQ = RFB.RQ, FBU = RFB.FBU, old_x, old_y;
c4164bda 715
5d8e7ec0 716 if (RQ.length < 4) {
c4164bda 717 //console.log(" waiting for " +
29cb15f9 718 // (FBU.bytes - RQ.length) + " COPYRECT bytes");
1098b5bf
JM
719 return;
720 }
c4164bda
JM
721 old_x = RQ.shift16();
722 old_y = RQ.shift16();
ed7e776d 723 Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
15046f00 724 FBU.rects -= 1;
1098b5bf 725 FBU.bytes = 0;
ed7e776d
JM
726},
727
728display_rre: function () {
56ec48be
JM
729 //console.log(">> display_rre (" + RFB.RQ.length + " bytes)");
730 var RQ = RFB.RQ, FBU = RFB.FBU, color, x, y, width, height, chunk;
c4164bda 731 if (FBU.subrects === 0) {
5d8e7ec0 732 if (RQ.length < 4 + RFB.fb_Bpp) {
c4164bda
JM
733 //console.log(" waiting for " +
734 // (4 + RFB.fb_Bpp - RQ.length) + " RRE bytes");
1098b5bf
JM
735 return;
736 }
5d8e7ec0 737 FBU.subrects = RQ.shift32();
c4164bda 738 color = RQ.shiftBytes(RFB.fb_Bpp); // Background
48ebcdb1 739 Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
f7618085 740 }
5d8e7ec0 741 while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) {
c4164bda
JM
742 color = RQ.shiftBytes(RFB.fb_Bpp);
743 x = RQ.shift16();
744 y = RQ.shift16();
745 width = RQ.shift16();
746 height = RQ.shift16();
48ebcdb1 747 Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color);
15046f00 748 FBU.subrects -= 1;
ed7e776d 749 }
c4164bda
JM
750 //console.log(" display_rre: rects: " + FBU.rects +
751 // ", FBU.subrects: " + FBU.subrects);
ed7e776d
JM
752
753 if (FBU.subrects > 0) {
c4164bda 754 chunk = Math.min(RFB.rre_chunk, FBU.subrects);
f7618085 755 FBU.bytes = (RFB.fb_Bpp + 8) * chunk;
ed7e776d 756 } else {
15046f00 757 FBU.rects -= 1;
1098b5bf 758 FBU.bytes = 0;
ed7e776d 759 }
b7ec5487 760 //console.log("<< display_rre, FBU.bytes: " + FBU.bytes);
ed7e776d
JM
761},
762
0f628064 763display_hextile: function() {
8759ea6f 764 //console.log(">> display_hextile");
56ec48be
JM
765 var RQ = RFB.RQ, FBU = RFB.FBU,
766 subencoding, subrects, idx, tile, color, cur_tile,
767 tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh;
b7ec5487 768
c4164bda 769 if (FBU.tiles === 0) {
1098b5bf
JM
770 FBU.tiles_x = Math.ceil(FBU.width/16);
771 FBU.tiles_y = Math.ceil(FBU.height/16);
772 FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
773 FBU.tiles = FBU.total_tiles;
774 }
775
5d8e7ec0 776 /* FBU.bytes comes in as 1, RQ.length at least 1 */
1098b5bf
JM
777 while (FBU.tiles > 0) {
778 FBU.bytes = 1;
5d8e7ec0 779 if (RQ.length < FBU.bytes) {
30e53963 780 //console.log(" waiting for HEXTILE subencoding byte");
1098b5bf
JM
781 return;
782 }
5d8e7ec0 783 subencoding = RQ[0]; // Peek
1098b5bf 784 if (subencoding > 30) { // Raw
c4164bda
JM
785 RFB.updateState('failed',
786 "Disconnected: illegal hextile subencoding " + subencoding);
5d8e7ec0 787 console.log("RQ.slice(0,30):" + RQ.slice(0,30));
1098b5bf
JM
788 return;
789 }
790 subrects = 0;
9f4af5a7
JM
791 cur_tile = FBU.total_tiles - FBU.tiles;
792 tile_x = cur_tile % FBU.tiles_x;
793 tile_y = Math.floor(cur_tile / FBU.tiles_x);
794 x = FBU.x + tile_x * 16;
795 y = FBU.y + tile_y * 16;
c4164bda
JM
796 w = Math.min(16, (FBU.x + FBU.width) - x);
797 h = Math.min(16, (FBU.y + FBU.height) - y);
b7ec5487 798
1098b5bf
JM
799 /* Figure out how much we are expecting */
800 if (subencoding & 0x01) { // Raw
801 //console.log(" Raw subencoding");
802 FBU.bytes += w * h * RFB.fb_Bpp;
803 } else {
804 if (subencoding & 0x02) { // Background
805 FBU.bytes += RFB.fb_Bpp;
806 }
807 if (subencoding & 0x04) { // Foreground
808 FBU.bytes += RFB.fb_Bpp;
809 }
810 if (subencoding & 0x08) { // AnySubrects
15046f00 811 FBU.bytes += 1; // Since we aren't shifting it off
5d8e7ec0 812 if (RQ.length < FBU.bytes) {
1098b5bf 813 /* Wait for subrects byte */
30e53963 814 //console.log(" waiting for hextile subrects header byte");
1098b5bf 815 return;
b7ec5487 816 }
5d8e7ec0 817 subrects = RQ[FBU.bytes-1]; // Peek
1098b5bf
JM
818 if (subencoding & 0x10) { // SubrectsColoured
819 FBU.bytes += subrects * (RFB.fb_Bpp + 2);
820 } else {
821 FBU.bytes += subrects * 2;
b7ec5487
JM
822 }
823 }
824 }
825
c4164bda
JM
826 //console.log(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
827 // ", subencoding:" + subencoding +
828 // "(last: " + FBU.lastsubencoding + "), subrects:" +
829 // subrects + ", tile:" + tile_x + "," + tile_y +
830 // " [" + x + "," + y + "]@" + w + "x" + h +
831 // ", d.length:" + RQ.length + ", bytes:" + FBU.bytes +
832 // " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) +
833 // " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10));
5d8e7ec0 834 if (RQ.length < FBU.bytes) {
c4164bda
JM
835 //console.log(" waiting for " +
836 // (FBU.bytes - RQ.length) + " hextile bytes");
b7ec5487
JM
837 return;
838 }
839
1098b5bf 840 /* We know the encoding and have a whole tile */
7f4f41b0
JM
841 FBU.subencoding = RQ[0];
842 idx = 1;
c4164bda 843 if (FBU.subencoding === 0) {
1098b5bf
JM
844 if (FBU.lastsubencoding & 0x01) {
845 /* Weird: ignore blanks after RAW */
846 console.log(" Ignoring blank after RAW");
7f4f41b0
JM
847 } else {
848 Canvas.fillRect(x, y, w, h, FBU.background);
1098b5bf 849 }
1098b5bf 850 } else if (FBU.subencoding & 0x01) { // Raw
d41c33e4 851 Canvas.blitImage(x, y, w, h, RQ, idx);
1098b5bf 852 } else {
1098b5bf 853 if (FBU.subencoding & 0x02) { // Background
5d8e7ec0 854 FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp);
1098b5bf 855 idx += RFB.fb_Bpp;
1098b5bf
JM
856 }
857 if (FBU.subencoding & 0x04) { // Foreground
5d8e7ec0 858 FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp);
1098b5bf 859 idx += RFB.fb_Bpp;
1098b5bf 860 }
3875f847 861
c4164bda 862 tile = Canvas.getTile(x, y, w, h, FBU.background);
1098b5bf 863 if (FBU.subencoding & 0x08) { // AnySubrects
5d8e7ec0 864 subrects = RQ[idx];
15046f00
JM
865 idx += 1;
866 for (s = 0; s < subrects; s += 1) {
1098b5bf 867 if (FBU.subencoding & 0x10) { // SubrectsColoured
5d8e7ec0 868 color = RQ.slice(idx, idx + RFB.fb_Bpp);
1098b5bf
JM
869 idx += RFB.fb_Bpp;
870 } else {
871 color = FBU.foreground;
b7ec5487 872 }
5d8e7ec0 873 xy = RQ[idx];
15046f00 874 idx += 1;
3875f847
JM
875 sx = (xy >> 4);
876 sy = (xy & 0x0f);
1098b5bf 877
5d8e7ec0 878 wh = RQ[idx];
15046f00 879 idx += 1;
1098b5bf
JM
880 sw = (wh >> 4) + 1;
881 sh = (wh & 0x0f) + 1;
882
d93d3e09 883 Canvas.setSubTile(tile, sx, sy, sw, sh, color);
b7ec5487
JM
884 }
885 }
3875f847 886 Canvas.putTile(tile);
b7ec5487 887 }
5d8e7ec0 888 RQ.shiftBytes(FBU.bytes);
1098b5bf
JM
889 FBU.lastsubencoding = FBU.subencoding;
890 FBU.bytes = 0;
15046f00 891 FBU.tiles -= 1;
0f628064
JM
892 }
893
c4164bda 894 if (FBU.tiles === 0) {
15046f00 895 FBU.rects -= 1;
b7ec5487
JM
896 }
897
8759ea6f 898 //console.log("<< display_hextile");
0f628064
JM
899},
900
ed7e776d 901
29cb15f9
JM
902display_tight_png: function() {
903 //console.log(">> display_tight_png");
904 var RQ = RFB.RQ, FBU = RFB.FBU,
15046f00 905 ctl, cmode, clength, getCLength, color, img;
29cb15f9
JM
906 //console.log(" FBU.rects: " + FBU.rects);
907 //console.log(" RQ.length: " + RQ.length);
908 //console.log(" RQ.slice(0,20): " + RQ.slice(0,20));
909
910
911 FBU.bytes = 1; // compression-control byte
912 if (RQ.length < FBU.bytes) {
30e53963 913 //console.log(" waiting for TIGHT compression-control byte");
29cb15f9
JM
914 return;
915 }
916
917 // Get 'compact length' header and data size
918 getCLength = function (arr, offset) {
919 var header = 1, data = 0;
920 data += arr[offset + 0] & 0x7f;
921 if (arr[offset + 0] & 0x80) {
15046f00 922 header += 1;
29cb15f9
JM
923 data += (arr[offset + 1] & 0x7f) << 7;
924 if (arr[offset + 1] & 0x80) {
15046f00 925 header += 1;
29cb15f9
JM
926 data += arr[offset + 2] << 14;
927 }
928 }
929 return [header, data];
15046f00 930 };
29cb15f9
JM
931
932 ctl = RQ[0];
933 switch (ctl >> 4) {
934 case 0x08: cmode = "fill"; break;
935 case 0x09: cmode = "jpeg"; break;
936 case 0x0A: cmode = "png"; break;
15046f00 937 default: throw("Illegal ctl: " + ctl);
29cb15f9
JM
938 }
939 switch (cmode) {
940 // fill uses fb_depth because TPIXELs drop the padding byte
941 case "fill": FBU.bytes += RFB.fb_depth; break; // TPIXEL
942 case "jpeg": FBU.bytes += 3; break; // max clength
943 case "png": FBU.bytes += 3; break; // max clength
944 }
945
946 if (RQ.length < FBU.bytes) {
30e53963 947 //console.log(" waiting for TIGHT " + cmode + " bytes");
29cb15f9
JM
948 return;
949 }
950
4b4496ad 951 //console.log(" RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")");
29cb15f9
JM
952 //console.log(" cmode: " + cmode);
953
954 // Determine FBU.bytes
955 switch (cmode) {
956 case "fill":
957 RQ.shift8(); // shift off ctl
958 color = RQ.shiftBytes(RFB.fb_depth);
959 Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
960 break;
961 case "jpeg":
962 case "png":
963 clength = getCLength(RQ, 1);
964 FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
965 if (RQ.length < FBU.bytes) {
4b4496ad 966 //console.log(" waiting for TIGHT " + cmode + " bytes");
29cb15f9
JM
967 return;
968 }
969
970 // We have everything, render it
971 //console.log(" png, RQ.length: " + RQ.length + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
972 RQ.shiftBytes(1 + clength[0]); // shift off ctl + compact length
973 img = new Image();
6a52558d
JM
974 img.onload = RFB.scan_tight_imgs;
975 FBU.imgs.push([img, FBU.x, FBU.y]);
c3d28aab 976 img.src = "data:image/" + cmode +
29cb15f9 977 RFB.extract_data_uri(RQ.shiftBytes(clength[1]));
c3d28aab 978 img = null;
29cb15f9
JM
979 break;
980 }
981 FBU.bytes = 0;
15046f00 982 FBU.rects -= 1;
29cb15f9
JM
983 //console.log(" ending RQ.length: " + RQ.length);
984 //console.log(" ending RQ.slice(0,20): " + RQ.slice(0,20));
985},
986
987extract_data_uri : function (arr) {
0664669c 988 var i, stra = [];
15046f00 989 for (i=0; i< arr.length; i += 1) {
0664669c
JM
990 stra.push(String.fromCharCode(arr[i]));
991 }
c3d28aab
JM
992 //return "," + escape(stra.join(''));
993 return ";base64," + Base64.encode(arr);
29cb15f9
JM
994},
995
6a52558d 996scan_tight_imgs : function () {
15046f00 997 var img, imgs;
6a52558d
JM
998 if (RFB.state === 'normal') {
999 imgs = RFB.FBU.imgs;
1000 while ((imgs.length > 0) && (imgs[0][0].complete)) {
1001 img = imgs.shift();
1002 Canvas.ctx.drawImage(img[0], img[1], img[2]);
1003 }
15046f00 1004 setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate);
6a52558d
JM
1005 }
1006},
1007
4b4496ad
JM
1008set_desktopsize : function () {
1009 console.log(">> set_desktopsize");
1010 RFB.fb_width = RFB.FBU.width;
1011 RFB.fb_height = RFB.FBU.height;
1012 Canvas.clear();
1013 Canvas.resize(RFB.fb_width, RFB.fb_height);
1014 RFB.timing.fbu_rt_start = (new Date()).getTime();
1015 // Send a new non-incremental request
1016 RFB.send_array(RFB.fbUpdateRequest(0));
1017 console.log("<< set_desktopsize");
1018
1019 RFB.FBU.bytes = 0;
15046f00 1020 RFB.FBU.rects -= 1;
4b4496ad
JM
1021},
1022
1023set_jpeg_quality : function () {
1024 console.log(">> set_jpeg_quality");
1025},
1026set_compress_level: function () {
1027 console.log(">> set_compress_level");
1028},
65e27ddd
JM
1029
1030/*
1031 * Client message routines
1032 */
1033
9f4af5a7 1034pixelFormat: function () {
4b4496ad 1035 //console.log(">> pixelFormat");
9f4af5a7
JM
1036 var arr;
1037 arr = [0]; // msg-type
64ab5c4d
JM
1038 arr.push8(0); // padding
1039 arr.push8(0); // padding
1040 arr.push8(0); // padding
1041
cc0410a3 1042 arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel
d41c33e4 1043 arr.push8(RFB.fb_depth * 8); // depth
64ab5c4d 1044 arr.push8(0); // little-endian
d41c33e4 1045 arr.push8(RFB.true_color); // true-color
64ab5c4d
JM
1046
1047 arr.push16(255); // red-max
1048 arr.push16(255); // green-max
1049 arr.push16(255); // blue-max
9f4af5a7 1050 arr.push8(0); // red-shift
64ab5c4d 1051 arr.push8(8); // green-shift
9f4af5a7 1052 arr.push8(16); // blue-shift
64ab5c4d 1053
9f4af5a7
JM
1054 arr.push8(0); // padding
1055 arr.push8(0); // padding
1056 arr.push8(0); // padding
4b4496ad 1057 //console.log("<< pixelFormat");
9f4af5a7 1058 return arr;
64ab5c4d
JM
1059},
1060
1061fixColourMapEntries: function () {
1062},
1063
29cb15f9 1064clientEncodings: function () {
4b4496ad 1065 //console.log(">> clientEncodings");
29cb15f9 1066 var arr, i;
9f4af5a7 1067 arr = [2]; // msg-type
64ab5c4d 1068 arr.push8(0); // padding
b7ec5487 1069
29cb15f9 1070 arr.push16(RFB.encodings.length); // encoding count
b7ec5487 1071
15046f00 1072 for (i=0; i<RFB.encodings.length; i += 1) {
8a837006 1073 arr.push32(RFB.encodings[i][1]);
29cb15f9 1074 }
30e53963 1075 //console.log("<< clientEncodings: " + arr);
9f4af5a7 1076 return arr;
64ab5c4d 1077},
65e27ddd 1078
64ab5c4d 1079fbUpdateRequest: function (incremental, x, y, xw, yw) {
410960ba 1080 //console.log(">> fbUpdateRequest");
c4164bda
JM
1081 if (!x) { x = 0; }
1082 if (!y) { y = 0; }
1083 if (!xw) { xw = RFB.fb_width; }
1084 if (!yw) { yw = RFB.fb_height; }
9f4af5a7
JM
1085 var arr;
1086 arr = [3]; // msg-type
64ab5c4d
JM
1087 arr.push8(incremental);
1088 arr.push16(x);
1089 arr.push16(y);
1090 arr.push16(xw);
1091 arr.push16(yw);
b7ec5487 1092 //console.log("<< fbUpdateRequest");
9f4af5a7 1093 return arr;
64ab5c4d 1094},
65e27ddd 1095
d9cbdc7d 1096keyEvent: function (keysym, down) {
410960ba 1097 //console.log(">> keyEvent, keysym: " + keysym + ", down: " + down);
9f4af5a7
JM
1098 var arr;
1099 arr = [4]; // msg-type
64ab5c4d
JM
1100 arr.push8(down);
1101 arr.push16(0);
d9cbdc7d 1102 arr.push32(keysym);
b7ec5487 1103 //console.log("<< keyEvent");
d064769c 1104 return arr;
64ab5c4d 1105},
65e27ddd 1106
d064769c 1107pointerEvent: function (x, y) {
c4164bda
JM
1108 //console.log(">> pointerEvent, x,y: " + x + "," + y +
1109 // " , mask: " + RFB.mouse_buttonMask);
d064769c
JM
1110 var arr;
1111 arr = [5]; // msg-type
97bfe5ba 1112 arr.push8(RFB.mouse_buttonMask);
d064769c
JM
1113 arr.push16(x);
1114 arr.push16(y);
410960ba 1115 //console.log("<< pointerEvent");
d064769c 1116 return arr;
64ab5c4d 1117},
65e27ddd 1118
30059bdf 1119clientCutText: function (text) {
91308399 1120 //console.log(">> clientCutText");
30059bdf
JM
1121 var arr;
1122 arr = [6]; // msg-type
1123 arr.push8(0); // padding
1124 arr.push8(0); // padding
1125 arr.push8(0); // padding
1126 arr.push32(text.length);
1127 arr.pushStr(text);
91308399 1128 //console.log("<< clientCutText:" + arr);
30059bdf 1129 return arr;
64ab5c4d
JM
1130},
1131
1132
1133/*
1134 * Utility routines
1135 */
1136
507b473a
JM
1137encode_message: function(arr) {
1138 if (RFB.b64encode) {
1139 RFB.SQ = RFB.SQ + Base64.encode(arr);
1140 } else {
1141 RFB.SQ = RFB.SQ + arr.map(function (num) {
1142 return String.fromCharCode(num); } ).join('');
1143 }
1144},
1145
1146decode_message: function(data, offset) {
1147 //console.log(">> decode_message: " + data);
1148 if (RFB.b64encode) {
1149 RFB.RQ = RFB.RQ.concat(Base64.decode(data, offset));
1150 } else {
af183e63
JM
1151 // A bit faster in firefox
1152 var i, length = data.length, RQ = RFB.RQ;
15046f00 1153 for (i=offset; i < length; i += 1) {
af183e63
JM
1154 RQ.push(data.charCodeAt(i) % 256);
1155 }
507b473a
JM
1156 }
1157 //console.log(">> decode_message, RQ: " + RFB.RQ);
1158},
1159
07287cfd
JM
1160recv_message: function(e) {
1161 //console.log(">> recv_message");
07287cfd 1162
753bde8f
JM
1163 try {
1164 if (RFB.use_seq) {
1165 RFB.recv_message_reorder(e);
1166 } else {
507b473a 1167 RFB.decode_message(e.data, 0);
753bde8f
JM
1168
1169 RFB.handle_message();
1170 }
c4164bda 1171 } catch (exc) {
14355cb2
JM
1172 if (typeof exc.stack !== 'undefined') {
1173 console.log("recv_message, caught exception: " + exc.stack);
1174 } else if (typeof exc.description !== 'undefined') {
1175 console.log("recv_message, caught exception: " + exc.description);
1176 } else {
1177 console.log("recv_message, caught exception:" + exc);
1178 }
c4164bda
JM
1179 if (typeof exc.name !== 'undefined') {
1180 RFB.updateState('failed', exc.name + ": " + exc.message);
753bde8f 1181 } else {
c4164bda 1182 RFB.updateState('failed', exc);
753bde8f
JM
1183 }
1184 }
07287cfd
JM
1185 //console.log("<< recv_message");
1186},
1187
1188recv_message_reorder: function(e) {
1189 //console.log(">> recv_message_reorder");
1190
15046f00 1191 var offset, seq_num, i;
c4164bda
JM
1192
1193 offset = e.data.indexOf(":") + 1;
1194 seq_num = parseInt(e.data.substr(0, offset-1), 10);
56ec48be 1195 if (RFB.RQ_seq_num === seq_num) {
507b473a 1196 RFB.decode_message(e.data, offset);
15046f00 1197 RFB.RQ_seq_num += 1;
07287cfd 1198 } else {
c4164bda 1199 console.warn("sequence number mismatch: expected " +
56ec48be 1200 RFB.RQ_seq_num + ", got " + seq_num);
d93d3e09 1201 if (RFB.RQ_reorder.length > 40) {
07287cfd
JM
1202 RFB.updateState('failed', "Re-order queue too long");
1203 } else {
56ec48be 1204 RFB.RQ_reorder = RFB.RQ_reorder.concat(e.data.substr(0));
c4164bda 1205 i = 0;
56ec48be
JM
1206 while (i < RFB.RQ_reorder.length) {
1207 offset = RFB.RQ_reorder[i].indexOf(":") + 1;
1208 seq_num = parseInt(RFB.RQ_reorder[i].substr(0, offset-1), 10);
c4164bda
JM
1209 //console.log("Searching reorder list item " +
1210 // i + ", seq_num " + seq_num);
56ec48be 1211 if (seq_num === RFB.RQ_seq_num) {
07287cfd
JM
1212 /* Remove it from reorder queue, decode it and
1213 * add it to the receive queue */
1214 console.log("Found re-ordered packet seq_num " + seq_num);
507b473a 1215 RFB.decode_message(RFB.RQ_reorder.splice(i, 1)[0], offset);
15046f00 1216 RFB.RQ_seq_num += 1;
07287cfd
JM
1217 i = 0; // Start search again for next one
1218 } else {
15046f00 1219 i += 1;
07287cfd
JM
1220 }
1221 }
1222
1223 }
1224 }
1225
97763d0e
JM
1226 if (RFB.RQ.length > 0) {
1227 RFB.handle_message();
1228 }
07287cfd
JM
1229 //console.log("<< recv_message_reorder");
1230},
1231
1232handle_message: function () {
4b4496ad 1233 //console.log("RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")");
07287cfd
JM
1234 switch (RFB.state) {
1235 case 'disconnected':
1236 console.error("Got data while disconnected");
1237 break;
07287cfd
JM
1238 case 'failed':
1239 console.log("Giving up!");
1240 RFB.disconnect();
1241 break;
1242 case 'normal':
1243 RFB.normal_msg();
1244 /*
56ec48be 1245 while (RFB.RQ.length > 0) {
c4164bda 1246 if (RFB.normal_msg() && RFB.state === 'normal') {
07287cfd
JM
1247 console.log("More to process");
1248 } else {
1249 break;
1250 }
1251 }
1252 */
1253 break;
1254 default:
1255 RFB.init_msg();
1256 break;
1257 }
1258},
1259
64ab5c4d 1260send_string: function (str) {
b7ec5487 1261 //console.log(">> send_string: " + str);
9f4af5a7 1262 RFB.send_array(str.split('').map(
c4164bda 1263 function (chr) { return chr.charCodeAt(0); } ) );
64ab5c4d
JM
1264},
1265
1266send_array: function (arr) {
1098b5bf 1267 //console.log(">> send_array: " + arr);
507b473a 1268 RFB.encode_message(arr);
c4164bda 1269 if (RFB.ws.bufferedAmount === 0) {
56ec48be
JM
1270 RFB.ws.send(RFB.SQ);
1271 RFB.SQ = "";
5d8e7ec0
JM
1272 } else {
1273 console.log("Delaying send");
1274 }
64ab5c4d
JM
1275},
1276
c539e4dc 1277DES: function (password, challenge) {
c4164bda
JM
1278 var i, passwd, response;
1279 passwd = [];
1280 response = challenge.slice();
15046f00 1281 for (i=0; i < password.length; i += 1) {
c539e4dc 1282 passwd.push(password.charCodeAt(i));
532a9fd9 1283 }
c539e4dc
JM
1284
1285 DES.setKeys(passwd);
1286 DES.encrypt(response, 0, response, 0);
1287 DES.encrypt(response, 8, response, 8);
1288 return response;
532a9fd9
JM
1289},
1290
d064769c 1291flushClient: function () {
97bfe5ba
JM
1292 if (RFB.mouse_arr.length > 0) {
1293 //RFB.send_array(RFB.mouse_arr.concat(RFB.fbUpdateRequest(1)));
c4164bda 1294 RFB.send_array(RFB.mouse_arr);
5d8e7ec0
JM
1295 setTimeout(function() {
1296 RFB.send_array(RFB.fbUpdateRequest(1));
1297 }, 50);
1298
97bfe5ba 1299 RFB.mouse_arr = [];
8cf20615 1300 return true;
d064769c 1301 } else {
8cf20615 1302 return false;
d064769c
JM
1303 }
1304},
1305
8cf20615 1306checkEvents: function () {
c4164bda
JM
1307 var now;
1308 if (RFB.state === 'normal') {
8cf20615 1309 if (! RFB.flushClient()) {
c4164bda 1310 now = new Date().getTime();
8cf20615
JM
1311 if (now > RFB.last_req + RFB.req_rate) {
1312 RFB.last_req = now;
1313 RFB.send_array(RFB.fbUpdateRequest(1));
1314 }
1315 }
64ab5c4d 1316 }
15046f00 1317 setTimeout(RFB.checkEvents, RFB.check_rate);
64ab5c4d
JM
1318},
1319
e2e7c224 1320keyPress: function (keysym, down) {
c4164bda 1321 var arr;
e2e7c224 1322 arr = RFB.keyEvent(keysym, down);
d064769c
JM
1323 arr = arr.concat(RFB.fbUpdateRequest(1));
1324 RFB.send_array(arr);
64ab5c4d
JM
1325},
1326
e2e7c224
JM
1327mouseButton: function(x, y, down, bmask) {
1328 if (down) {
1329 RFB.mouse_buttonMask |= bmask;
1330 } else {
1331 RFB.mouse_buttonMask ^= bmask;
1332 }
97bfe5ba 1333 RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) );
d064769c
JM
1334 RFB.flushClient();
1335},
1336
e2e7c224 1337mouseMove: function(x, y) {
1098b5bf 1338 //console.log('>> mouseMove ' + x + "," + y);
97bfe5ba 1339 RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) );
d064769c
JM
1340},
1341
30059bdf 1342clipboardCopyTo: function (text) {
91308399
JM
1343 console.log(">> clipboardCopyTo stub");
1344 // Stub
30059bdf
JM
1345},
1346
91308399
JM
1347externalUpdateState: function(state, msg) {
1348 console.log(">> externalUpdateState stub");
1349 // Stub
30059bdf 1350},
d064769c 1351
8759ea6f 1352updateState: function(state, statusMsg) {
91308399 1353 var func, cmsg;
15046f00
JM
1354 if (state === 'failed') {
1355 func = function(msg) { console.error(msg); };
1356 } else {
1357 func = function(msg) { console.warn(msg); };
8759ea6f
JM
1358 }
1359
91308399
JM
1360 cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
1361 func("New state '" + state + "'." + cmsg);
1362
2152a88f
JM
1363 if ((state === 'disconnected') && (RFB.state !== 'disconnected')) {
1364 RFB.show_timings();
1365 }
1366
753bde8f
JM
1367 if ((RFB.state === 'failed') &&
1368 ((state === 'disconnected') || (state === 'closed'))) {
1369 // Leave the failed message
15046f00 1370 RFB.externalUpdateState(state);
91308399
JM
1371 } else {
1372 RFB.state = state;
15046f00 1373 RFB.externalUpdateState(state, statusMsg);
8759ea6f 1374 }
2152a88f
JM
1375},
1376
1377update_timings: function() {
1378 var now, timing = RFB.timing, offset;
1379 now = (new Date()).getTime();
1380 timing.history.push([now,
1381 timing.h_fbus,
1382 timing.h_rects,
1383 timing.h_bytes,
1384 timing.h_pixels]);
1385 timing.h_fbus = 0;
1386 timing.h_rects = 0;
1387 timing.h_bytes = 0;
1388 timing.h_pixels = 0;
1389 if ((RFB.state !== 'disconnected') && (RFB.state !== 'failed')) {
1390 // Try for every second
1391 offset = (now - timing.history_start) % 1000;
1392 if (offset < 500) {
15046f00 1393 setTimeout(RFB.update_timings, 1000 - offset);
2152a88f 1394 } else {
15046f00 1395 setTimeout(RFB.update_timings, 2000 - offset);
2152a88f
JM
1396 }
1397 }
1398},
1399
1400show_timings: function() {
1401 var i, timing = RFB.timing, history, msg,
1402 delta, tot_time = 0, tot_fbus = 0, tot_rects = 0,
1403 tot_bytes = 0, tot_pixels = 0;
15046f00 1404 if (timing.history_start === 0) { return; }
2152a88f
JM
1405 console.log(">> show_timings");
1406 RFB.update_timings(); // Final accumulate
1407 msg = "\nTimings\n";
1408 msg += " time: fbus,rects,bytes,pixels\n";
15046f00 1409 for (i=0; i < timing.history.length; i += 1) {
2152a88f
JM
1410 history = timing.history[i];
1411 delta = ((history[0]-timing.history_start)/1000);
1412 tot_time = delta;
1413 tot_fbus += history[1];
1414 tot_rects += history[2];
1415 tot_bytes += history[3];
1416 tot_pixels += history[4];
1417
1418 msg += " " + delta.toFixed(3);
1419 msg += ": " + history.slice(1) + "\n";
1420 }
1421 msg += "\nTotals:\n";
1422 msg += " time: fbus,rects,bytes,pixels\n";
1423 msg += " " + tot_time.toFixed(3);
1424 msg += ": " + tot_fbus + "," + tot_rects;
1425 msg += "," + tot_bytes + "," + tot_pixels;
1426 console.log(msg);
8759ea6f 1427},
65e27ddd
JM
1428
1429/*
1430 * Setup routines
1431 */
1432
532a9fd9 1433init_ws: function () {
30e53963 1434 //console.log(">> init_ws");
c4164bda 1435
507b473a 1436 var uri = "", vars = [];
db504ade
JM
1437 if (RFB.encrypt) {
1438 uri = "wss://";
1439 } else {
1440 uri = "ws://";
1441 }
507b473a
JM
1442 uri += RFB.host + ":" + RFB.port + "/";
1443 if (RFB.b64encode) {
1444 vars.push("b64encode");
1445 }
07287cfd 1446 if (RFB.use_seq) {
507b473a
JM
1447 vars.push("seq_num");
1448 }
1449 if (vars.length > 0) {
1450 uri += "?" + vars.join("&");
07287cfd 1451 }
b7ec5487 1452 console.log("connecting to " + uri);
cc0410a3 1453 RFB.ws = new WebSocket(uri);
5d8e7ec0 1454
753bde8f 1455 RFB.ws.onmessage = RFB.recv_message;
cc0410a3 1456 RFB.ws.onopen = function(e) {
30e53963 1457 //console.log(">> WebSocket.onopen");
8759ea6f 1458 RFB.updateState('ProtocolVersion', "Starting VNC handshake");
5d8e7ec0
JM
1459 RFB.sendID = setInterval(function() {
1460 /*
1461 * Send updates either at a rate of one update every 50ms,
1462 * or whatever slower rate the network can handle
1463 */
c4164bda 1464 if (RFB.ws.bufferedAmount === 0) {
56ec48be
JM
1465 if (RFB.SQ) {
1466 RFB.ws.send(RFB.SQ);
1467 RFB.SQ = "";
5d8e7ec0
JM
1468 }
1469 } else {
1470 console.log("Delaying send");
1471 }
1472 }, 50);
30e53963 1473 //console.log("<< WebSocket.onopen");
65e27ddd 1474 };
cc0410a3 1475 RFB.ws.onclose = function(e) {
30e53963 1476 //console.log(">> WebSocket.onclose");
5d8e7ec0 1477 clearInterval(RFB.sendID);
753bde8f 1478 RFB.updateState('disconnected', 'VNC disconnected');
30e53963 1479 //console.log("<< WebSocket.onclose");
5d2c3864
JM
1480 };
1481 RFB.ws.onerror = function(e) {
8759ea6f 1482 console.error(">> WebSocket.onerror");
af183e63 1483 RFB.updateState('failed', "WebSocket error");
8759ea6f 1484 console.error("<< WebSocket.onerror");
5d2c3864 1485 };
64ab5c4d 1486
2a4e7d8a
JM
1487 setTimeout(function () {
1488 if (RFB.ws.readyState === WebSocket.CONNECTING) {
1489 RFB.updateState('failed', "Connect timeout");
1490 RFB.ws.close();
1491 }
1492 }, RFB.connectTimeout);
1493
30e53963 1494 //console.log("<< init_ws");
64ab5c4d 1495},
65e27ddd 1496
5d8e7ec0
JM
1497init_vars: function () {
1498 /* Reset state */
56ec48be
JM
1499 RFB.cuttext = 'none';
1500 RFB.ct_length = 0;
1501 RFB.RQ = [];
1502 RFB.RQ_reorder = [];
1503 RFB.RQ_seq_num = 0;
1504 RFB.SQ = "";
1505 RFB.FBU.rects = 0;
1506 RFB.FBU.subrects = 0; // RRE and HEXTILE
1507 RFB.FBU.lines = 0; // RAW
1508 RFB.FBU.tiles = 0; // HEXTILE
6a52558d 1509 RFB.FBU.imgs = []; // TIGHT_PNG image queue
97bfe5ba 1510 RFB.mouse_buttonmask = 0;
56ec48be 1511 RFB.mouse_arr = [];
2152a88f
JM
1512
1513 RFB.timing.history_start = 0;
1514 RFB.timing.history = [];
1515 RFB.timing.h_fbus = 0;
1516 RFB.timing.h_rects = 0;
1517 RFB.timing.h_bytes = 0;
1518 RFB.timing.h_pixels = 0;
c4164bda 1519}
ded9dfae 1520
64ab5c4d 1521}; /* End of RFB */