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