]>
Commit | Line | Data |
---|---|---|
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 | 16 | function RFB(defaults) { |
d890e864 | 17 | "use strict"; |
8db09746 | 18 | |
5210330a JM |
19 | var 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 |
132 | Util.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 | 178 | that.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 |
191 | that.get_display = function() { return display; }; | |
a7a89626 | 192 | |
5210330a | 193 | that.get_keyboard = function() { return keyboard; }; |
8db09746 | 194 | |
5210330a | 195 | that.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 | 205 | function 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 |
289 | function 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 | 311 | init_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 | |
338 | print_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 |
382 | updateState = 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 | |
522 | fail = function(msg) { | |
ce2b6909 JM |
523 | updateState('failed', msg); |
524 | return false; | |
43cf7bd8 | 525 | }; |
8db09746 | 526 | |
43cf7bd8 | 527 | handle_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 | |
561 | function 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 | ||
569 | function 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 | |
585 | checkEvents = 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 | 599 | keyPress = 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 | 609 | mouseButton = 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 | 636 | mouseMove = 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 | |
665 | init_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 */ | |
919 | normal_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 | 978 | framebufferUpdate = 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 | 1096 | encHandlers.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 | 1124 | encHandlers.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 | ||
1143 | encHandlers.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 | 1176 | encHandlers.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 |
1309 | getTightCLength = 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 | 1323 | function 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 | 1561 | extract_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 |
1570 | encHandlers.TIGHT = function () { return display_tight(false); }; |
1571 | encHandlers.TIGHT_PNG = function () { return display_tight(true); }; | |
1572 | ||
a09a75e8 | 1573 | encHandlers.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 |
1580 | encHandlers.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 |
1597 | encHandlers.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 |
1624 | encHandlers.JPEG_quality_lo = function set_jpeg_quality() { |
1625 | Util.Error("Server sent jpeg_quality pseudo-encoding"); | |
1626 | }; | |
a7a89626 | 1627 | |
8db09746 JM |
1628 | encHandlers.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 |
1636 | pixelFormat = 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 |
1663 | clientEncodings = 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 |
1688 | fbUpdateRequest = 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 |
1706 | fbUpdateRequests = 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 |
1725 | keyEvent = 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 |
1736 | pointerEvent = 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 |
1748 | clientCutText = 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 | 1770 | that.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 |
1787 | that.disconnect = function() { |
1788 | //Util.Debug(">> disconnect"); | |
e3efeb32 | 1789 | updateState('disconnect', 'Disconnecting'); |
8db09746 JM |
1790 | //Util.Debug("<< disconnect"); |
1791 | }; | |
a7a89626 | 1792 | |
8db09746 JM |
1793 | that.sendPassword = function(passwd) { |
1794 | rfb_password = passwd; | |
1795 | rfb_state = "Authentication"; | |
1796 | setTimeout(init_msg, 1); | |
1797 | }; | |
1798 | ||
1799 | that.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. | |
1815 | that.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 |
1830 | that.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 | 1838 | that.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 | ||
1852 | return constructor(); // Return the public API interface | |
a7a89626 | 1853 | |
8db09746 | 1854 | } // End of RFB() |