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