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