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