]> git.proxmox.com Git - mirror_novnc.git/blame - include/rfb.js
Change RepeaterID to be a config option
[mirror_novnc.git] / include / rfb.js
CommitLineData
a7a89626
JM
1/*
2 * noVNC: HTML5 VNC client
d0c29bb6 3 * Copyright (C) 2011 Joel Martin
5f409eee 4 * Licensed under LGPL-3 (see LICENSE.txt)
a7a89626
JM
5 *
6 * See README.md for usage and integration instructions.
d38db74a
MT
7 *
8 * TIGHT decoder portion:
9 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
a7a89626
JM
10 */
11
9c57ac39 12/*jslint white: false, browser: true, bitwise: false, plusplus: false */
0911173a 13/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
a7a89626 14
a7a89626 15
5210330a 16function RFB(defaults) {
d890e864 17"use strict";
8db09746 18
5210330a
JM
19var that = {}, // Public API methods
20 conf = {}, // Configuration attributes
8db09746
JM
21
22 // Pre-declare private functions used before definitions (jslint)
43cf7bd8
JM
23 init_vars, updateState, fail, handle_message,
24 init_msg, normal_msg, framebufferUpdate, print_stats,
8db09746 25
832c7445 26 pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
8db09746
JM
27 keyEvent, pointerEvent, clientCutText,
28
34d8b844 29 getTightCLength, extract_data_uri,
d3796c14 30 keyPress, mouseButton, mouseMove,
8db09746 31
72f1348b 32 checkEvents, // Overridable for testing
8db09746
JM
33
34
35 //
36 // Private RFB namespace variables
37 //
38 rfb_host = '',
39 rfb_port = 5900,
40 rfb_password = '',
6209639f 41 rfb_path = '',
8db09746
JM
42
43 rfb_state = 'disconnected',
44 rfb_version = 0,
45 rfb_max_version= 3.8,
46 rfb_auth_scheme= '',
8db09746
JM
47
48
49 // In preference order
50 encodings = [
51 ['COPYRECT', 0x01 ],
de84e098 52 ['TIGHT', 0x07 ],
8db09746
JM
53 ['TIGHT_PNG', -260 ],
54 ['HEXTILE', 0x05 ],
55 ['RRE', 0x02 ],
56 ['RAW', 0x00 ],
57 ['DesktopSize', -223 ],
58 ['Cursor', -239 ],
59
60 // Psuedo-encoding settings
2cedf483
MT
61 //['JPEG_quality_lo', -32 ],
62 ['JPEG_quality_med', -26 ],
8db09746 63 //['JPEG_quality_hi', -23 ],
2cedf483
MT
64 //['compress_lo', -255 ],
65 ['compress_hi', -247 ],
66 ['last_rect', -224 ]
8db09746
JM
67 ],
68
69 encHandlers = {},
70 encNames = {},
a679a97d 71 encStats = {}, // [rectCnt, rectCntTot]
8db09746 72
72f1348b 73 ws = null, // Websock object
d890e864 74 display = null, // Display object
d3796c14
JM
75 keyboard = null, // Keyboard input handler object
76 mouse = null, // Mouse input handler object
d67de767 77 sendTimer = null, // Send Queue check timer
e3efeb32
JM
78 connTimer = null, // connection timer
79 disconnTimer = null, // disconnection timer
d67de767 80 msgTimer = null, // queued handle_message timer
8db09746 81
8db09746
JM
82 // Frame buffer update state
83 FBU = {
84 rects : 0,
ce2b6909 85 subrects : 0, // RRE
8db09746
JM
86 lines : 0, // RAW
87 tiles : 0, // HEXTILE
88 bytes : 0,
89 x : 0,
90 y : 0,
91 width : 0,
92 height : 0,
93 encoding : 0,
94 subencoding : -1,
95 background : null,
d065cad9 96 zlibs : [] // TIGHT zlib streams
8db09746
JM
97 },
98
99 fb_Bpp = 4,
100 fb_depth = 3,
101 fb_width = 0,
102 fb_height = 0,
103 fb_name = "",
104
8db09746
JM
105 last_req_time = 0,
106 rre_chunk_sz = 100,
107
108 timing = {
109 last_fbu : 0,
110 fbu_total : 0,
111 fbu_total_cnt : 0,
112 full_fbu_total : 0,
113 full_fbu_cnt : 0,
114
115 fbu_rt_start : 0,
116 fbu_rt_total : 0,
a09a75e8
JM
117 fbu_rt_cnt : 0,
118 pixels : 0
8db09746
JM
119 },
120
121 test_mode = false,
122
5210330a
JM
123 def_con_timeout = Websock_native ? 2 : 5,
124
8db09746
JM
125 /* Mouse state */
126 mouse_buttonMask = 0,
a5df24b4
JM
127 mouse_arr = [],
128 viewportDragging = false,
129 viewportDragPos = {};
8db09746 130
5210330a
JM
131// Configuration attributes
132Util.conf_defaults(conf, that, defaults, [
133 ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
134 ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'],
135
136 ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
12acb663 137 ['repeaterID', 'rw', 'string', '', 'RepeaterID to connect to'],
5210330a
JM
138 ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
139 ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
140 ['shared', 'rw', 'bool', true, 'Request shared mode'],
06a9ef0c 141 ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
5210330a
JM
142
143 ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
144 ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
145
a5df24b4
JM
146 ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
147
5210330a
JM
148 ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
149 ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
150
151 // Callback functions
152 ['onUpdateState', 'rw', 'func', function() { },
153 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
154 ['onPasswordRequired', 'rw', 'func', function() { },
155 'onPasswordRequired(rfb): VNC password is required '],
156 ['onClipboard', 'rw', 'func', function() { },
157 'onClipboard(rfb, text): RFB clipboard contents received'],
158 ['onBell', 'rw', 'func', function() { },
159 'onBell(rfb): RFB Bell message received '],
160 ['onFBUReceive', 'rw', 'func', function() { },
161 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
162 ['onFBUComplete', 'rw', 'func', function() { },
163 'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
164
165 // These callback names are deprecated
166 ['updateState', 'rw', 'func', function() { },
167 'obsolete, use onUpdateState'],
168 ['clipboardReceive', 'rw', 'func', function() { },
169 'obsolete, use onClipboard']
170 ]);
171
172
173// Override/add some specific configuration getters/setters
8db09746 174that.set_local_cursor = function(cursor) {
a7a89626 175 if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
8db09746 176 conf.local_cursor = false;
a7a89626 177 } else {
d890e864 178 if (display.get_cursor_uri()) {
8db09746 179 conf.local_cursor = true;
a7a89626
JM
180 } else {
181 Util.Warn("Browser does not support local cursor");
182 }
183 }
8db09746 184};
a7a89626 185
5210330a
JM
186// These are fake configuration getters
187that.get_display = function() { return display; };
a7a89626 188
5210330a 189that.get_keyboard = function() { return keyboard; };
8db09746 190
5210330a 191that.get_mouse = function() { return mouse; };
8db09746
JM
192
193
60440cee 194
8db09746
JM
195//
196// Setup routines
197//
198
d890e864
JM
199// Create the public API interface and initialize values that stay
200// constant across connect/disconnect
8db09746 201function constructor() {
3b20e7a9 202 var i, rmode;
67b4e987 203 Util.Debug(">> RFB.constructor");
a7a89626 204
8db09746
JM
205 // Create lookup tables based encoding number
206 for (i=0; i < encodings.length; i+=1) {
207 encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
208 encNames[encodings[i][1]] = encodings[i][0];
a679a97d 209 encStats[encodings[i][1]] = [0, 0];
a7a89626 210 }
d890e864 211 // Initialize display, mouse, keyboard, and websock
a7a89626 212 try {
d890e864 213 display = new Display({'target': conf.target});
a7a89626 214 } catch (exc) {
d890e864
JM
215 Util.Error("Display exception: " + exc);
216 updateState('fatal', "No working Display");
a7a89626 217 }
d890e864
JM
218 keyboard = new Keyboard({'target': conf.focusContainer,
219 'onKeyPress': keyPress});
220 mouse = new Mouse({'target': conf.target,
221 'onMouseButton': mouseButton,
222 'onMouseMove': mouseMove});
223
224 rmode = display.get_render_mode();
225
d890e864
JM
226 ws = new Websock();
227 ws.on('message', handle_message);
228 ws.on('open', function() {
229 if (rfb_state === "connect") {
230 updateState('ProtocolVersion', "Starting VNC handshake");
231 } else {
232 fail("Got unexpected WebSockets connection");
233 }
234 });
b688a909 235 ws.on('close', function(e) {
dcf1994d 236 Util.Warn("WebSocket on-close event");
f8380ff9 237 var msg = "";
b688a909 238 if (e.code) {
f8380ff9
JM
239 msg = " (code: " + e.code;
240 if (e.reason) {
241 msg += ", reason: " + e.reason;
242 }
243 msg += ")";
b688a909 244 }
d890e864 245 if (rfb_state === 'disconnect') {
f8380ff9 246 updateState('disconnected', 'VNC disconnected' + msg);
d890e864 247 } else if (rfb_state === 'ProtocolVersion') {
f8380ff9 248 fail('Failed to connect to server' + msg);
d890e864 249 } else if (rfb_state in {'failed':1, 'disconnected':1}) {
f8380ff9 250 Util.Error("Received onclose while disconnected" + msg);
d890e864 251 } else {
f8380ff9 252 fail('Server disconnected' + msg);
d890e864
JM
253 }
254 });
255 ws.on('error', function(e) {
dcf1994d
JM
256 Util.Warn("WebSocket on-error event");
257 //fail("WebSock reported an error");
d890e864
JM
258 });
259
a7a89626 260
0d1e1b72
JM
261 init_vars();
262
263 /* Check web-socket-js if no builtin WebSocket support */
72f1348b 264 if (Websock_native) {
0d1e1b72 265 Util.Info("Using native WebSockets");
3b20e7a9 266 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
0d1e1b72 267 } else {
e3716842
JM
268 Util.Warn("Using web-socket-js bridge. Flash version: " +
269 Util.Flash.version);
0d1e1b72
JM
270 if ((! Util.Flash) ||
271 (Util.Flash.version < 9)) {
f8990704 272 updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
0d1e1b72
JM
273 } else if (document.location.href.substr(0, 7) === "file://") {
274 updateState('fatal',
275 "'file://' URL is incompatible with Adobe Flash");
276 } else {
3b20e7a9 277 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
0d1e1b72
JM
278 }
279 }
280
67b4e987 281 Util.Debug("<< RFB.constructor");
8db09746
JM
282 return that; // Return the public API interface
283}
a7a89626 284
72f1348b
JM
285function connect() {
286 Util.Debug(">> RFB.connect");
d38db74a
MT
287 var uri;
288
289 if (typeof UsingSocketIO !== "undefined") {
290 uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
8db09746 291 } else {
d38db74a
MT
292 if (conf.encrypt) {
293 uri = "wss://";
294 } else {
295 uri = "ws://";
296 }
297 uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
8db09746 298 }
8db09746 299 Util.Info("connecting to " + uri);
72f1348b 300 ws.open(uri);
a7a89626 301
72f1348b
JM
302 Util.Debug("<< RFB.connect");
303}
304
d890e864 305// Initialize variables that are reset before each connection
72f1348b 306init_vars = function() {
43cf7bd8
JM
307 var i;
308
72f1348b 309 /* Reset state */
72f1348b
JM
310 ws.init();
311
8db09746
JM
312 FBU.rects = 0;
313 FBU.subrects = 0; // RRE and HEXTILE
314 FBU.lines = 0; // RAW
315 FBU.tiles = 0; // HEXTILE
a820f126 316 FBU.zlibs = []; // TIGHT zlib encoders
8db09746
JM
317 mouse_buttonMask = 0;
318 mouse_arr = [];
a679a97d
JM
319
320 // Clear the per connection encoding stats
43cf7bd8 321 for (i=0; i < encodings.length; i+=1) {
a679a97d
JM
322 encStats[encodings[i][1]][0] = 0;
323 }
de84e098
M
324
325 for (i=0; i < 4; i++) {
6fbc3748 326 //FBU.zlibs[i] = new InflateStream();
de84e098
M
327 FBU.zlibs[i] = new TINF();
328 FBU.zlibs[i].init();
329 }
a679a97d
JM
330};
331
332// Print statistics
333print_stats = function() {
43cf7bd8 334 var i, s;
a679a97d
JM
335 Util.Info("Encoding stats for this connection:");
336 for (i=0; i < encodings.length; i+=1) {
337 s = encStats[encodings[i][1]];
338 if ((s[0] + s[1]) > 0) {
339 Util.Info(" " + encodings[i][0] + ": " +
340 s[0] + " rects");
341 }
342 }
343 Util.Info("Encoding stats since page load:");
344 for (i=0; i < encodings.length; i+=1) {
345 s = encStats[encodings[i][1]];
346 if ((s[0] + s[1]) > 0) {
43cf7bd8
JM
347 Util.Info(" " + encodings[i][0] + ": " +
348 s[1] + " rects");
a679a97d
JM
349 }
350 }
8db09746
JM
351};
352
353//
354// Utility routines
355//
a7a89626
JM
356
357
358/*
8db09746
JM
359 * Page states:
360 * loaded - page load, equivalent to disconnected
d890e864
JM
361 * disconnected - idle state
362 * connect - starting to connect (to ProtocolVersion)
363 * normal - connected
364 * disconnect - starting to disconnect
365 * failed - abnormal disconnect
8db09746
JM
366 * fatal - failed to load page, or fatal error
367 *
d890e864
JM
368 * RFB protocol initialization states:
369 * ProtocolVersion
8db09746
JM
370 * Security
371 * Authentication
e3efeb32 372 * password - waiting for password, not part of RFB
8db09746 373 * SecurityResult
1f758e87 374 * ClientInitialization - not triggered by server message
d890e864 375 * ServerInitialization (to normal)
a7a89626 376 */
8db09746
JM
377updateState = function(state, statusMsg) {
378 var func, cmsg, oldstate = rfb_state;
e3efeb32 379
8db09746
JM
380 if (state === oldstate) {
381 /* Already here, ignore */
382 Util.Debug("Already in state '" + state + "', ignoring.");
383 return;
384 }
a7a89626 385
e3efeb32
JM
386 /*
387 * These are disconnected states. A previous connect may
388 * asynchronously cause a connection so make sure we are closed.
389 */
390 if (state in {'disconnected':1, 'loaded':1, 'connect':1,
391 'disconnect':1, 'failed':1, 'fatal':1}) {
392 if (sendTimer) {
393 clearInterval(sendTimer);
394 sendTimer = null;
395 }
396
397 if (msgTimer) {
398 clearInterval(msgTimer);
399 msgTimer = null;
400 }
401
d890e864 402 if (display && display.get_context()) {
d3796c14
JM
403 keyboard.ungrab();
404 mouse.ungrab();
d890e864 405 display.defaultCursor();
159ad55d
JM
406 if ((Util.get_logging() !== 'debug') ||
407 (state === 'loaded')) {
408 // Show noVNC logo on load and when disconnected if
409 // debug is off
d890e864 410 display.clear();
159ad55d 411 }
e3efeb32
JM
412 }
413
72f1348b 414 ws.close();
e3efeb32
JM
415 }
416
8db09746
JM
417 if (oldstate === 'fatal') {
418 Util.Error("Fatal error, cannot continue");
419 }
a7a89626 420
8db09746
JM
421 if ((state === 'failed') || (state === 'fatal')) {
422 func = Util.Error;
423 } else {
424 func = Util.Warn;
425 }
a7a89626 426
dcf1994d
JM
427 cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
428 func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
429
8db09746 430 if ((oldstate === 'failed') && (state === 'disconnected')) {
dcf1994d 431 // Do disconnect action, but stay in failed state
8db09746
JM
432 rfb_state = 'failed';
433 } else {
434 rfb_state = state;
435 }
a7a89626 436
e3efeb32
JM
437 if (connTimer && (rfb_state !== 'connect')) {
438 Util.Debug("Clearing connect timer");
439 clearInterval(connTimer);
440 connTimer = null;
441 }
8db09746 442
e3efeb32
JM
443 if (disconnTimer && (rfb_state !== 'disconnect')) {
444 Util.Debug("Clearing disconnect timer");
445 clearInterval(disconnTimer);
446 disconnTimer = null;
447 }
8db09746 448
e3efeb32
JM
449 switch (state) {
450 case 'normal':
451 if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
452 Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
a7a89626
JM
453 }
454
a7a89626
JM
455 break;
456
a7a89626 457
8db09746 458 case 'connect':
e3efeb32
JM
459
460 connTimer = setTimeout(function () {
ce2b6909 461 fail("Connect timeout");
ff36b127 462 }, conf.connectTimeout * 1000);
a7a89626 463
e3efeb32 464 init_vars();
72f1348b 465 connect();
8db09746 466
e3efeb32 467 // WebSocket.onopen transitions to 'ProtocolVersion'
a7a89626
JM
468 break;
469
a7a89626 470
e3efeb32 471 case 'disconnect':
a7a89626 472
ff36b127
JM
473 if (! test_mode) {
474 disconnTimer = setTimeout(function () {
ce2b6909 475 fail("Disconnect timeout");
ff36b127
JM
476 }, conf.disconnectTimeout * 1000);
477 }
a7a89626 478
a679a97d
JM
479 print_stats();
480
e3efeb32 481 // WebSocket.onclose transitions to 'disconnected'
8db09746 482 break;
a7a89626 483
a7a89626 484
8db09746
JM
485 case 'failed':
486 if (oldstate === 'disconnected') {
487 Util.Error("Invalid transition from 'disconnected' to 'failed'");
488 }
489 if (oldstate === 'normal') {
490 Util.Error("Error while connected.");
491 }
492 if (oldstate === 'init') {
493 Util.Error("Error while initializing.");
a7a89626
JM
494 }
495
8db09746
JM
496 // Make sure we transition to disconnected
497 setTimeout(function() { updateState('disconnected'); }, 50);
498
a7a89626 499 break;
a7a89626
JM
500
501
8db09746 502 default:
e3efeb32 503 // No state change action to take
a7a89626 504
8db09746 505 }
a7a89626 506
8db09746
JM
507 if ((oldstate === 'failed') && (state === 'disconnected')) {
508 // Leave the failed message
d890e864
JM
509 conf.updateState(that, state, oldstate); // Obsolete
510 conf.onUpdateState(that, state, oldstate);
8db09746 511 } else {
d890e864
JM
512 conf.updateState(that, state, oldstate, statusMsg); // Obsolete
513 conf.onUpdateState(that, state, oldstate, statusMsg);
8db09746
JM
514 }
515};
43cf7bd8
JM
516
517fail = function(msg) {
ce2b6909
JM
518 updateState('failed', msg);
519 return false;
43cf7bd8 520};
8db09746 521
43cf7bd8 522handle_message = function() {
72f1348b
JM
523 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
524 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
525 if (ws.rQlen() === 0) {
8db09746
JM
526 Util.Warn("handle_message called on empty receive queue");
527 return;
528 }
529 switch (rfb_state) {
530 case 'disconnected':
8db09746 531 case 'failed':
e3efeb32 532 Util.Error("Got data while disconnected");
8db09746
JM
533 break;
534 case 'normal':
72f1348b 535 if (normal_msg() && ws.rQlen() > 0) {
8db09746 536 // true means we can continue processing
8db09746 537 // Give other events a chance to run
4ed717ad
JM
538 if (msgTimer === null) {
539 Util.Debug("More data to process, creating timer");
540 msgTimer = setTimeout(function () {
541 msgTimer = null;
542 handle_message();
543 }, 10);
544 } else {
545 Util.Debug("More data to process, existing timer");
546 }
8db09746
JM
547 }
548 break;
549 default:
550 init_msg();
551 break;
552 }
43cf7bd8 553};
8db09746 554
8db09746
JM
555
556function genDES(password, challenge) {
43cf7bd8 557 var i, passwd = [];
8db09746
JM
558 for (i=0; i < password.length; i += 1) {
559 passwd.push(password.charCodeAt(i));
560 }
a9995971 561 return (new DES(passwd)).encrypt(challenge);
8db09746
JM
562}
563
564function flushClient() {
565 if (mouse_arr.length > 0) {
832c7445 566 //send(mouse_arr.concat(fbUpdateRequests()));
72f1348b 567 ws.send(mouse_arr);
8db09746 568 setTimeout(function() {
832c7445 569 ws.send(fbUpdateRequests());
8db09746
JM
570 }, 50);
571
572 mouse_arr = [];
573 return true;
574 } else {
575 return false;
576 }
577}
578
579// overridable for testing
580checkEvents = function() {
581 var now;
a5df24b4 582 if (rfb_state === 'normal' && !viewportDragging) {
8db09746
JM
583 if (! flushClient()) {
584 now = new Date().getTime();
585 if (now > last_req_time + conf.fbu_req_rate) {
586 last_req_time = now;
832c7445 587 ws.send(fbUpdateRequests());
8db09746
JM
588 }
589 }
590 }
591 setTimeout(checkEvents, conf.check_rate);
592};
593
d3796c14 594keyPress = function(keysym, down) {
8db09746 595 var arr;
06a9ef0c
JM
596
597 if (conf.view_only) { return; } // View only, skip keyboard events
598
8db09746 599 arr = keyEvent(keysym, down);
832c7445 600 arr = arr.concat(fbUpdateRequests());
72f1348b 601 ws.send(arr);
d3796c14 602};
8db09746 603
d3796c14 604mouseButton = function(x, y, down, bmask) {
8db09746
JM
605 if (down) {
606 mouse_buttonMask |= bmask;
607 } else {
608 mouse_buttonMask ^= bmask;
609 }
a5df24b4
JM
610
611 if (conf.viewportDrag) {
612 if (down && !viewportDragging) {
613 viewportDragging = true;
614 viewportDragPos = {'x': x, 'y': y};
615
616 // Skip sending mouse events
617 return;
618 } else {
619 viewportDragging = false;
06a9ef0c 620 ws.send(fbUpdateRequests()); // Force immediate redraw
a5df24b4
JM
621 }
622 }
623
06a9ef0c
JM
624 if (conf.view_only) { return; } // View only, skip mouse events
625
a5df24b4
JM
626 mouse_arr = mouse_arr.concat(
627 pointerEvent(display.absX(x), display.absY(y)) );
8db09746 628 flushClient();
d3796c14 629};
8db09746 630
d3796c14 631mouseMove = function(x, y) {
8db09746 632 //Util.Debug('>> mouseMove ' + x + "," + y);
a5df24b4
JM
633 var deltaX, deltaY;
634
635 if (viewportDragging) {
636 //deltaX = x - viewportDragPos.x; // drag viewport
637 deltaX = viewportDragPos.x - x; // drag frame buffer
638 //deltaY = y - viewportDragPos.y; // drag viewport
639 deltaY = viewportDragPos.y - y; // drag frame buffer
640 viewportDragPos = {'x': x, 'y': y};
641
642 display.viewportChange(deltaX, deltaY);
643
644 // Skip sending mouse events
645 return;
646 }
647
06a9ef0c
JM
648 if (conf.view_only) { return; } // View only, skip mouse events
649
a5df24b4
JM
650 mouse_arr = mouse_arr.concat(
651 pointerEvent(display.absX(x), display.absY(y)) );
d3796c14 652};
8db09746
JM
653
654
8db09746
JM
655//
656// Server message handlers
657//
658
659// RFB/VNC initialisation message handler
660init_msg = function() {
661 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
662
8d0c7fb5 663 var strlen, reason, length, sversion, cversion, repeaterID,
8db09746 664 i, types, num_types, challenge, response, bpp, depth,
c6fd7153 665 big_endian, red_max, green_max, blue_max, red_shift,
8d0c7fb5 666 green_shift, blue_shift, true_color, name_length, is_repeater;
8db09746 667
72f1348b 668 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
8db09746
JM
669 switch (rfb_state) {
670
671 case 'ProtocolVersion' :
72f1348b 672 if (ws.rQlen() < 12) {
ce2b6909 673 return fail("Incomplete protocol version");
8db09746 674 }
72f1348b 675 sversion = ws.rQshiftStr(12).substr(4,7);
8db09746 676 Util.Info("Server ProtocolVersion: " + sversion);
8d0c7fb5 677 is_repeater = 0;
8db09746 678 switch (sversion) {
8d0c7fb5 679 case "000.000": is_repeater = 1; break; // UltraVNC repeater
8db09746 680 case "003.003": rfb_version = 3.3; break;
1a5dd77d 681 case "003.006": rfb_version = 3.3; break; // UltraVNC
f84504bc 682 case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop
8db09746
JM
683 case "003.007": rfb_version = 3.7; break;
684 case "003.008": rfb_version = 3.8; break;
a9a7f0e1 685 case "004.000": rfb_version = 3.8; break; // Intel AMT KVM
58ad8387 686 case "004.001": rfb_version = 3.8; break; // RealVNC 4.6
8db09746 687 default:
ce2b6909 688 return fail("Invalid server version " + sversion);
8db09746 689 }
8d0c7fb5 690 if (is_repeater) {
12acb663 691 repeaterID = conf.repeaterID;
8d0c7fb5
WL
692 while(repeaterID.length < 250)
693 repeaterID += "\0";
694 ws.send_string(repeaterID);
695 break;
696 }
8db09746
JM
697 if (rfb_version > rfb_max_version) {
698 rfb_version = rfb_max_version;
699 }
700
701 if (! test_mode) {
4ed717ad 702 sendTimer = setInterval(function() {
8db09746
JM
703 // Send updates either at a rate of one update
704 // every 50ms, or whatever slower rate the network
705 // can handle.
72f1348b 706 ws.flush();
8db09746
JM
707 }, 50);
708 }
709
710 cversion = "00" + parseInt(rfb_version,10) +
711 ".00" + ((rfb_version * 10) % 10);
72f1348b 712 ws.send_string("RFB " + cversion + "\n");
1f758e87 713 updateState('Security', "Sent ProtocolVersion: " + cversion);
8db09746
JM
714 break;
715
716 case 'Security' :
717 if (rfb_version >= 3.7) {
1f758e87 718 // Server sends supported list, client decides
72f1348b
JM
719 num_types = ws.rQshift8();
720 if (ws.rQwait("security type", num_types, 1)) { return false; }
8db09746 721 if (num_types === 0) {
72f1348b
JM
722 strlen = ws.rQshift32();
723 reason = ws.rQshiftStr(strlen);
ce2b6909 724 return fail("Security failure: " + reason);
8db09746
JM
725 }
726 rfb_auth_scheme = 0;
72f1348b 727 types = ws.rQshiftBytes(num_types);
8db09746
JM
728 Util.Debug("Server security types: " + types);
729 for (i=0; i < types.length; i+=1) {
730 if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
731 rfb_auth_scheme = types[i];
732 }
733 }
734 if (rfb_auth_scheme === 0) {
ce2b6909 735 return fail("Unsupported security types: " + types);
8db09746
JM
736 }
737
72f1348b 738 ws.send([rfb_auth_scheme]);
8db09746 739 } else {
1f758e87 740 // Server decides
72f1348b
JM
741 if (ws.rQwait("security scheme", 4)) { return false; }
742 rfb_auth_scheme = ws.rQshift32();
8d0c7fb5 743 //rfb_auth_scheme = ws.rQshiftStr(12);
8db09746
JM
744 }
745 updateState('Authentication',
746 "Authenticating using scheme: " + rfb_auth_scheme);
747 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
748 break;
749
1f758e87 750 // Triggered by fallthough, not by server message
8db09746
JM
751 case 'Authentication' :
752 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
753 switch (rfb_auth_scheme) {
754 case 0: // connection failed
72f1348b
JM
755 if (ws.rQwait("auth reason", 4)) { return false; }
756 strlen = ws.rQshift32();
757 reason = ws.rQshiftStr(strlen);
ce2b6909 758 return fail("Auth failure: " + reason);
8db09746 759 case 1: // no authentication
1f758e87
JM
760 if (rfb_version >= 3.8) {
761 updateState('SecurityResult');
762 return;
1f758e87 763 }
43cf7bd8 764 // Fall through to ClientInitialisation
8db09746
JM
765 break;
766 case 2: // VNC authentication
767 if (rfb_password.length === 0) {
d890e864
JM
768 // Notify via both callbacks since it is kind of
769 // a RFB state change and a UI interface issue.
8db09746 770 updateState('password', "Password Required");
d890e864 771 conf.onPasswordRequired(that);
8db09746
JM
772 return;
773 }
72f1348b
JM
774 if (ws.rQwait("auth challenge", 16)) { return false; }
775 challenge = ws.rQshiftBytes(16);
8db09746
JM
776 //Util.Debug("Password: " + rfb_password);
777 //Util.Debug("Challenge: " + challenge +
778 // " (" + challenge.length + ")");
779 response = genDES(rfb_password, challenge);
780 //Util.Debug("Response: " + response +
781 // " (" + response.length + ")");
782
783 //Util.Debug("Sending DES encrypted auth response");
72f1348b 784 ws.send(response);
8db09746 785 updateState('SecurityResult');
1f758e87 786 return;
8db09746 787 default:
ce2b6909 788 fail("Unsupported auth scheme: " + rfb_auth_scheme);
8db09746
JM
789 return;
790 }
1f758e87
JM
791 updateState('ClientInitialisation', "No auth required");
792 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
8db09746
JM
793 break;
794
795 case 'SecurityResult' :
72f1348b
JM
796 if (ws.rQwait("VNC auth response ", 4)) { return false; }
797 switch (ws.rQshift32()) {
8db09746 798 case 0: // OK
1f758e87 799 // Fall through to ClientInitialisation
8db09746
JM
800 break;
801 case 1: // failed
802 if (rfb_version >= 3.8) {
72f1348b
JM
803 length = ws.rQshift32();
804 if (ws.rQwait("SecurityResult reason", length, 8)) {
60440cee 805 return false;
aa787069 806 }
72f1348b 807 reason = ws.rQshiftStr(length);
ce2b6909 808 fail(reason);
8db09746 809 } else {
ce2b6909 810 fail("Authentication failed");
8db09746
JM
811 }
812 return;
813 case 2: // too-many
ce2b6909 814 return fail("Too many auth attempts");
8db09746 815 }
1f758e87
JM
816 updateState('ClientInitialisation', "Authentication OK");
817 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
818 break;
819
820 // Triggered by fallthough, not by server message
821 case 'ClientInitialisation' :
72f1348b 822 ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
1f758e87 823 updateState('ServerInitialisation', "Authentication OK");
8db09746
JM
824 break;
825
826 case 'ServerInitialisation' :
72f1348b 827 if (ws.rQwait("server initialization", 24)) { return false; }
8db09746
JM
828
829 /* Screen size */
72f1348b
JM
830 fb_width = ws.rQshift16();
831 fb_height = ws.rQshift16();
8db09746
JM
832
833 /* PIXEL_FORMAT */
72f1348b
JM
834 bpp = ws.rQshift8();
835 depth = ws.rQshift8();
836 big_endian = ws.rQshift8();
837 true_color = ws.rQshift8();
8db09746 838
c6fd7153
JM
839 red_max = ws.rQshift16();
840 green_max = ws.rQshift16();
841 blue_max = ws.rQshift16();
842 red_shift = ws.rQshift8();
843 green_shift = ws.rQshift8();
844 blue_shift = ws.rQshift8();
845 ws.rQshiftStr(3); // padding
846
8db09746
JM
847 Util.Info("Screen: " + fb_width + "x" + fb_height +
848 ", bpp: " + bpp + ", depth: " + depth +
849 ", big_endian: " + big_endian +
c6fd7153
JM
850 ", true_color: " + true_color +
851 ", red_max: " + red_max +
852 ", green_max: " + green_max +
853 ", blue_max: " + blue_max +
854 ", red_shift: " + red_shift +
855 ", green_shift: " + green_shift +
856 ", blue_shift: " + blue_shift);
8db09746 857
ac99a1f7
JM
858 if (big_endian !== 0) {
859 Util.Warn("Server native endian is not little endian");
860 }
861 if (red_shift !== 16) {
862 Util.Warn("Server native red-shift is not 16");
863 }
864 if (blue_shift !== 0) {
865 Util.Warn("Server native blue-shift is not 0");
866 }
867
8db09746 868 /* Connection name/title */
72f1348b
JM
869 name_length = ws.rQshift32();
870 fb_name = ws.rQshiftStr(name_length);
a9a7f0e1
JM
871
872 if (conf.true_color && fb_name === "Intel(r) AMT KVM")
873 {
874 Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
875 conf.true_color = false;
876 }
8db09746 877
d890e864
JM
878 display.set_true_color(conf.true_color);
879 display.resize(fb_width, fb_height);
d3796c14
JM
880 keyboard.grab();
881 mouse.grab();
8db09746
JM
882
883 if (conf.true_color) {
884 fb_Bpp = 4;
885 fb_depth = 3;
886 } else {
887 fb_Bpp = 1;
888 fb_depth = 1;
889 }
890
891 response = pixelFormat();
892 response = response.concat(clientEncodings());
832c7445 893 response = response.concat(fbUpdateRequests());
8db09746 894 timing.fbu_rt_start = (new Date()).getTime();
72f1348b 895 ws.send(response);
8db09746
JM
896
897 /* Start pushing/polling */
898 setTimeout(checkEvents, conf.check_rate);
8db09746
JM
899
900 if (conf.encrypt) {
901 updateState('normal', "Connected (encrypted) to: " + fb_name);
902 } else {
903 updateState('normal', "Connected (unencrypted) to: " + fb_name);
904 }
905 break;
906 }
907 //Util.Debug("<< init_msg");
908};
909
910
911/* Normal RFB/VNC server message handler */
912normal_msg = function() {
913 //Util.Debug(">> normal_msg");
914
d890e864 915 var ret = true, msg_type, length, text,
8db09746
JM
916 c, first_colour, num_colours, red, green, blue;
917
8db09746
JM
918 if (FBU.rects > 0) {
919 msg_type = 0;
a7a89626 920 } else {
72f1348b 921 msg_type = ws.rQshift8();
a7a89626
JM
922 }
923 switch (msg_type) {
924 case 0: // FramebufferUpdate
8db09746 925 ret = framebufferUpdate(); // false means need more data
a7a89626
JM
926 break;
927 case 1: // SetColourMapEntries
928 Util.Debug("SetColourMapEntries");
72f1348b
JM
929 ws.rQshift8(); // Padding
930 first_colour = ws.rQshift16(); // First colour
931 num_colours = ws.rQshift16();
a9a7f0e1
JM
932 if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
933
a7a89626 934 for (c=0; c < num_colours; c+=1) {
72f1348b 935 red = ws.rQshift16();
a7a89626
JM
936 //Util.Debug("red before: " + red);
937 red = parseInt(red / 256, 10);
938 //Util.Debug("red after: " + red);
72f1348b
JM
939 green = parseInt(ws.rQshift16() / 256, 10);
940 blue = parseInt(ws.rQshift16() / 256, 10);
ac99a1f7 941 display.set_colourMap([blue, green, red], first_colour + c);
a7a89626 942 }
1150d0f7 943 Util.Debug("colourMap: " + display.get_colourMap());
a7a89626 944 Util.Info("Registered " + num_colours + " colourMap entries");
d890e864 945 //Util.Debug("colourMap: " + display.get_colourMap());
a7a89626
JM
946 break;
947 case 2: // Bell
d890e864
JM
948 Util.Debug("Bell");
949 conf.onBell(that);
a7a89626
JM
950 break;
951 case 3: // ServerCutText
952 Util.Debug("ServerCutText");
72f1348b
JM
953 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
954 ws.rQshiftBytes(3); // Padding
955 length = ws.rQshift32();
956 if (ws.rQwait("ServerCutText", length, 8)) { return false; }
60440cee 957
d890e864
JM
958 text = ws.rQshiftStr(length);
959 conf.clipboardReceive(that, text); // Obsolete
960 conf.onClipboard(that, text);
a7a89626
JM
961 break;
962 default:
ce2b6909 963 fail("Disconnected: illegal server message type " + msg_type);
72f1348b 964 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
a7a89626
JM
965 break;
966 }
967 //Util.Debug("<< normal_msg");
968 return ret;
8db09746 969};
a7a89626 970
8db09746 971framebufferUpdate = function() {
ce2b6909 972 var now, hdr, fbu_rt_diff, ret = true;
a7a89626
JM
973
974 if (FBU.rects === 0) {
72f1348b
JM
975 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
976 if (ws.rQwait("FBU header", 3)) {
977 ws.rQunshift8(0); // FBU msg_type
a7a89626
JM
978 return false;
979 }
72f1348b
JM
980 ws.rQshift8(); // padding
981 FBU.rects = ws.rQshift16();
a7a89626
JM
982 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
983 FBU.bytes = 0;
984 timing.cur_fbu = 0;
a7a89626
JM
985 if (timing.fbu_rt_start > 0) {
986 now = (new Date()).getTime();
987 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
988 }
989 }
990
42b2246c 991 while (FBU.rects > 0) {
8db09746 992 if (rfb_state !== "normal") {
42b2246c
JM
993 return false;
994 }
72f1348b 995 if (ws.rQwait("FBU", FBU.bytes)) { return false; }
a7a89626 996 if (FBU.bytes === 0) {
72f1348b 997 if (ws.rQwait("rect header", 12)) { return false; }
a7a89626 998 /* New FramebufferUpdate */
4ed717ad 999
72f1348b 1000 hdr = ws.rQshiftBytes(12);
4ed717ad
JM
1001 FBU.x = (hdr[0] << 8) + hdr[1];
1002 FBU.y = (hdr[2] << 8) + hdr[3];
1003 FBU.width = (hdr[4] << 8) + hdr[5];
1004 FBU.height = (hdr[6] << 8) + hdr[7];
1005 FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1006 (hdr[10] << 8) + hdr[11], 10);
a7a89626 1007
d890e864
JM
1008 conf.onFBUReceive(that,
1009 {'x': FBU.x, 'y': FBU.y,
1010 'width': FBU.width, 'height': FBU.height,
1011 'encoding': FBU.encoding,
1012 'encodingName': encNames[FBU.encoding]});
1013
8db09746 1014 if (encNames[FBU.encoding]) {
a7a89626
JM
1015 // Debug:
1016 /*
1017 var msg = "FramebufferUpdate rects:" + FBU.rects;
9c57ac39 1018 msg += " x: " + FBU.x + " y: " + FBU.y;
a7a89626
JM
1019 msg += " width: " + FBU.width + " height: " + FBU.height;
1020 msg += " encoding:" + FBU.encoding;
8db09746 1021 msg += "(" + encNames[FBU.encoding] + ")";
72f1348b 1022 msg += ", ws.rQlen(): " + ws.rQlen();
a7a89626
JM
1023 Util.Debug(msg);
1024 */
1025 } else {
ce2b6909
JM
1026 fail("Disconnected: unsupported encoding " +
1027 FBU.encoding);
a7a89626
JM
1028 return false;
1029 }
1030 }
1031
1032 timing.last_fbu = (new Date()).getTime();
a7a89626 1033
8db09746 1034 ret = encHandlers[FBU.encoding]();
a7a89626
JM
1035
1036 now = (new Date()).getTime();
1037 timing.cur_fbu += (now - timing.last_fbu);
a7a89626 1038
a679a97d
JM
1039 if (ret) {
1040 encStats[FBU.encoding][0] += 1;
1041 encStats[FBU.encoding][1] += 1;
a09a75e8 1042 timing.pixels += FBU.width * FBU.height;
a679a97d
JM
1043 }
1044
a09a75e8 1045 if (FBU.rects === 0 || (timing.pixels >= (fb_width * fb_height))) {
8db09746
JM
1046 if (((FBU.width === fb_width) &&
1047 (FBU.height === fb_height)) ||
a7a89626
JM
1048 (timing.fbu_rt_start > 0)) {
1049 timing.full_fbu_total += timing.cur_fbu;
1050 timing.full_fbu_cnt += 1;
1051 Util.Info("Timing of full FBU, cur: " +
1052 timing.cur_fbu + ", total: " +
1053 timing.full_fbu_total + ", cnt: " +
1054 timing.full_fbu_cnt + ", avg: " +
1055 (timing.full_fbu_total /
1056 timing.full_fbu_cnt));
1057 }
1058 if (timing.fbu_rt_start > 0) {
1059 fbu_rt_diff = now - timing.fbu_rt_start;
1060 timing.fbu_rt_total += fbu_rt_diff;
1061 timing.fbu_rt_cnt += 1;
1062 Util.Info("full FBU round-trip, cur: " +
1063 fbu_rt_diff + ", total: " +
1064 timing.fbu_rt_total + ", cnt: " +
1065 timing.fbu_rt_cnt + ", avg: " +
1066 (timing.fbu_rt_total /
1067 timing.fbu_rt_cnt));
1068 timing.fbu_rt_start = 0;
1069 }
1070 }
67b4e987 1071 if (! ret) {
ce2b6909 1072 return ret; // false ret means need more data
67b4e987 1073 }
a7a89626 1074 }
d890e864
JM
1075
1076 conf.onFBUComplete(that,
1077 {'x': FBU.x, 'y': FBU.y,
1078 'width': FBU.width, 'height': FBU.height,
1079 'encoding': FBU.encoding,
1080 'encodingName': encNames[FBU.encoding]});
1081
ce2b6909 1082 return true; // We finished this FBU
8db09746 1083};
a7a89626 1084
8db09746
JM
1085//
1086// FramebufferUpdate encodings
1087//
a7a89626 1088
8db09746 1089encHandlers.RAW = function display_raw() {
72f1348b 1090 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
a7a89626 1091
72f1348b 1092 var cur_y, cur_height;
a7a89626
JM
1093
1094 if (FBU.lines === 0) {
1095 FBU.lines = FBU.height;
1096 }
8db09746 1097 FBU.bytes = FBU.width * fb_Bpp; // At least a line
72f1348b 1098 if (ws.rQwait("RAW", FBU.bytes)) { return false; }
a7a89626
JM
1099 cur_y = FBU.y + (FBU.height - FBU.lines);
1100 cur_height = Math.min(FBU.lines,
72f1348b 1101 Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
d890e864 1102 display.blitImage(FBU.x, cur_y, FBU.width, cur_height,
72f1348b
JM
1103 ws.get_rQ(), ws.get_rQi());
1104 ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);
a7a89626
JM
1105 FBU.lines -= cur_height;
1106
1107 if (FBU.lines > 0) {
8db09746 1108 FBU.bytes = FBU.width * fb_Bpp; // At least another line
a7a89626
JM
1109 } else {
1110 FBU.rects -= 1;
1111 FBU.bytes = 0;
1112 }
72f1348b 1113 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
42b2246c 1114 return true;
8db09746 1115};
a7a89626 1116
8db09746 1117encHandlers.COPYRECT = function display_copy_rect() {
a7a89626
JM
1118 //Util.Debug(">> display_copy_rect");
1119
8db09746 1120 var old_x, old_y;
a7a89626 1121
72f1348b 1122 if (ws.rQwait("COPYRECT", 4)) { return false; }
72a5596e
JM
1123 display.renderQ_push({
1124 'type': 'copy',
1125 'old_x': ws.rQshift16(),
1126 'old_y': ws.rQshift16(),
1127 'x': FBU.x,
1128 'y': FBU.y,
1129 'width': FBU.width,
1130 'height': FBU.height});
a7a89626
JM
1131 FBU.rects -= 1;
1132 FBU.bytes = 0;
42b2246c 1133 return true;
8db09746
JM
1134};
1135
1136encHandlers.RRE = function display_rre() {
72f1348b 1137 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
8db09746 1138 var color, x, y, width, height, chunk;
a7a89626 1139
a7a89626 1140 if (FBU.subrects === 0) {
72f1348b
JM
1141 if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
1142 FBU.subrects = ws.rQshift32();
1143 color = ws.rQshiftBytes(fb_Bpp); // Background
d890e864 1144 display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
a7a89626 1145 }
72f1348b
JM
1146 while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {
1147 color = ws.rQshiftBytes(fb_Bpp);
1148 x = ws.rQshift16();
1149 y = ws.rQshift16();
1150 width = ws.rQshift16();
1151 height = ws.rQshift16();
d890e864 1152 display.fillRect(FBU.x + x, FBU.y + y, width, height, color);
a7a89626
JM
1153 FBU.subrects -= 1;
1154 }
1155 //Util.Debug(" display_rre: rects: " + FBU.rects +
1156 // ", FBU.subrects: " + FBU.subrects);
1157
1158 if (FBU.subrects > 0) {
8db09746
JM
1159 chunk = Math.min(rre_chunk_sz, FBU.subrects);
1160 FBU.bytes = (fb_Bpp + 8) * chunk;
a7a89626
JM
1161 } else {
1162 FBU.rects -= 1;
1163 FBU.bytes = 0;
1164 }
1165 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
42b2246c 1166 return true;
8db09746 1167};
a7a89626 1168
8db09746 1169encHandlers.HEXTILE = function display_hextile() {
a7a89626 1170 //Util.Debug(">> display_hextile");
490d471c 1171 var subencoding, subrects, color, cur_tile,
72f1348b
JM
1172 tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
1173 rQ = ws.get_rQ(), rQi = ws.get_rQi();
a7a89626
JM
1174
1175 if (FBU.tiles === 0) {
1176 FBU.tiles_x = Math.ceil(FBU.width/16);
1177 FBU.tiles_y = Math.ceil(FBU.height/16);
1178 FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
1179 FBU.tiles = FBU.total_tiles;
1180 }
1181
72f1348b 1182 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
a7a89626
JM
1183 while (FBU.tiles > 0) {
1184 FBU.bytes = 1;
72f1348b 1185 if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
9c57ac39 1186 subencoding = rQ[rQi]; // Peek
a7a89626 1187 if (subencoding > 30) { // Raw
ce2b6909 1188 fail("Disconnected: illegal hextile subencoding " + subencoding);
72f1348b 1189 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
42b2246c 1190 return false;
a7a89626
JM
1191 }
1192 subrects = 0;
1193 cur_tile = FBU.total_tiles - FBU.tiles;
1194 tile_x = cur_tile % FBU.tiles_x;
1195 tile_y = Math.floor(cur_tile / FBU.tiles_x);
1196 x = FBU.x + tile_x * 16;
1197 y = FBU.y + tile_y * 16;
1198 w = Math.min(16, (FBU.x + FBU.width) - x);
1199 h = Math.min(16, (FBU.y + FBU.height) - y);
1200
1201 /* Figure out how much we are expecting */
1202 if (subencoding & 0x01) { // Raw
1203 //Util.Debug(" Raw subencoding");
8db09746 1204 FBU.bytes += w * h * fb_Bpp;
a7a89626
JM
1205 } else {
1206 if (subencoding & 0x02) { // Background
8db09746 1207 FBU.bytes += fb_Bpp;
a7a89626
JM
1208 }
1209 if (subencoding & 0x04) { // Foreground
8db09746 1210 FBU.bytes += fb_Bpp;
a7a89626
JM
1211 }
1212 if (subencoding & 0x08) { // AnySubrects
1213 FBU.bytes += 1; // Since we aren't shifting it off
72f1348b 1214 if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
9c57ac39 1215 subrects = rQ[rQi + FBU.bytes-1]; // Peek
a7a89626 1216 if (subencoding & 0x10) { // SubrectsColoured
8db09746 1217 FBU.bytes += subrects * (fb_Bpp + 2);
a7a89626
JM
1218 } else {
1219 FBU.bytes += subrects * 2;
1220 }
1221 }
1222 }
1223
67b4e987
JM
1224 /*
1225 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1226 " (" + tile_x + "," + tile_y + ")" +
1227 " [" + x + "," + y + "]@" + w + "x" + h +
1228 ", subenc:" + subencoding +
1229 "(last: " + FBU.lastsubencoding + "), subrects:" +
1230 subrects +
72f1348b
JM
1231 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
1232 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
1233 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
67b4e987 1234 */
72f1348b 1235 if (ws.rQwait("hextile", FBU.bytes)) { return false; }
a7a89626
JM
1236
1237 /* We know the encoding and have a whole tile */
9c57ac39
JM
1238 FBU.subencoding = rQ[rQi];
1239 rQi += 1;
a7a89626
JM
1240 if (FBU.subencoding === 0) {
1241 if (FBU.lastsubencoding & 0x01) {
1242 /* Weird: ignore blanks after RAW */
1243 Util.Debug(" Ignoring blank after RAW");
1244 } else {
d890e864 1245 display.fillRect(x, y, w, h, FBU.background);
a7a89626
JM
1246 }
1247 } else if (FBU.subencoding & 0x01) { // Raw
d890e864 1248 display.blitImage(x, y, w, h, rQ, rQi);
9c57ac39 1249 rQi += FBU.bytes - 1;
a7a89626
JM
1250 } else {
1251 if (FBU.subencoding & 0x02) { // Background
9c57ac39
JM
1252 FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
1253 rQi += fb_Bpp;
a7a89626
JM
1254 }
1255 if (FBU.subencoding & 0x04) { // Foreground
9c57ac39
JM
1256 FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
1257 rQi += fb_Bpp;
a7a89626
JM
1258 }
1259
490d471c 1260 display.startTile(x, y, w, h, FBU.background);
a7a89626 1261 if (FBU.subencoding & 0x08) { // AnySubrects
9c57ac39
JM
1262 subrects = rQ[rQi];
1263 rQi += 1;
a7a89626
JM
1264 for (s = 0; s < subrects; s += 1) {
1265 if (FBU.subencoding & 0x10) { // SubrectsColoured
9c57ac39
JM
1266 color = rQ.slice(rQi, rQi + fb_Bpp);
1267 rQi += fb_Bpp;
a7a89626
JM
1268 } else {
1269 color = FBU.foreground;
1270 }
9c57ac39
JM
1271 xy = rQ[rQi];
1272 rQi += 1;
a7a89626
JM
1273 sx = (xy >> 4);
1274 sy = (xy & 0x0f);
1275
9c57ac39
JM
1276 wh = rQ[rQi];
1277 rQi += 1;
a7a89626
JM
1278 sw = (wh >> 4) + 1;
1279 sh = (wh & 0x0f) + 1;
1280
490d471c 1281 display.subTile(sx, sy, sw, sh, color);
a7a89626
JM
1282 }
1283 }
490d471c 1284 display.finishTile();
a7a89626 1285 }
72f1348b 1286 ws.set_rQi(rQi);
a7a89626
JM
1287 FBU.lastsubencoding = FBU.subencoding;
1288 FBU.bytes = 0;
1289 FBU.tiles -= 1;
1290 }
1291
1292 if (FBU.tiles === 0) {
1293 FBU.rects -= 1;
1294 }
1295
1296 //Util.Debug("<< display_hextile");
42b2246c 1297 return true;
8db09746 1298};
a7a89626
JM
1299
1300
d065cad9
JM
1301// Get 'compact length' header and data size
1302getTightCLength = function (arr) {
1303 var header = 1, data = 0;
1304 data += arr[0] & 0x7f;
1305 if (arr[0] & 0x80) {
1306 header += 1;
1307 data += (arr[1] & 0x7f) << 7;
1308 if (arr[1] & 0x80) {
1309 header += 1;
1310 data += arr[2] << 14;
1311 }
1312 }
1313 return [header, data];
1314};
1315
c0c20581 1316function display_tight(isTightPNG) {
ce86f5c9 1317 //Util.Debug(">> display_tight");
9b75bcaa 1318
d065cad9 1319 if (fb_depth === 1) {
9b75bcaa
MT
1320 fail("Tight protocol handler only implements true color mode");
1321 }
1322
d065cad9 1323 var ctl, cmode, clength, color, img, data;
de84e098
M
1324 var filterId = -1, resetStreams = 0, streamId = -1;
1325 var rQ = ws.get_rQ(), rQi = ws.get_rQi();
1326
1327 FBU.bytes = 1; // compression-control byte
1328 if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
de84e098 1329
a820f126
MT
1330 var checksum = function(data) {
1331 var sum=0, i;
1332 for (i=0; i<data.length;i++) {
1333 sum += data[i];
1334 if (sum > 65536) sum -= 65536;
1335 }
1336 return sum;
1337 }
d065cad9 1338
de84e098 1339 var decompress = function(data) {
9b75bcaa
MT
1340 for (var i=0; i<4; i++) {
1341 if ((resetStreams >> i) & 1) {
1342 FBU.zlibs[i].reset();
1343 Util.Info("Reset zlib stream " + i);
1344 }
1345 }
de84e098 1346 var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
d065cad9 1347 if (uncompressed.status !== 0) {
9b75bcaa 1348 Util.Error("Invalid data in zlib stream");
d065cad9
JM
1349 }
1350 //Util.Warn("Decompressed " + data.length + " to " +
1351 // uncompressed.data.length + " checksums " +
a14b8fae 1352 // checksum(data) + ":" + checksum(uncompressed.data));
d065cad9 1353
de84e098
M
1354 return uncompressed.data;
1355 }
1356
1357 var handlePalette = function() {
1358 var numColors = rQ[rQi + 2] + 1;
1359 var paletteSize = numColors * fb_depth;
1360 FBU.bytes += paletteSize;
de84e098
M
1361 if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
1362
de84e098
M
1363 var bpp = (numColors <= 2) ? 1 : 8;
1364 var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
6fbc3748
MT
1365 var raw = false;
1366 if (rowSize * FBU.height < 12) {
1367 raw = true;
de84e098 1368 clength = [0, rowSize * FBU.height];
d065cad9
JM
1369 } else {
1370 clength = getTightCLength(ws.rQslice(3 + paletteSize,
1371 3 + paletteSize + 3));
6fbc3748 1372 }
de84e098
M
1373 FBU.bytes += clength[0] + clength[1];
1374 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
1375
1376 // Shift ctl, filter id, num colors, palette entries, and clength off
c514fd5e 1377 ws.rQshiftBytes(3);
d065cad9 1378 var palette = ws.rQshiftBytes(paletteSize);
c514fd5e 1379 ws.rQshiftBytes(clength[0]);
d065cad9
JM
1380
1381 if (raw) {
de84e098 1382 data = ws.rQshiftBytes(clength[1]);
d065cad9 1383 } else {
de84e098 1384 data = decompress(ws.rQshiftBytes(clength[1]));
d065cad9 1385 }
5ca5e2d8 1386
c514fd5e 1387 // Convert indexed (palette based) image data to RGB
a14b8fae 1388 // TODO: reduce number of calculations inside loop
5ca5e2d8 1389 var dest = [];
d065cad9
JM
1390 var x, y, b, w, w1, dp, sp;
1391 if (numColors === 2) {
1392 w = Math.floor((FBU.width + 7) / 8);
1393 w1 = Math.floor(FBU.width / 8);
1394 for (y = 0; y < FBU.height; y++) {
1395 for (x = 0; x < w1; x++) {
1396 for (b = 7; b >= 0; b--) {
1397 dp = (y*FBU.width + x*8 + 7-b) * 3;
1398 sp = (data[y*w + x] >> b & 1) * 3;
1399 dest[dp ] = palette[sp ];
1400 dest[dp+1] = palette[sp+1];
1401 dest[dp+2] = palette[sp+2];
1402 }
1403 }
1404 for (b = 7; b >= 8 - FBU.width % 8; b--) {
1405 dp = (y*FBU.width + x*8 + 7-b) * 3;
1406 sp = (data[y*w + x] >> b & 1) * 3;
1407 dest[dp ] = palette[sp ];
1408 dest[dp+1] = palette[sp+1];
1409 dest[dp+2] = palette[sp+2];
1410 }
5ca5e2d8 1411 }
5ca5e2d8 1412 } else {
d065cad9
JM
1413 for (y = 0; y < FBU.height; y++) {
1414 for (x = 0; x < FBU.width; x++) {
1415 dp = (y*FBU.width + x) * 3;
1416 sp = data[y*FBU.width + x] * 3;
1417 dest[dp ] = palette[sp ];
1418 dest[dp+1] = palette[sp+1];
1419 dest[dp+2] = palette[sp+2];
1420 }
5ca5e2d8 1421 }
5ca5e2d8 1422 }
d065cad9 1423
34d8b844
JM
1424 display.renderQ_push({
1425 'type': 'blitRgb',
1426 'data': dest,
5ca5e2d8
MT
1427 'x': FBU.x,
1428 'y': FBU.y,
1429 'width': FBU.width,
1430 'height': FBU.height});
de84e098
M
1431 return true;
1432 }
1433
1434 var handleCopy = function() {
6fbc3748 1435 var raw = false;
de84e098 1436 var uncompressedSize = FBU.width * FBU.height * fb_depth;
6fbc3748
MT
1437 if (uncompressedSize < 12) {
1438 raw = true;
de84e098 1439 clength = [0, uncompressedSize];
d065cad9
JM
1440 } else {
1441 clength = getTightCLength(ws.rQslice(1, 4));
6fbc3748 1442 }
c514fd5e 1443 FBU.bytes = 1 + clength[0] + clength[1];
de84e098
M
1444 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
1445
c514fd5e
MT
1446 // Shift ctl, clength off
1447 ws.rQshiftBytes(1 + clength[0]);
1448
d065cad9 1449 if (raw) {
de84e098 1450 data = ws.rQshiftBytes(clength[1]);
d065cad9 1451 } else {
de84e098 1452 data = decompress(ws.rQshiftBytes(clength[1]));
d065cad9 1453 }
de84e098 1454
34d8b844
JM
1455 display.renderQ_push({
1456 'type': 'blitRgb',
1457 'data': data,
de84e098
M
1458 'x': FBU.x,
1459 'y': FBU.y,
1460 'width': FBU.width,
1461 'height': FBU.height});
1462 return true;
1463 }
d065cad9 1464
de84e098 1465 ctl = ws.rQpeek8();
d065cad9 1466
de84e098
M
1467 // Keep tight reset bits
1468 resetStreams = ctl & 0xF;
d065cad9 1469
de84e098
M
1470 // Figure out filter
1471 ctl = ctl >> 4;
1472 streamId = ctl & 0x3;
1473
d065cad9
JM
1474 if (ctl === 0x08) cmode = "fill";
1475 else if (ctl === 0x09) cmode = "jpeg";
c0c20581 1476 else if (ctl === 0x0A) cmode = "png";
d065cad9
JM
1477 else if (ctl & 0x04) cmode = "filter";
1478 else if (ctl < 0x04) cmode = "copy";
dc6e501f 1479 else return fail("Illegal tight compression received, ctl: " + ctl);
d065cad9 1480
c0c20581 1481 if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
dc6e501f 1482 return fail("filter/copy received in tightPNG mode");
c0c20581
JM
1483 }
1484
de84e098
M
1485 switch (cmode) {
1486 // fill uses fb_depth because TPIXELs drop the padding byte
d065cad9
JM
1487 case "fill": FBU.bytes += fb_depth; break; // TPIXEL
1488 case "jpeg": FBU.bytes += 3; break; // max clength
c0c20581 1489 case "png": FBU.bytes += 3; break; // max clength
d065cad9
JM
1490 case "filter": FBU.bytes += 2; break; // filter id + num colors if palette
1491 case "copy": break;
de84e098
M
1492 }
1493
1494 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
1495
1496 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
ce86f5c9 1497 //Util.Debug(" cmode: " + cmode);
de84e098
M
1498
1499 // Determine FBU.bytes
1500 switch (cmode) {
1501 case "fill":
1502 ws.rQshift8(); // shift off ctl
1503 color = ws.rQshiftBytes(fb_depth);
34d8b844 1504 display.renderQ_push({
de84e098 1505 'type': 'fill',
de84e098
M
1506 'x': FBU.x,
1507 'y': FBU.y,
1508 'width': FBU.width,
1509 'height': FBU.height,
2cedf483 1510 'color': [color[2], color[1], color[0]] });
de84e098 1511 break;
c0c20581 1512 case "png":
de84e098 1513 case "jpeg":
d065cad9 1514 clength = getTightCLength(ws.rQslice(1, 4));
de84e098
M
1515 FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
1516 if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
1517
1518 // We have everything, render it
d065cad9
JM
1519 //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
1520 // clength[0] + ", clength[1]: " + clength[1]);
de84e098
M
1521 ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
1522 img = new Image();
34d8b844
JM
1523 img.src = "data:image/" + cmode +
1524 extract_data_uri(ws.rQshiftBytes(clength[1]));
1525 display.renderQ_push({
de84e098
M
1526 'type': 'img',
1527 'img': img,
1528 'x': FBU.x,
1529 'y': FBU.y});
de84e098
M
1530 img = null;
1531 break;
1532 case "filter":
1533 filterId = rQ[rQi + 1];
d065cad9 1534 if (filterId === 1) {
de84e098
M
1535 if (!handlePalette()) { return false; }
1536 } else {
1537 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
1538 // Filter 2, Gradient is valid but not used if jpeg is enabled
d065cad9 1539 throw("Unsupported tight subencoding received, filter: " + filterId);
de84e098
M
1540 }
1541 break;
1542 case "copy":
1543 if (!handleCopy()) { return false; }
1544 break;
1545 }
d065cad9 1546
de84e098
M
1547 FBU.bytes = 0;
1548 FBU.rects -= 1;
1549 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1550 //Util.Debug("<< display_tight_png");
1551 return true;
c0c20581 1552}
a7a89626 1553
8db09746 1554extract_data_uri = function(arr) {
a7a89626
JM
1555 //var i, stra = [];
1556 //for (i=0; i< arr.length; i += 1) {
1557 // stra.push(String.fromCharCode(arr[i]));
1558 //}
1559 //return "," + escape(stra.join(''));
1560 return ";base64," + Base64.encode(arr);
8db09746 1561};
a7a89626 1562
c0c20581
JM
1563encHandlers.TIGHT = function () { return display_tight(false); };
1564encHandlers.TIGHT_PNG = function () { return display_tight(true); };
1565
a09a75e8 1566encHandlers.last_rect = function last_rect() {
a0726a4b 1567 //Util.Debug(">> last_rect");
a09a75e8 1568 FBU.rects = 0;
a0726a4b 1569 //Util.Debug("<< last_rect");
a09a75e8
JM
1570 return true;
1571};
1572
8db09746
JM
1573encHandlers.DesktopSize = function set_desktopsize() {
1574 Util.Debug(">> set_desktopsize");
1575 fb_width = FBU.width;
1576 fb_height = FBU.height;
d890e864 1577 display.resize(fb_width, fb_height);
8db09746
JM
1578 timing.fbu_rt_start = (new Date()).getTime();
1579 // Send a new non-incremental request
832c7445 1580 ws.send(fbUpdateRequests());
a7a89626 1581
8db09746
JM
1582 FBU.bytes = 0;
1583 FBU.rects -= 1;
a7a89626 1584
8db09746
JM
1585 Util.Debug("<< set_desktopsize");
1586 return true;
1587};
a7a89626 1588
8db09746
JM
1589encHandlers.Cursor = function set_cursor() {
1590 var x, y, w, h, pixelslength, masklength;
a0726a4b 1591 Util.Debug(">> set_cursor");
8db09746
JM
1592 x = FBU.x; // hotspot-x
1593 y = FBU.y; // hotspot-y
1594 w = FBU.width;
1595 h = FBU.height;
a7a89626 1596
8db09746
JM
1597 pixelslength = w * h * fb_Bpp;
1598 masklength = Math.floor((w + 7) / 8) * h;
a7a89626 1599
60440cee 1600 FBU.bytes = pixelslength + masklength;
72f1348b 1601 if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
a7a89626 1602
8db09746 1603 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
a7a89626 1604
d890e864 1605 display.changeCursor(ws.rQshiftBytes(pixelslength),
72f1348b 1606 ws.rQshiftBytes(masklength),
8db09746 1607 x, y, w, h);
a7a89626 1608
8db09746
JM
1609 FBU.bytes = 0;
1610 FBU.rects -= 1;
a7a89626 1611
a0726a4b 1612 Util.Debug("<< set_cursor");
8db09746
JM
1613 return true;
1614};
a7a89626 1615
8db09746
JM
1616encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
1617 Util.Error("Server sent jpeg_quality pseudo-encoding");
1618};
a7a89626 1619
8db09746
JM
1620encHandlers.compress_lo = function set_compress_level() {
1621 Util.Error("Server sent compress level pseudo-encoding");
1622};
a7a89626 1623
8db09746
JM
1624/*
1625 * Client message routines
1626 */
a7a89626 1627
8db09746
JM
1628pixelFormat = function() {
1629 //Util.Debug(">> pixelFormat");
1630 var arr;
1631 arr = [0]; // msg-type
1632 arr.push8(0); // padding
1633 arr.push8(0); // padding
1634 arr.push8(0); // padding
a7a89626 1635
8db09746
JM
1636 arr.push8(fb_Bpp * 8); // bits-per-pixel
1637 arr.push8(fb_depth * 8); // depth
1638 arr.push8(0); // little-endian
1639 arr.push8(conf.true_color ? 1 : 0); // true-color
a7a89626 1640
8db09746
JM
1641 arr.push16(255); // red-max
1642 arr.push16(255); // green-max
1643 arr.push16(255); // blue-max
ac99a1f7 1644 arr.push8(16); // red-shift
8db09746 1645 arr.push8(8); // green-shift
ac99a1f7 1646 arr.push8(0); // blue-shift
a7a89626 1647
8db09746
JM
1648 arr.push8(0); // padding
1649 arr.push8(0); // padding
1650 arr.push8(0); // padding
1651 //Util.Debug("<< pixelFormat");
1652 return arr;
1653};
a7a89626 1654
8db09746
JM
1655clientEncodings = function() {
1656 //Util.Debug(">> clientEncodings");
1657 var arr, i, encList = [];
a7a89626 1658
8db09746
JM
1659 for (i=0; i<encodings.length; i += 1) {
1660 if ((encodings[i][0] === "Cursor") &&
1661 (! conf.local_cursor)) {
1662 Util.Debug("Skipping Cursor pseudo-encoding");
1663 } else {
1664 //Util.Debug("Adding encoding: " + encodings[i][0]);
1665 encList.push(encodings[i][1]);
1666 }
1667 }
a7a89626 1668
8db09746
JM
1669 arr = [2]; // msg-type
1670 arr.push8(0); // padding
a7a89626 1671
8db09746
JM
1672 arr.push16(encList.length); // encoding count
1673 for (i=0; i < encList.length; i += 1) {
1674 arr.push32(encList[i]);
1675 }
1676 //Util.Debug("<< clientEncodings: " + arr);
1677 return arr;
1678};
a7a89626 1679
8db09746
JM
1680fbUpdateRequest = function(incremental, x, y, xw, yw) {
1681 //Util.Debug(">> fbUpdateRequest");
e0b23efe
JM
1682 if (typeof(x) === "undefined") { x = 0; }
1683 if (typeof(y) === "undefined") { y = 0; }
1684 if (typeof(xw) === "undefined") { xw = fb_width; }
1685 if (typeof(yw) === "undefined") { yw = fb_height; }
8db09746
JM
1686 var arr;
1687 arr = [3]; // msg-type
1688 arr.push8(incremental);
1689 arr.push16(x);
1690 arr.push16(y);
1691 arr.push16(xw);
1692 arr.push16(yw);
1693 //Util.Debug("<< fbUpdateRequest");
1694 return arr;
1695};
a7a89626 1696
832c7445
JM
1697// Based on clean/dirty areas, generate requests to send
1698fbUpdateRequests = function() {
1699 var cleanDirty = display.getCleanDirtyReset(),
1700 arr = [], i, cb, db;
1701
1702 cb = cleanDirty.cleanBox;
1703 if (cb.w > 0 && cb.h > 0) {
1704 // Request incremental for clean box
1705 arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
1706 }
1707 for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
1708 db = cleanDirty.dirtyBoxes[i];
1709 // Force all (non-incremental for dirty box
1710 arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
1711 }
1712 return arr;
1713};
1714
1715
1716
8db09746
JM
1717keyEvent = function(keysym, down) {
1718 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1719 var arr;
1720 arr = [4]; // msg-type
1721 arr.push8(down);
1722 arr.push16(0);
1723 arr.push32(keysym);
1724 //Util.Debug("<< keyEvent");
1725 return arr;
1726};
a7a89626 1727
8db09746
JM
1728pointerEvent = function(x, y) {
1729 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1730 // " , mask: " + mouse_buttonMask);
1731 var arr;
1732 arr = [5]; // msg-type
1733 arr.push8(mouse_buttonMask);
1734 arr.push16(x);
1735 arr.push16(y);
1736 //Util.Debug("<< pointerEvent");
1737 return arr;
1738};
a7a89626 1739
8db09746
JM
1740clientCutText = function(text) {
1741 //Util.Debug(">> clientCutText");
67b4e987 1742 var arr, i, n;
8db09746
JM
1743 arr = [6]; // msg-type
1744 arr.push8(0); // padding
1745 arr.push8(0); // padding
1746 arr.push8(0); // padding
1747 arr.push32(text.length);
67b4e987
JM
1748 n = text.length;
1749 for (i=0; i < n; i+=1) {
1750 arr.push(text.charCodeAt(i));
1751 }
8db09746
JM
1752 //Util.Debug("<< clientCutText:" + arr);
1753 return arr;
1754};
a7a89626 1755
a7a89626 1756
a7a89626 1757
8db09746
JM
1758//
1759// Public API interface functions
1760//
a7a89626 1761
12acb663 1762that.connect = function(host, port, password, path) {
8db09746 1763 //Util.Debug(">> connect");
a7a89626 1764
8db09746
JM
1765 rfb_host = host;
1766 rfb_port = port;
1767 rfb_password = (password !== undefined) ? password : "";
6209639f 1768 rfb_path = (path !== undefined) ? path : "";
a7a89626 1769
8db09746 1770 if ((!rfb_host) || (!rfb_port)) {
ce2b6909 1771 return fail("Must set host and port");
a7a89626 1772 }
a7a89626 1773
8db09746
JM
1774 updateState('connect');
1775 //Util.Debug("<< connect");
a7a89626 1776
8db09746 1777};
a7a89626 1778
8db09746
JM
1779that.disconnect = function() {
1780 //Util.Debug(">> disconnect");
e3efeb32 1781 updateState('disconnect', 'Disconnecting');
8db09746
JM
1782 //Util.Debug("<< disconnect");
1783};
a7a89626 1784
8db09746
JM
1785that.sendPassword = function(passwd) {
1786 rfb_password = passwd;
1787 rfb_state = "Authentication";
1788 setTimeout(init_msg, 1);
1789};
1790
1791that.sendCtrlAltDel = function() {
06a9ef0c 1792 if (rfb_state !== "normal" || conf.view_only) { return false; }
8db09746
JM
1793 Util.Info("Sending Ctrl-Alt-Del");
1794 var arr = [];
1795 arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
1796 arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
1797 arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
1798 arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
1799 arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
1800 arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
832c7445 1801 arr = arr.concat(fbUpdateRequests());
72f1348b 1802 ws.send(arr);
8db09746
JM
1803};
1804
0a1184bd
JM
1805// Send a key press. If 'down' is not specified then send a down key
1806// followed by an up key.
1807that.sendKey = function(code, down) {
06a9ef0c 1808 if (rfb_state !== "normal" || conf.view_only) { return false; }
0a1184bd
JM
1809 var arr = [];
1810 if (typeof down !== 'undefined') {
1811 Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
1812 arr = arr.concat(keyEvent(code, down ? 1 : 0));
1813 } else {
1814 Util.Info("Sending key code (down + up): " + code);
1815 arr = arr.concat(keyEvent(code, 1));
1816 arr = arr.concat(keyEvent(code, 0));
1817 }
832c7445 1818 arr = arr.concat(fbUpdateRequests());
72f1348b 1819 ws.send(arr);
0a1184bd
JM
1820};
1821
8db09746
JM
1822that.clipboardPasteFrom = function(text) {
1823 if (rfb_state !== "normal") { return; }
1824 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
72f1348b 1825 ws.send(clientCutText(text));
8db09746
JM
1826 //Util.Debug("<< clipboardPasteFrom");
1827};
1828
fa8f14d5 1829// Override internal functions for testing
72f1348b 1830that.testMode = function(override_send) {
8db09746 1831 test_mode = true;
fa8f14d5 1832 that.recv_message = ws.testMode(override_send);
8db09746
JM
1833
1834 checkEvents = function () { /* Stub Out */ };
1835 that.connect = function(host, port, password) {
1836 rfb_host = host;
1837 rfb_port = port;
1838 rfb_password = password;
1839 updateState('ProtocolVersion', "Starting VNC handshake");
1840 };
1841};
1842
1843
1844return constructor(); // Return the public API interface
a7a89626 1845
8db09746 1846} // End of RFB()