]>
Commit | Line | Data |
---|---|---|
97bfe5ba | 1 | /* |
71d2426a | 2 | * noVNC: HTML5 VNC client |
af6b17ce JM |
3 | * Copyright (C) 2010 Joel Martin |
4 | * Licensed under LGPL-3 (see LICENSE.LGPL-3) | |
ded9dfae JM |
5 | * |
6 | * See README.md for usage and integration instructions. | |
97bfe5ba | 7 | */ |
c4164bda JM |
8 | "use strict"; |
9 | ||
91308399 | 10 | /*global window, WebSocket, $, Browser, Canvas, VNC_uri_prefix Base64, DES */ |
c4164bda JM |
11 | |
12 | // Globals defined here | |
56ec48be | 13 | var VNC_native_ws, RFB; |
97bfe5ba | 14 | |
97bfe5ba JM |
15 | /* |
16 | * Load supporting scripts | |
17 | */ | |
c4164bda JM |
18 | (function () { |
19 | var extra, start, end; | |
91308399 JM |
20 | |
21 | if (typeof VNC_uri_prefix === "undefined") { VNC_uri_prefix=""; } | |
c4164bda | 22 | extra = ""; |
91308399 | 23 | start = "<script src='" + VNC_uri_prefix; |
c4164bda JM |
24 | end = "'><\/script>"; |
25 | ||
26 | // Uncomment to activate firebug lite | |
27 | //extra += start + "http://getfirebug.com/releases/lite/1.2/" + | |
28 | // firebug-lite-compressed.js" + end; | |
29 | ||
30 | extra += start + "include/mootools.js" + end; | |
31 | extra += start + "include/base64.js" + end; | |
32 | extra += start + "include/des.js" + end; | |
33 | extra += start + "include/util.js" + end; | |
f9583f1f | 34 | extra += start + "include/canvas.js" + end; |
c4164bda JM |
35 | |
36 | /* If no builtin websockets then load web_socket.js */ | |
37 | if (window.WebSocket) { | |
38 | VNC_native_ws = true; | |
39 | } else { | |
40 | VNC_native_ws = false; | |
41 | extra += start + "include/web-socket-js/swfobject.js" + end; | |
42 | extra += start + "include/web-socket-js/FABridge.js" + end; | |
43 | extra += start + "include/web-socket-js/web_socket.js" + end; | |
44 | } | |
45 | document.write(extra); | |
46 | }()); | |
97bfe5ba | 47 | |
65e27ddd | 48 | /* |
cc0410a3 | 49 | * RFB namespace |
65e27ddd JM |
50 | */ |
51 | ||
64ab5c4d JM |
52 | RFB = { |
53 | ||
91308399 JM |
54 | /* |
55 | * External interface variables and methods | |
56 | */ | |
57 | clipboardFocus : false, | |
58 | ||
59 | setUpdateState: function(externalUpdateState) { | |
60 | RFB.externalUpdateState = externalUpdateState; | |
61 | }, | |
62 | ||
63 | setClipboardReceive: function(clipReceive) { | |
64 | RFB.clipboardCopyTo = clipReceive; | |
65 | }, | |
66 | ||
67 | setCanvasID: function(canvasID) { | |
68 | RFB.canvasID = canvasID; | |
69 | }, | |
70 | ||
71 | setPassword: function(passwd) { | |
72 | RFB.password = passwd; | |
73 | RFB.state = "Authentication"; | |
74 | setTimeout(RFB.init_msg, 1); | |
75 | }, | |
76 | ||
77 | load: function () { | |
78 | //console.log(">> load"); | |
79 | ||
80 | /* Load web-socket-js if no builtin WebSocket support */ | |
81 | if (VNC_native_ws) { | |
82 | console.log("Using native WebSockets"); | |
83 | RFB.updateState('disconnected', 'Disconnected'); | |
84 | } else { | |
85 | console.log("Using web-socket-js flash bridge"); | |
86 | if ((! Browser.Plugins.Flash) || | |
87 | (Browser.Plugins.Flash.version < 9)) { | |
88 | RFB.updateState('failed', "WebSockets or Adobe Flash is required"); | |
89 | } else if (location.href.substr(0, 7) === "file://") { | |
90 | RFB.updateState('failed', | |
91 | "'file://' URL is incompatible with Adobe Flash"); | |
92 | } else { | |
93 | WebSocket.__swfLocation = VNC_uri_prefix + | |
94 | "include/web-socket-js/WebSocketMain.swf"; | |
95 | WebSocket.__initialize(); | |
96 | RFB.use_seq = true; | |
97 | RFB.updateState('disconnected', 'Disconnected'); | |
98 | } | |
99 | } | |
100 | ||
101 | //console.log("<< load"); | |
102 | }, | |
103 | ||
104 | connect: function (host, port, password, encrypt, true_color) { | |
105 | console.log(">> connect"); | |
106 | ||
107 | RFB.host = host; | |
108 | RFB.port = port; | |
109 | RFB.password = (password !== undefined) ? password : ""; | |
110 | RFB.encrypt = (encrypt !== undefined) ? encrypt : true; | |
111 | if ((RFB.encrypt === "0") || | |
112 | (RFB.encrypt === "no") || | |
113 | (RFB.encrypt === "false")) { | |
114 | RFB.encrypt = false; | |
115 | } | |
116 | RFB.true_color = (true_color !== undefined) ? true_color: true; | |
117 | if ((RFB.true_color === "0") || | |
118 | (RFB.true_color === "no") || | |
119 | (RFB.true_color === "false")) { | |
120 | RFB.true_color = false; | |
121 | } | |
122 | ||
123 | if ((!RFB.host) || (!RFB.port)) { | |
124 | updateState('disconnected', "Must set host and port"); | |
125 | return; | |
126 | } | |
127 | ||
128 | RFB.init_vars(); | |
129 | ||
130 | if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { | |
131 | RFB.ws.close(); | |
132 | } | |
133 | RFB.init_ws(); | |
134 | ||
135 | RFB.updateState('ProtocolVersion'); | |
136 | console.log("<< connect"); | |
137 | ||
138 | }, | |
139 | ||
140 | disconnect: function () { | |
141 | console.log(">> disconnect"); | |
142 | if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { | |
143 | RFB.updateState('closed'); | |
144 | RFB.ws.onmessage = function (e) { return; }; | |
145 | RFB.ws.close(); | |
146 | } | |
147 | if (Canvas.ctx) { | |
148 | Canvas.stop(); | |
149 | if (! /__debug__$/i.test(document.location.href)) { | |
150 | Canvas.clear(); | |
151 | } | |
152 | } | |
153 | ||
154 | RFB.updateState('disconnected', 'Disconnected'); | |
155 | console.log("<< disconnect"); | |
156 | }, | |
157 | ||
158 | clipboardPasteFrom: function (text) { | |
159 | if (RFB.state !== "normal") { return; } | |
160 | //console.log(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); | |
161 | RFB.send_array(RFB.clientCutText(text)); | |
162 | //console.log("<< clipboardPasteFrom"); | |
163 | }, | |
164 | ||
165 | ||
166 | /* | |
167 | * Private variables and methods | |
168 | */ | |
169 | ||
56ec48be JM |
170 | ws : null, // Web Socket object |
171 | sendID : null, | |
172 | use_seq : false, | |
507b473a | 173 | b64encode : true, |
56ec48be JM |
174 | |
175 | // Receive and send queues | |
176 | RQ : [], // Receive Queue | |
177 | RQ_reorder : [], // Receive Queue re-order list | |
178 | RQ_seq_num : 0, // Expected sequence number | |
179 | SQ : "", // Send Queue | |
180 | ||
181 | // Frame buffer update state | |
182 | FBU : { | |
183 | rects : 0, | |
184 | subrects : 0, // RRE and HEXTILE | |
185 | lines : 0, // RAW | |
186 | tiles : 0, // HEXTILE | |
187 | bytes : 0, | |
188 | x : 0, | |
189 | y : 0, | |
190 | width : 0, | |
191 | height : 0, | |
192 | encoding : 0, | |
193 | subencoding : -1, | |
194 | background : null | |
195 | }, | |
cc0410a3 | 196 | |
d41c33e4 JM |
197 | true_color : false, |
198 | fb_Bpp : 4, | |
199 | fb_depth : 3, | |
200 | ||
56ec48be JM |
201 | max_version : 3.8, |
202 | version : 0, | |
203 | auth_scheme : '', | |
204 | state : 'disconnected', | |
205 | cuttext : 'none', // ServerCutText wait state | |
206 | ct_length : 0, | |
30059bdf | 207 | |
56ec48be JM |
208 | shared : 1, |
209 | check_rate : 217, | |
210 | req_rate : 1413, | |
211 | last_req : 0, | |
64ab5c4d | 212 | |
56ec48be JM |
213 | host : '', |
214 | port : 5900, | |
215 | password : '', | |
cc0410a3 | 216 | |
91308399 | 217 | canvasID : 'VNC_canvas', |
56ec48be JM |
218 | fb_width : 0, |
219 | fb_height : 0, | |
220 | fb_name : "", | |
56ec48be | 221 | rre_chunk : 100, |
cc0410a3 | 222 | |
56ec48be JM |
223 | timing : { |
224 | last_fbu : 0, | |
225 | fbu_total : 0, | |
753bde8f JM |
226 | fbu_total_cnt : 0, |
227 | full_fbu_total : 0, | |
c3996e24 JM |
228 | full_fbu_cnt : 0, |
229 | ||
230 | fbu_rt_start : 0, | |
231 | fbu_rt_total : 0, | |
232 | fbu_rt_cnt : 0, | |
753bde8f JM |
233 | }, |
234 | ||
97bfe5ba JM |
235 | /* Mouse state */ |
236 | mouse_buttonmask : 0, | |
237 | mouse_arr : [], | |
238 | ||
cc0410a3 JM |
239 | /* |
240 | * Server message handlers | |
241 | */ | |
242 | ||
65e27ddd | 243 | /* RFB/VNC initialisation */ |
484a4696 | 244 | init_msg: function () { |
753bde8f | 245 | console.log(">> init_msg [RFB.state '" + RFB.state + "']"); |
ef764d3b | 246 | |
67b24a90 JM |
247 | var RQ = RFB.RQ, strlen, reason, reason_len, |
248 | sversion, cversion, types, num_types, challenge, response, | |
c4164bda JM |
249 | bpp, depth, big_endian, true_color, name_length; |
250 | ||
56ec48be | 251 | //console.log("RQ (" + RQ.length + ") " + RQ); |
64ab5c4d | 252 | switch (RFB.state) { |
65e27ddd JM |
253 | |
254 | case 'ProtocolVersion' : | |
ef764d3b | 255 | if (RQ.length < 12) { |
c4164bda JM |
256 | RFB.updateState('failed', |
257 | "Disconnected: incomplete protocol version"); | |
ef764d3b JM |
258 | return; |
259 | } | |
67b24a90 JM |
260 | sversion = RQ.shiftStr(12).substr(4,7); |
261 | console.log("Server ProtocolVersion: " + sversion); | |
262 | switch (sversion) { | |
263 | case "003.003": RFB.version = 3.3; break; | |
264 | case "003.007": RFB.version = 3.7; break; | |
265 | case "003.008": RFB.version = 3.8; break; | |
266 | default: | |
267 | RFB.updateState('failed', | |
268 | "Invalid server version " + sversion); | |
269 | return; | |
270 | } | |
271 | if (RFB.version > RFB.max_version) { | |
272 | RFB.version = RFB.max_version; | |
65e27ddd | 273 | } |
67b24a90 JM |
274 | |
275 | cversion = "00" + parseInt(RFB.version,10) + | |
276 | ".00" + ((RFB.version * 10) % 10); | |
277 | RFB.send_string("RFB " + cversion + "\n"); | |
278 | RFB.updateState('Security', "Sent ProtocolVersion: " + sversion); | |
65e27ddd JM |
279 | break; |
280 | ||
ef764d3b | 281 | case 'Security' : |
67b24a90 | 282 | if (RFB.version >= 3.7) { |
c4164bda JM |
283 | num_types = RQ.shift8(); |
284 | if (num_types === 0) { | |
285 | strlen = RQ.shift32(); | |
286 | reason = RQ.shiftStr(strlen); | |
287 | RFB.updateState('failed', | |
288 | "Disconnected: security failure: " + reason); | |
ef764d3b JM |
289 | return; |
290 | } | |
c4164bda | 291 | types = RQ.shiftBytes(num_types); |
2cec49d4 | 292 | |
91308399 JM |
293 | RFB.auth_scheme = types[0]; |
294 | if ((RFB.auth_scheme !== 1) && (RFB.auth_scheme !== 2)) { | |
c4164bda JM |
295 | RFB.updateState('failed', |
296 | "Disconnected: invalid security types list: " + types); | |
ef764d3b JM |
297 | return; |
298 | } | |
2cec49d4 | 299 | |
ef764d3b | 300 | RFB.send_array([RFB.auth_scheme]); |
67b24a90 | 301 | } else { |
c539e4dc JM |
302 | if (RQ.length < 4) { |
303 | RFB.updateState('failed', "Invalid security frame"); | |
304 | return; | |
305 | } | |
306 | RFB.auth_scheme = RQ.shift32(); | |
65e27ddd | 307 | } |
c4164bda JM |
308 | RFB.updateState('Authentication', |
309 | "Authenticating using scheme: " + RFB.auth_scheme); | |
ef764d3b JM |
310 | // Fall through |
311 | ||
312 | case 'Authentication' : | |
313 | console.log("Security auth scheme: " + RFB.auth_scheme); | |
314 | switch (RFB.auth_scheme) { | |
65e27ddd | 315 | case 0: // connection failed |
ef764d3b JM |
316 | if (RQ.length < 4) { |
317 | console.log(" waiting for auth reason bytes"); | |
318 | return; | |
319 | } | |
c4164bda JM |
320 | strlen = RQ.shift32(); |
321 | reason = RQ.shiftStr(strlen); | |
322 | RFB.updateState('failed', | |
323 | "Disconnected: auth failure: " + reason); | |
65e27ddd JM |
324 | return; |
325 | case 1: // no authentication | |
2cec49d4 KC |
326 | // RFB.send_array([RFB.shared]); // ClientInitialisation |
327 | RFB.updateState('SecurityResult'); | |
65e27ddd JM |
328 | break; |
329 | case 2: // VNC authentication | |
91308399 JM |
330 | if (RFB.password.length === 0) { |
331 | RFB.updateState('password', "Password Required"); | |
332 | return; | |
333 | } | |
ef764d3b JM |
334 | if (RQ.length < 16) { |
335 | console.log(" waiting for auth challenge bytes"); | |
336 | return; | |
337 | } | |
c4164bda | 338 | challenge = RQ.shiftBytes(16); |
753bde8f | 339 | //console.log("Password: " + RFB.password); |
c4164bda JM |
340 | //console.log("Challenge: " + challenge + |
341 | // " (" + challenge.length + ")"); | |
342 | response = RFB.DES(RFB.password, challenge); | |
343 | //console.log("Response: " + response + | |
344 | // " (" + response.length + ")"); | |
753bde8f JM |
345 | |
346 | console.log("Sending DES encrypted auth response"); | |
8580b989 | 347 | RFB.send_array(response); |
8759ea6f | 348 | RFB.updateState('SecurityResult'); |
65e27ddd | 349 | break; |
484a4696 | 350 | default: |
c4164bda JM |
351 | RFB.updateState('failed', |
352 | "Disconnected: unsupported auth scheme: " + | |
353 | RFB.auth_scheme); | |
484a4696 | 354 | return; |
65e27ddd JM |
355 | } |
356 | break; | |
357 | ||
64ab5c4d | 358 | case 'SecurityResult' : |
c539e4dc JM |
359 | if (RQ.length < 4) { |
360 | RFB.updateState('failed', "Invalid VNC auth response"); | |
65e27ddd JM |
361 | return; |
362 | } | |
c4164bda | 363 | switch (RQ.shift32()) { |
65e27ddd | 364 | case 0: // OK |
8759ea6f | 365 | RFB.updateState('ServerInitialisation', "Authentication OK"); |
65e27ddd JM |
366 | break; |
367 | case 1: // failed | |
67b24a90 | 368 | if (RFB.version >= 3.8) { |
c4164bda JM |
369 | reason_len = RQ.shift32(); |
370 | reason = RQ.shiftStr(reason_len); | |
c539e4dc | 371 | RFB.updateState('failed', reason); |
67b24a90 | 372 | } else { |
c539e4dc JM |
373 | RFB.updateState('failed', "Authentication failed"); |
374 | } | |
65e27ddd JM |
375 | return; |
376 | case 2: // too-many | |
c4164bda JM |
377 | RFB.updateState('failed', |
378 | "Disconnected: too many auth attempts"); | |
65e27ddd JM |
379 | return; |
380 | } | |
64ab5c4d | 381 | RFB.send_array([RFB.shared]); // ClientInitialisation |
65e27ddd JM |
382 | break; |
383 | ||
384 | case 'ServerInitialisation' : | |
5d8e7ec0 | 385 | if (RQ.length < 24) { |
c539e4dc | 386 | RFB.updateState('failed', "Invalid server initialisation"); |
65e27ddd JM |
387 | return; |
388 | } | |
489d1676 JM |
389 | |
390 | /* Screen size */ | |
5d8e7ec0 JM |
391 | RFB.fb_width = RQ.shift16(); |
392 | RFB.fb_height = RQ.shift16(); | |
489d1676 | 393 | |
489d1676 | 394 | /* PIXEL_FORMAT */ |
c4164bda JM |
395 | bpp = RQ.shift8(); |
396 | depth = RQ.shift8(); | |
397 | big_endian = RQ.shift8(); | |
398 | true_color = RQ.shift8(); | |
489d1676 | 399 | |
753bde8f JM |
400 | console.log("Screen: " + RFB.fb_width + "x" + RFB.fb_height + |
401 | ", bpp: " + bpp + ", depth: " + depth + | |
402 | ", big_endian: " + big_endian + | |
403 | ", true_color: " + true_color); | |
489d1676 JM |
404 | |
405 | /* Connection name/title */ | |
5d8e7ec0 | 406 | RQ.shiftStr(12); |
c4164bda | 407 | name_length = RQ.shift32(); |
5d8e7ec0 | 408 | RFB.fb_name = RQ.shiftStr(name_length); |
489d1676 | 409 | |
91308399 | 410 | Canvas.init(RFB.canvasID, RFB.fb_width, RFB.fb_height, RFB.true_color, |
a575a383 JM |
411 | RFB.keyDown, RFB.keyUp, RFB.mouseDown, RFB.mouseUp, |
412 | RFB.mouseMove, RFB.mouseWheel); | |
64ab5c4d | 413 | |
d41c33e4 JM |
414 | if (RFB.true_color) { |
415 | RFB.fb_Bpp = 4; | |
416 | RFB.fb_depth = 3; | |
417 | } else { | |
418 | RFB.fb_Bpp = 1; | |
419 | RFB.fb_depth = 1; | |
420 | } | |
421 | ||
c4164bda JM |
422 | response = RFB.pixelFormat(); |
423 | response = response.concat(RFB.encodings()); | |
424 | response = response.concat(RFB.fbUpdateRequest(0)); | |
c3996e24 | 425 | RFB.timing.fbu_rt_start = (new Date()).getTime(); |
c4164bda | 426 | RFB.send_array(response); |
9f4af5a7 | 427 | |
d064769c | 428 | /* Start pushing/polling */ |
8cf20615 | 429 | RFB.checkEvents.delay(RFB.check_rate); |
489d1676 | 430 | |
8759ea6f | 431 | RFB.updateState('normal', "Connected to: " + RFB.fb_name); |
65e27ddd JM |
432 | break; |
433 | } | |
753bde8f | 434 | //console.log("<< init_msg"); |
64ab5c4d | 435 | }, |
65e27ddd | 436 | |
28a5f293 JM |
437 | |
438 | /* Normal RFB/VNC server messages */ | |
439 | normal_msg: function () { | |
440 | //console.log(">> normal_msg"); | |
c4164bda | 441 | |
c3996e24 | 442 | var RQ = RFB.RQ, FBU = RFB.FBU, now, fbu_rt_diff, |
d41c33e4 JM |
443 | ret = true, msg_type, msg, |
444 | c, first_colour, num_colours, red, green, blue; | |
56ec48be | 445 | |
1098b5bf | 446 | if (FBU.rects > 0) { |
c4164bda JM |
447 | msg_type = 0; |
448 | } else if (RFB.cuttext !== 'none') { | |
449 | msg_type = 3; | |
28a5f293 | 450 | } else { |
c4164bda | 451 | msg_type = RQ.shift8(); |
28a5f293 JM |
452 | } |
453 | switch (msg_type) { | |
454 | case 0: // FramebufferUpdate | |
c4164bda | 455 | if (FBU.rects === 0) { |
5d8e7ec0 JM |
456 | if (RQ.length < 3) { |
457 | RQ.unshift(msg_type); | |
28a5f293 | 458 | console.log(" waiting for FBU header bytes"); |
5d8e7ec0 | 459 | return false; |
28a5f293 | 460 | } |
5d8e7ec0 JM |
461 | RQ.shift8(); |
462 | FBU.rects = RQ.shift16(); | |
28a5f293 | 463 | //console.log("FramebufferUpdate, rects:" + FBU.rects); |
753bde8f | 464 | RFB.timing.cur_fbu = 0; |
28a5f293 JM |
465 | FBU.bytes = 0; |
466 | } | |
467 | ||
5d8e7ec0 | 468 | while ((FBU.rects > 0) && (RQ.length >= FBU.bytes)) { |
c4164bda | 469 | if (FBU.bytes === 0) { |
5d8e7ec0 | 470 | if (RQ.length < 12) { |
28a5f293 | 471 | console.log(" waiting for rect header bytes"); |
5d8e7ec0 | 472 | return false; |
28a5f293 JM |
473 | } |
474 | /* New FramebufferUpdate */ | |
5d8e7ec0 JM |
475 | FBU.x = RQ.shift16(); |
476 | FBU.y = RQ.shift16(); | |
477 | FBU.width = RQ.shift16(); | |
478 | FBU.height = RQ.shift16(); | |
479 | FBU.encoding = parseInt(RQ.shift32(), 10); | |
1098b5bf JM |
480 | |
481 | // Debug: | |
482 | /* | |
c4164bda JM |
483 | msg = "FramebufferUpdate rects:" + FBU.rects + |
484 | " encoding:" + FBU.encoding | |
28a5f293 | 485 | switch (FBU.encoding) { |
1098b5bf JM |
486 | case 0: msg += "(RAW)"; break; |
487 | case 1: msg += "(COPY-RECT)"; break; | |
488 | case 2: msg += "(RRE)"; break; | |
489 | case 5: msg += "(HEXTILE " + FBU.tiles + " tiles)"; break; | |
28a5f293 | 490 | default: |
c4164bda JM |
491 | RFB.updateState('failed', |
492 | "Disconnected: unsupported encoding " + | |
493 | FBU.encoding); | |
5d8e7ec0 | 494 | return false; |
28a5f293 | 495 | } |
5d8e7ec0 | 496 | msg += ", RQ.length: " + RQ.length |
1098b5bf JM |
497 | console.log(msg); |
498 | */ | |
28a5f293 JM |
499 | } |
500 | ||
c4164bda | 501 | RFB.timing.last_fbu = (new Date()).getTime(); |
1098b5bf | 502 | switch (FBU.encoding) { |
5d8e7ec0 JM |
503 | case 0: ret = RFB.display_raw(); break; // Raw |
504 | case 1: ret = RFB.display_copy_rect(); break; // Copy-Rect | |
505 | case 2: ret = RFB.display_rre(); break; // RRE | |
506 | case 5: ret = RFB.display_hextile(); break; // hextile | |
28a5f293 | 507 | } |
c3996e24 JM |
508 | now = (new Date()).getTime(); |
509 | RFB.timing.cur_fbu += (now - RFB.timing.last_fbu); | |
c4164bda JM |
510 | if (FBU.rects === 0) { |
511 | if ((FBU.width === RFB.fb_width) && | |
512 | (FBU.height === RFB.fb_height)) { | |
753bde8f JM |
513 | RFB.timing.full_fbu_total += RFB.timing.cur_fbu; |
514 | RFB.timing.full_fbu_cnt += 1; | |
c4164bda JM |
515 | console.log("Timing of full FBU, cur: " + |
516 | RFB.timing.cur_fbu + ", total: " + | |
517 | RFB.timing.full_fbu_total + ", cnt: " + | |
518 | RFB.timing.full_fbu_cnt + ", avg: " + | |
519 | (RFB.timing.full_fbu_total / | |
520 | RFB.timing.full_fbu_cnt)); | |
c3996e24 JM |
521 | if (RFB.timing.fbu_rt_start > 0) { |
522 | fbu_rt_diff = now - RFB.timing.fbu_rt_start; | |
523 | RFB.timing.fbu_rt_total += fbu_rt_diff; | |
524 | RFB.timing.fbu_rt_cnt += 1; | |
525 | console.log("full FBU round-trip, cur: " + | |
526 | fbu_rt_diff + ", total: " + | |
527 | RFB.timing.fbu_rt_total + ", cnt: " + | |
528 | RFB.timing.fbu_rt_cnt + ", avg: " + | |
529 | (RFB.timing.fbu_rt_total / | |
530 | RFB.timing.fbu_rt_cnt)); | |
531 | RFB.timing.fbu_rt_start = 0; | |
532 | } | |
753bde8f JM |
533 | } |
534 | } | |
c4164bda | 535 | if (RFB.state !== "normal") { return true; } |
28a5f293 JM |
536 | } |
537 | ||
28a5f293 JM |
538 | break; |
539 | case 1: // SetColourMapEntries | |
d41c33e4 | 540 | console.log("SetColourMapEntries"); |
5d8e7ec0 | 541 | RQ.shift8(); // Padding |
d41c33e4 | 542 | first_colour = RQ.shift16(); // First colour |
c4164bda | 543 | num_colours = RQ.shift16(); |
d41c33e4 JM |
544 | for (c=0; c < num_colours; c++) { |
545 | red = RQ.shift16(); | |
546 | //console.log("red before: " + red); | |
547 | red = parseInt(red / 256, 10); | |
548 | //console.log("red after: " + red); | |
549 | green = parseInt(RQ.shift16() / 256, 10); | |
550 | blue = parseInt(RQ.shift16() / 256, 10); | |
551 | Canvas.colourMap[first_colour + c] = [red, green, blue]; | |
552 | } | |
553 | console.log("Registered " + num_colours + " colourMap entries"); | |
554 | //console.log("colourMap: " + Canvas.colourMap); | |
28a5f293 JM |
555 | break; |
556 | case 2: // Bell | |
557 | console.log("Bell (unsupported)"); | |
558 | break; | |
559 | case 3: // ServerCutText | |
560 | console.log("ServerCutText"); | |
5d8e7ec0 | 561 | console.log("RQ:" + RQ.slice(0,20)); |
c4164bda | 562 | if (RFB.cuttext === 'none') { |
30059bdf JM |
563 | RFB.cuttext = 'header'; |
564 | } | |
c4164bda | 565 | if (RFB.cuttext === 'header') { |
5d8e7ec0 | 566 | if (RQ.length < 7) { |
30059bdf | 567 | console.log("waiting for ServerCutText header"); |
5d8e7ec0 | 568 | return false; |
30059bdf | 569 | } |
5d8e7ec0 JM |
570 | RQ.shiftBytes(3); // Padding |
571 | RFB.ct_length = RQ.shift32(); | |
30059bdf JM |
572 | } |
573 | RFB.cuttext = 'bytes'; | |
5d8e7ec0 | 574 | if (RQ.length < RFB.ct_length) { |
30059bdf | 575 | console.log("waiting for ServerCutText bytes"); |
5d8e7ec0 | 576 | return false; |
30059bdf | 577 | } |
5d8e7ec0 | 578 | RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length)); |
30059bdf | 579 | RFB.cuttext = 'none'; |
28a5f293 JM |
580 | break; |
581 | default: | |
c4164bda JM |
582 | RFB.updateState('failed', |
583 | "Disconnected: illegal server message type " + msg_type); | |
5d8e7ec0 | 584 | console.log("RQ.slice(0,30):" + RQ.slice(0,30)); |
28a5f293 JM |
585 | break; |
586 | } | |
587 | //console.log("<< normal_msg"); | |
5d8e7ec0 | 588 | return ret; |
28a5f293 JM |
589 | }, |
590 | ||
591 | ||
592 | /* | |
593 | * FramebufferUpdate encodings | |
594 | */ | |
595 | ||
ed7e776d | 596 | display_raw: function () { |
1098b5bf | 597 | //console.log(">> display_raw"); |
c4164bda | 598 | |
56ec48be | 599 | var RQ = RFB.RQ, FBU = RFB.FBU, cur_y, cur_height; |
c4164bda JM |
600 | |
601 | if (FBU.lines === 0) { | |
1098b5bf JM |
602 | FBU.lines = FBU.height; |
603 | } | |
604 | FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line | |
5d8e7ec0 | 605 | if (RQ.length < FBU.bytes) { |
c4164bda JM |
606 | //console.log(" waiting for " + |
607 | // (FBU.bytes - RQ.length) + " RAW bytes"); | |
1098b5bf JM |
608 | return; |
609 | } | |
c4164bda JM |
610 | cur_y = FBU.y + (FBU.height - FBU.lines); |
611 | cur_height = Math.min(FBU.lines, | |
612 | Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp))); | |
d41c33e4 | 613 | Canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); |
5d8e7ec0 | 614 | RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp); |
1098b5bf JM |
615 | FBU.lines -= cur_height; |
616 | ||
617 | if (FBU.lines > 0) { | |
618 | FBU.bytes = FBU.width * RFB.fb_Bpp; // At least another line | |
619 | } else { | |
620 | FBU.rects --; | |
621 | FBU.bytes = 0; | |
622 | } | |
ed7e776d JM |
623 | }, |
624 | ||
625 | display_copy_rect: function () { | |
1098b5bf | 626 | //console.log(">> display_copy_rect"); |
c4164bda | 627 | |
56ec48be | 628 | var RQ = RFB.RQ, FBU = RFB.FBU, old_x, old_y; |
c4164bda | 629 | |
5d8e7ec0 | 630 | if (RQ.length < 4) { |
c4164bda JM |
631 | //console.log(" waiting for " + |
632 | // (FBU.bytes - RQ.length) + " COPY-RECT bytes"); | |
1098b5bf JM |
633 | return; |
634 | } | |
c4164bda JM |
635 | old_x = RQ.shift16(); |
636 | old_y = RQ.shift16(); | |
ed7e776d JM |
637 | Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); |
638 | FBU.rects --; | |
1098b5bf | 639 | FBU.bytes = 0; |
ed7e776d JM |
640 | }, |
641 | ||
642 | display_rre: function () { | |
56ec48be JM |
643 | //console.log(">> display_rre (" + RFB.RQ.length + " bytes)"); |
644 | var RQ = RFB.RQ, FBU = RFB.FBU, color, x, y, width, height, chunk; | |
c4164bda | 645 | if (FBU.subrects === 0) { |
5d8e7ec0 | 646 | if (RQ.length < 4 + RFB.fb_Bpp) { |
c4164bda JM |
647 | //console.log(" waiting for " + |
648 | // (4 + RFB.fb_Bpp - RQ.length) + " RRE bytes"); | |
1098b5bf JM |
649 | return; |
650 | } | |
5d8e7ec0 | 651 | FBU.subrects = RQ.shift32(); |
c4164bda | 652 | color = RQ.shiftBytes(RFB.fb_Bpp); // Background |
48ebcdb1 | 653 | Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); |
f7618085 | 654 | } |
5d8e7ec0 | 655 | while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) { |
c4164bda JM |
656 | color = RQ.shiftBytes(RFB.fb_Bpp); |
657 | x = RQ.shift16(); | |
658 | y = RQ.shift16(); | |
659 | width = RQ.shift16(); | |
660 | height = RQ.shift16(); | |
48ebcdb1 | 661 | Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); |
1098b5bf | 662 | FBU.subrects --; |
ed7e776d | 663 | } |
c4164bda JM |
664 | //console.log(" display_rre: rects: " + FBU.rects + |
665 | // ", FBU.subrects: " + FBU.subrects); | |
ed7e776d JM |
666 | |
667 | if (FBU.subrects > 0) { | |
c4164bda | 668 | chunk = Math.min(RFB.rre_chunk, FBU.subrects); |
f7618085 | 669 | FBU.bytes = (RFB.fb_Bpp + 8) * chunk; |
ed7e776d | 670 | } else { |
6dab56f9 | 671 | FBU.rects --; |
1098b5bf | 672 | FBU.bytes = 0; |
ed7e776d | 673 | } |
b7ec5487 | 674 | //console.log("<< display_rre, FBU.bytes: " + FBU.bytes); |
ed7e776d JM |
675 | }, |
676 | ||
0f628064 | 677 | display_hextile: function() { |
8759ea6f | 678 | //console.log(">> display_hextile"); |
56ec48be JM |
679 | var RQ = RFB.RQ, FBU = RFB.FBU, |
680 | subencoding, subrects, idx, tile, color, cur_tile, | |
681 | tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh; | |
b7ec5487 | 682 | |
c4164bda | 683 | if (FBU.tiles === 0) { |
1098b5bf JM |
684 | FBU.tiles_x = Math.ceil(FBU.width/16); |
685 | FBU.tiles_y = Math.ceil(FBU.height/16); | |
686 | FBU.total_tiles = FBU.tiles_x * FBU.tiles_y; | |
687 | FBU.tiles = FBU.total_tiles; | |
688 | } | |
689 | ||
5d8e7ec0 | 690 | /* FBU.bytes comes in as 1, RQ.length at least 1 */ |
1098b5bf JM |
691 | while (FBU.tiles > 0) { |
692 | FBU.bytes = 1; | |
5d8e7ec0 | 693 | if (RQ.length < FBU.bytes) { |
1098b5bf JM |
694 | console.log(" waiting for HEXTILE subencoding byte"); |
695 | return; | |
696 | } | |
5d8e7ec0 | 697 | subencoding = RQ[0]; // Peek |
1098b5bf | 698 | if (subencoding > 30) { // Raw |
c4164bda JM |
699 | RFB.updateState('failed', |
700 | "Disconnected: illegal hextile subencoding " + subencoding); | |
5d8e7ec0 | 701 | console.log("RQ.slice(0,30):" + RQ.slice(0,30)); |
1098b5bf JM |
702 | return; |
703 | } | |
704 | subrects = 0; | |
9f4af5a7 JM |
705 | cur_tile = FBU.total_tiles - FBU.tiles; |
706 | tile_x = cur_tile % FBU.tiles_x; | |
707 | tile_y = Math.floor(cur_tile / FBU.tiles_x); | |
708 | x = FBU.x + tile_x * 16; | |
709 | y = FBU.y + tile_y * 16; | |
c4164bda JM |
710 | w = Math.min(16, (FBU.x + FBU.width) - x); |
711 | h = Math.min(16, (FBU.y + FBU.height) - y); | |
b7ec5487 | 712 | |
1098b5bf JM |
713 | /* Figure out how much we are expecting */ |
714 | if (subencoding & 0x01) { // Raw | |
715 | //console.log(" Raw subencoding"); | |
716 | FBU.bytes += w * h * RFB.fb_Bpp; | |
717 | } else { | |
718 | if (subencoding & 0x02) { // Background | |
719 | FBU.bytes += RFB.fb_Bpp; | |
720 | } | |
721 | if (subencoding & 0x04) { // Foreground | |
722 | FBU.bytes += RFB.fb_Bpp; | |
723 | } | |
724 | if (subencoding & 0x08) { // AnySubrects | |
725 | FBU.bytes++; // Since we aren't shifting it off | |
5d8e7ec0 | 726 | if (RQ.length < FBU.bytes) { |
1098b5bf JM |
727 | /* Wait for subrects byte */ |
728 | console.log(" waiting for hextile subrects header byte"); | |
729 | return; | |
b7ec5487 | 730 | } |
5d8e7ec0 | 731 | subrects = RQ[FBU.bytes-1]; // Peek |
1098b5bf JM |
732 | if (subencoding & 0x10) { // SubrectsColoured |
733 | FBU.bytes += subrects * (RFB.fb_Bpp + 2); | |
734 | } else { | |
735 | FBU.bytes += subrects * 2; | |
b7ec5487 JM |
736 | } |
737 | } | |
738 | } | |
739 | ||
c4164bda JM |
740 | //console.log(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + |
741 | // ", subencoding:" + subencoding + | |
742 | // "(last: " + FBU.lastsubencoding + "), subrects:" + | |
743 | // subrects + ", tile:" + tile_x + "," + tile_y + | |
744 | // " [" + x + "," + y + "]@" + w + "x" + h + | |
745 | // ", d.length:" + RQ.length + ", bytes:" + FBU.bytes + | |
746 | // " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) + | |
747 | // " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10)); | |
5d8e7ec0 | 748 | if (RQ.length < FBU.bytes) { |
c4164bda JM |
749 | //console.log(" waiting for " + |
750 | // (FBU.bytes - RQ.length) + " hextile bytes"); | |
b7ec5487 JM |
751 | return; |
752 | } | |
753 | ||
1098b5bf | 754 | /* We know the encoding and have a whole tile */ |
7f4f41b0 JM |
755 | FBU.subencoding = RQ[0]; |
756 | idx = 1; | |
c4164bda | 757 | if (FBU.subencoding === 0) { |
1098b5bf JM |
758 | if (FBU.lastsubencoding & 0x01) { |
759 | /* Weird: ignore blanks after RAW */ | |
760 | console.log(" Ignoring blank after RAW"); | |
7f4f41b0 JM |
761 | } else { |
762 | Canvas.fillRect(x, y, w, h, FBU.background); | |
1098b5bf | 763 | } |
1098b5bf | 764 | } else if (FBU.subencoding & 0x01) { // Raw |
d41c33e4 | 765 | Canvas.blitImage(x, y, w, h, RQ, idx); |
1098b5bf | 766 | } else { |
1098b5bf | 767 | if (FBU.subencoding & 0x02) { // Background |
5d8e7ec0 | 768 | FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp); |
1098b5bf | 769 | idx += RFB.fb_Bpp; |
1098b5bf JM |
770 | } |
771 | if (FBU.subencoding & 0x04) { // Foreground | |
5d8e7ec0 | 772 | FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp); |
1098b5bf | 773 | idx += RFB.fb_Bpp; |
1098b5bf | 774 | } |
3875f847 | 775 | |
c4164bda | 776 | tile = Canvas.getTile(x, y, w, h, FBU.background); |
1098b5bf | 777 | if (FBU.subencoding & 0x08) { // AnySubrects |
5d8e7ec0 | 778 | subrects = RQ[idx]; |
1098b5bf | 779 | idx++; |
c4164bda | 780 | for (s = 0; s < subrects; s ++) { |
1098b5bf | 781 | if (FBU.subencoding & 0x10) { // SubrectsColoured |
5d8e7ec0 | 782 | color = RQ.slice(idx, idx + RFB.fb_Bpp); |
1098b5bf JM |
783 | idx += RFB.fb_Bpp; |
784 | } else { | |
785 | color = FBU.foreground; | |
b7ec5487 | 786 | } |
5d8e7ec0 | 787 | xy = RQ[idx]; |
1098b5bf | 788 | idx++; |
3875f847 JM |
789 | sx = (xy >> 4); |
790 | sy = (xy & 0x0f); | |
1098b5bf | 791 | |
5d8e7ec0 | 792 | wh = RQ[idx]; |
1098b5bf JM |
793 | idx++; |
794 | sw = (wh >> 4) + 1; | |
795 | sh = (wh & 0x0f) + 1; | |
796 | ||
3875f847 | 797 | Canvas.setTile(tile, sx, sy, sw, sh, color); |
b7ec5487 JM |
798 | } |
799 | } | |
3875f847 | 800 | Canvas.putTile(tile); |
b7ec5487 | 801 | } |
5d8e7ec0 | 802 | RQ.shiftBytes(FBU.bytes); |
1098b5bf JM |
803 | FBU.lastsubencoding = FBU.subencoding; |
804 | FBU.bytes = 0; | |
805 | FBU.tiles --; | |
0f628064 JM |
806 | } |
807 | ||
c4164bda | 808 | if (FBU.tiles === 0) { |
b7ec5487 JM |
809 | FBU.rects --; |
810 | } | |
811 | ||
8759ea6f | 812 | //console.log("<< display_hextile"); |
0f628064 JM |
813 | }, |
814 | ||
ed7e776d | 815 | |
65e27ddd JM |
816 | |
817 | /* | |
818 | * Client message routines | |
819 | */ | |
820 | ||
9f4af5a7 | 821 | pixelFormat: function () { |
753bde8f | 822 | //console.log(">> setPixelFormat"); |
9f4af5a7 JM |
823 | var arr; |
824 | arr = [0]; // msg-type | |
64ab5c4d JM |
825 | arr.push8(0); // padding |
826 | arr.push8(0); // padding | |
827 | arr.push8(0); // padding | |
828 | ||
cc0410a3 | 829 | arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel |
d41c33e4 | 830 | arr.push8(RFB.fb_depth * 8); // depth |
64ab5c4d | 831 | arr.push8(0); // little-endian |
d41c33e4 | 832 | arr.push8(RFB.true_color); // true-color |
64ab5c4d JM |
833 | |
834 | arr.push16(255); // red-max | |
835 | arr.push16(255); // green-max | |
836 | arr.push16(255); // blue-max | |
9f4af5a7 | 837 | arr.push8(0); // red-shift |
64ab5c4d | 838 | arr.push8(8); // green-shift |
9f4af5a7 | 839 | arr.push8(16); // blue-shift |
64ab5c4d | 840 | |
9f4af5a7 JM |
841 | arr.push8(0); // padding |
842 | arr.push8(0); // padding | |
843 | arr.push8(0); // padding | |
753bde8f | 844 | //console.log("<< setPixelFormat"); |
9f4af5a7 | 845 | return arr; |
64ab5c4d JM |
846 | }, |
847 | ||
848 | fixColourMapEntries: function () { | |
849 | }, | |
850 | ||
9f4af5a7 | 851 | encodings: function () { |
753bde8f | 852 | //console.log(">> setEncodings"); |
9f4af5a7 JM |
853 | var arr; |
854 | arr = [2]; // msg-type | |
64ab5c4d | 855 | arr.push8(0); // padding |
b7ec5487 JM |
856 | |
857 | //arr.push16(3); // encoding count | |
858 | arr.push16(4); // encoding count | |
859 | arr.push32(5); // hextile encoding | |
860 | ||
ed7e776d | 861 | arr.push32(2); // RRE encoding |
64ab5c4d JM |
862 | arr.push32(1); // copy-rect encoding |
863 | arr.push32(0); // raw encoding | |
753bde8f | 864 | //console.log("<< setEncodings"); |
9f4af5a7 | 865 | return arr; |
64ab5c4d | 866 | }, |
65e27ddd | 867 | |
64ab5c4d | 868 | fbUpdateRequest: function (incremental, x, y, xw, yw) { |
410960ba | 869 | //console.log(">> fbUpdateRequest"); |
c4164bda JM |
870 | if (!x) { x = 0; } |
871 | if (!y) { y = 0; } | |
872 | if (!xw) { xw = RFB.fb_width; } | |
873 | if (!yw) { yw = RFB.fb_height; } | |
9f4af5a7 JM |
874 | var arr; |
875 | arr = [3]; // msg-type | |
64ab5c4d JM |
876 | arr.push8(incremental); |
877 | arr.push16(x); | |
878 | arr.push16(y); | |
879 | arr.push16(xw); | |
880 | arr.push16(yw); | |
b7ec5487 | 881 | //console.log("<< fbUpdateRequest"); |
9f4af5a7 | 882 | return arr; |
64ab5c4d | 883 | }, |
65e27ddd | 884 | |
d9cbdc7d | 885 | keyEvent: function (keysym, down) { |
410960ba | 886 | //console.log(">> keyEvent, keysym: " + keysym + ", down: " + down); |
9f4af5a7 JM |
887 | var arr; |
888 | arr = [4]; // msg-type | |
64ab5c4d JM |
889 | arr.push8(down); |
890 | arr.push16(0); | |
d9cbdc7d | 891 | arr.push32(keysym); |
b7ec5487 | 892 | //console.log("<< keyEvent"); |
d064769c | 893 | return arr; |
64ab5c4d | 894 | }, |
65e27ddd | 895 | |
d064769c | 896 | pointerEvent: function (x, y) { |
c4164bda JM |
897 | //console.log(">> pointerEvent, x,y: " + x + "," + y + |
898 | // " , mask: " + RFB.mouse_buttonMask); | |
d064769c JM |
899 | var arr; |
900 | arr = [5]; // msg-type | |
97bfe5ba | 901 | arr.push8(RFB.mouse_buttonMask); |
d064769c JM |
902 | arr.push16(x); |
903 | arr.push16(y); | |
410960ba | 904 | //console.log("<< pointerEvent"); |
d064769c | 905 | return arr; |
64ab5c4d | 906 | }, |
65e27ddd | 907 | |
30059bdf | 908 | clientCutText: function (text) { |
91308399 | 909 | //console.log(">> clientCutText"); |
30059bdf JM |
910 | var arr; |
911 | arr = [6]; // msg-type | |
912 | arr.push8(0); // padding | |
913 | arr.push8(0); // padding | |
914 | arr.push8(0); // padding | |
915 | arr.push32(text.length); | |
916 | arr.pushStr(text); | |
91308399 | 917 | //console.log("<< clientCutText:" + arr); |
30059bdf | 918 | return arr; |
64ab5c4d JM |
919 | }, |
920 | ||
921 | ||
922 | /* | |
923 | * Utility routines | |
924 | */ | |
925 | ||
507b473a JM |
926 | encode_message: function(arr) { |
927 | if (RFB.b64encode) { | |
928 | RFB.SQ = RFB.SQ + Base64.encode(arr); | |
929 | } else { | |
930 | RFB.SQ = RFB.SQ + arr.map(function (num) { | |
931 | return String.fromCharCode(num); } ).join(''); | |
932 | } | |
933 | }, | |
934 | ||
935 | decode_message: function(data, offset) { | |
936 | //console.log(">> decode_message: " + data); | |
937 | if (RFB.b64encode) { | |
938 | RFB.RQ = RFB.RQ.concat(Base64.decode(data, offset)); | |
939 | } else { | |
940 | RFB.RQ = RFB.RQ.concat(data.split('').slice(offset). | |
941 | map(function (chr) { | |
942 | return (chr.charCodeAt(0) % 256); })); | |
943 | } | |
944 | //console.log(">> decode_message, RQ: " + RFB.RQ); | |
945 | }, | |
946 | ||
07287cfd JM |
947 | recv_message: function(e) { |
948 | //console.log(">> recv_message"); | |
07287cfd | 949 | |
753bde8f JM |
950 | try { |
951 | if (RFB.use_seq) { | |
952 | RFB.recv_message_reorder(e); | |
953 | } else { | |
507b473a | 954 | RFB.decode_message(e.data, 0); |
753bde8f JM |
955 | |
956 | RFB.handle_message(); | |
957 | } | |
c4164bda | 958 | } catch (exc) { |
14355cb2 JM |
959 | if (typeof exc.stack !== 'undefined') { |
960 | console.log("recv_message, caught exception: " + exc.stack); | |
961 | } else if (typeof exc.description !== 'undefined') { | |
962 | console.log("recv_message, caught exception: " + exc.description); | |
963 | } else { | |
964 | console.log("recv_message, caught exception:" + exc); | |
965 | } | |
c4164bda JM |
966 | if (typeof exc.name !== 'undefined') { |
967 | RFB.updateState('failed', exc.name + ": " + exc.message); | |
753bde8f | 968 | } else { |
c4164bda | 969 | RFB.updateState('failed', exc); |
753bde8f JM |
970 | } |
971 | } | |
07287cfd JM |
972 | //console.log("<< recv_message"); |
973 | }, | |
974 | ||
975 | recv_message_reorder: function(e) { | |
976 | //console.log(">> recv_message_reorder"); | |
977 | ||
56ec48be | 978 | var RQ_reorder = RFB.RQ_reorder, offset, seq_num, i; |
c4164bda JM |
979 | |
980 | offset = e.data.indexOf(":") + 1; | |
981 | seq_num = parseInt(e.data.substr(0, offset-1), 10); | |
56ec48be | 982 | if (RFB.RQ_seq_num === seq_num) { |
507b473a | 983 | RFB.decode_message(e.data, offset); |
56ec48be | 984 | RFB.RQ_seq_num++; |
07287cfd | 985 | } else { |
c4164bda | 986 | console.warn("sequence number mismatch: expected " + |
56ec48be JM |
987 | RFB.RQ_seq_num + ", got " + seq_num); |
988 | if (RFB.RQ_reorder.length > 20) { | |
07287cfd JM |
989 | RFB.updateState('failed', "Re-order queue too long"); |
990 | } else { | |
56ec48be | 991 | RFB.RQ_reorder = RFB.RQ_reorder.concat(e.data.substr(0)); |
c4164bda | 992 | i = 0; |
56ec48be JM |
993 | while (i < RFB.RQ_reorder.length) { |
994 | offset = RFB.RQ_reorder[i].indexOf(":") + 1; | |
995 | seq_num = parseInt(RFB.RQ_reorder[i].substr(0, offset-1), 10); | |
c4164bda JM |
996 | //console.log("Searching reorder list item " + |
997 | // i + ", seq_num " + seq_num); | |
56ec48be | 998 | if (seq_num === RFB.RQ_seq_num) { |
07287cfd JM |
999 | /* Remove it from reorder queue, decode it and |
1000 | * add it to the receive queue */ | |
1001 | console.log("Found re-ordered packet seq_num " + seq_num); | |
507b473a | 1002 | RFB.decode_message(RFB.RQ_reorder.splice(i, 1)[0], offset); |
56ec48be | 1003 | RFB.RQ_seq_num++; |
07287cfd JM |
1004 | i = 0; // Start search again for next one |
1005 | } else { | |
1006 | i++; | |
1007 | } | |
1008 | } | |
1009 | ||
1010 | } | |
1011 | } | |
1012 | ||
97763d0e JM |
1013 | if (RFB.RQ.length > 0) { |
1014 | RFB.handle_message(); | |
1015 | } | |
07287cfd JM |
1016 | //console.log("<< recv_message_reorder"); |
1017 | }, | |
1018 | ||
1019 | handle_message: function () { | |
1020 | switch (RFB.state) { | |
1021 | case 'disconnected': | |
1022 | console.error("Got data while disconnected"); | |
1023 | break; | |
07287cfd JM |
1024 | case 'failed': |
1025 | console.log("Giving up!"); | |
1026 | RFB.disconnect(); | |
1027 | break; | |
1028 | case 'normal': | |
1029 | RFB.normal_msg(); | |
1030 | /* | |
56ec48be | 1031 | while (RFB.RQ.length > 0) { |
c4164bda | 1032 | if (RFB.normal_msg() && RFB.state === 'normal') { |
07287cfd JM |
1033 | console.log("More to process"); |
1034 | } else { | |
1035 | break; | |
1036 | } | |
1037 | } | |
1038 | */ | |
1039 | break; | |
1040 | default: | |
1041 | RFB.init_msg(); | |
1042 | break; | |
1043 | } | |
1044 | }, | |
1045 | ||
64ab5c4d | 1046 | send_string: function (str) { |
b7ec5487 | 1047 | //console.log(">> send_string: " + str); |
9f4af5a7 | 1048 | RFB.send_array(str.split('').map( |
c4164bda | 1049 | function (chr) { return chr.charCodeAt(0); } ) ); |
64ab5c4d JM |
1050 | }, |
1051 | ||
1052 | send_array: function (arr) { | |
1098b5bf | 1053 | //console.log(">> send_array: " + arr); |
507b473a | 1054 | RFB.encode_message(arr); |
c4164bda | 1055 | if (RFB.ws.bufferedAmount === 0) { |
56ec48be JM |
1056 | RFB.ws.send(RFB.SQ); |
1057 | RFB.SQ = ""; | |
5d8e7ec0 JM |
1058 | } else { |
1059 | console.log("Delaying send"); | |
1060 | } | |
64ab5c4d JM |
1061 | }, |
1062 | ||
c539e4dc | 1063 | DES: function (password, challenge) { |
c4164bda JM |
1064 | var i, passwd, response; |
1065 | passwd = []; | |
1066 | response = challenge.slice(); | |
1067 | for (i=0; i < password.length; i++) { | |
c539e4dc | 1068 | passwd.push(password.charCodeAt(i)); |
532a9fd9 | 1069 | } |
c539e4dc JM |
1070 | |
1071 | DES.setKeys(passwd); | |
1072 | DES.encrypt(response, 0, response, 0); | |
1073 | DES.encrypt(response, 8, response, 8); | |
1074 | return response; | |
532a9fd9 JM |
1075 | }, |
1076 | ||
d064769c | 1077 | flushClient: function () { |
97bfe5ba JM |
1078 | if (RFB.mouse_arr.length > 0) { |
1079 | //RFB.send_array(RFB.mouse_arr.concat(RFB.fbUpdateRequest(1))); | |
c4164bda | 1080 | RFB.send_array(RFB.mouse_arr); |
5d8e7ec0 JM |
1081 | setTimeout(function() { |
1082 | RFB.send_array(RFB.fbUpdateRequest(1)); | |
1083 | }, 50); | |
1084 | ||
97bfe5ba | 1085 | RFB.mouse_arr = []; |
8cf20615 | 1086 | return true; |
d064769c | 1087 | } else { |
8cf20615 | 1088 | return false; |
d064769c JM |
1089 | } |
1090 | }, | |
1091 | ||
8cf20615 | 1092 | checkEvents: function () { |
c4164bda JM |
1093 | var now; |
1094 | if (RFB.state === 'normal') { | |
8cf20615 | 1095 | if (! RFB.flushClient()) { |
c4164bda | 1096 | now = new Date().getTime(); |
8cf20615 JM |
1097 | if (now > RFB.last_req + RFB.req_rate) { |
1098 | RFB.last_req = now; | |
1099 | RFB.send_array(RFB.fbUpdateRequest(1)); | |
1100 | } | |
1101 | } | |
64ab5c4d | 1102 | } |
5d8e7ec0 | 1103 | RFB.checkEvents.delay(RFB.check_rate); |
64ab5c4d JM |
1104 | }, |
1105 | ||
c4164bda JM |
1106 | keyX: function (e, down) { |
1107 | var arr; | |
30059bdf JM |
1108 | if (RFB.clipboardFocus) { |
1109 | return true; | |
1110 | } | |
64ab5c4d | 1111 | e.stop(); |
c4164bda | 1112 | arr = RFB.keyEvent(Canvas.getKeysym(e), down); |
d064769c JM |
1113 | arr = arr.concat(RFB.fbUpdateRequest(1)); |
1114 | RFB.send_array(arr); | |
64ab5c4d JM |
1115 | }, |
1116 | ||
30059bdf JM |
1117 | keyDown: function (e) { |
1118 | //console.log(">> keyDown: " + Canvas.getKeysym(e)); | |
c4164bda | 1119 | RFB.keyX(e, 1); |
30059bdf JM |
1120 | }, |
1121 | ||
64ab5c4d | 1122 | keyUp: function (e) { |
1098b5bf | 1123 | //console.log(">> keyUp: " + Canvas.getKeysym(e)); |
c4164bda | 1124 | RFB.keyX(e, 0); |
d064769c JM |
1125 | }, |
1126 | ||
1127 | mouseDown: function(e) { | |
c4164bda JM |
1128 | var evt, x, y; |
1129 | evt = e.event || window.event; | |
d064769c JM |
1130 | x = (evt.clientX - Canvas.c_x); |
1131 | y = (evt.clientY - Canvas.c_y); | |
c4164bda JM |
1132 | //console.log(">> mouseDown " + evt.which + "/" + evt.button + |
1133 | // " " + x + "," + y); | |
97bfe5ba JM |
1134 | RFB.mouse_buttonMask |= 1 << evt.button; |
1135 | RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); | |
d064769c JM |
1136 | |
1137 | RFB.flushClient(); | |
64ab5c4d | 1138 | }, |
65e27ddd | 1139 | |
d064769c | 1140 | mouseUp: function(e) { |
c4164bda JM |
1141 | var evt, x, y; |
1142 | evt = e.event || window.event; | |
d064769c JM |
1143 | x = (evt.clientX - Canvas.c_x); |
1144 | y = (evt.clientY - Canvas.c_y); | |
c4164bda JM |
1145 | //console.log(">> mouseUp " + evt.which + "/" + evt.button + |
1146 | // " " + x + "," + y); | |
97bfe5ba JM |
1147 | RFB.mouse_buttonMask ^= 1 << evt.button; |
1148 | RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); | |
d064769c JM |
1149 | |
1150 | RFB.flushClient(); | |
1151 | }, | |
1152 | ||
1153 | mouseMove: function(e) { | |
c4164bda JM |
1154 | var evt, x, y; |
1155 | evt = e.event || window.event; | |
8cf20615 JM |
1156 | x = (evt.clientX - Canvas.c_x); |
1157 | y = (evt.clientY - Canvas.c_y); | |
1098b5bf | 1158 | //console.log('>> mouseMove ' + x + "," + y); |
97bfe5ba | 1159 | RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); |
d064769c JM |
1160 | }, |
1161 | ||
a575a383 JM |
1162 | mouseWheel: function (e) { |
1163 | var evt, wheelData, bmask; | |
1164 | evt = e.event || window.event; | |
1165 | //e = e ? e : window.event; | |
1166 | x = (evt.clientX - Canvas.c_x); | |
1167 | y = (evt.clientY - Canvas.c_y); | |
1168 | wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; | |
1169 | //console.log('>> mouseWheel ' + wheelData + | |
1170 | // " " + x + "," + y); | |
1171 | ||
1172 | if (wheelData > 0) { | |
1173 | bmask = 1 << 3; | |
1174 | } else { | |
1175 | bmask = 1 << 4; | |
1176 | } | |
1177 | RFB.mouse_buttonMask |= bmask; | |
1178 | RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); | |
1179 | RFB.mouse_buttonMask ^= bmask; | |
1180 | RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); | |
1181 | RFB.flushClient(); | |
1182 | }, | |
1183 | ||
1184 | ||
30059bdf | 1185 | clipboardCopyTo: function (text) { |
91308399 JM |
1186 | console.log(">> clipboardCopyTo stub"); |
1187 | // Stub | |
30059bdf JM |
1188 | }, |
1189 | ||
91308399 JM |
1190 | externalUpdateState: function(state, msg) { |
1191 | console.log(">> externalUpdateState stub"); | |
1192 | // Stub | |
30059bdf | 1193 | }, |
d064769c | 1194 | |
8759ea6f | 1195 | updateState: function(state, statusMsg) { |
91308399 | 1196 | var func, cmsg; |
c4164bda | 1197 | func = function(msg) { console.log(msg); }; |
8759ea6f JM |
1198 | switch (state) { |
1199 | case 'failed': | |
c4164bda | 1200 | func = function(msg) { console.error(msg); }; |
8759ea6f JM |
1201 | break; |
1202 | case 'normal': | |
8759ea6f JM |
1203 | break; |
1204 | case 'disconnected': | |
8759ea6f JM |
1205 | break; |
1206 | default: | |
c4164bda | 1207 | func = function(msg) { console.warn(msg); }; |
8759ea6f JM |
1208 | break; |
1209 | } | |
1210 | ||
91308399 JM |
1211 | cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; |
1212 | func("New state '" + state + "'." + cmsg); | |
1213 | ||
753bde8f JM |
1214 | if ((RFB.state === 'failed') && |
1215 | ((state === 'disconnected') || (state === 'closed'))) { | |
1216 | // Leave the failed message | |
91308399 JM |
1217 | RFB.externalUpdateState(state) |
1218 | } else { | |
1219 | RFB.state = state; | |
1220 | RFB.externalUpdateState(state, statusMsg) | |
8759ea6f JM |
1221 | } |
1222 | }, | |
65e27ddd JM |
1223 | |
1224 | /* | |
1225 | * Setup routines | |
1226 | */ | |
1227 | ||
532a9fd9 | 1228 | init_ws: function () { |
b7ec5487 | 1229 | console.log(">> init_ws"); |
c4164bda | 1230 | |
507b473a | 1231 | var uri = "", vars = []; |
db504ade JM |
1232 | if (RFB.encrypt) { |
1233 | uri = "wss://"; | |
1234 | } else { | |
1235 | uri = "ws://"; | |
1236 | } | |
507b473a JM |
1237 | uri += RFB.host + ":" + RFB.port + "/"; |
1238 | if (RFB.b64encode) { | |
1239 | vars.push("b64encode"); | |
1240 | } | |
07287cfd | 1241 | if (RFB.use_seq) { |
507b473a JM |
1242 | vars.push("seq_num"); |
1243 | } | |
1244 | if (vars.length > 0) { | |
1245 | uri += "?" + vars.join("&"); | |
07287cfd | 1246 | } |
b7ec5487 | 1247 | console.log("connecting to " + uri); |
cc0410a3 | 1248 | RFB.ws = new WebSocket(uri); |
5d8e7ec0 | 1249 | |
753bde8f | 1250 | RFB.ws.onmessage = RFB.recv_message; |
cc0410a3 | 1251 | RFB.ws.onopen = function(e) { |
8759ea6f JM |
1252 | console.log(">> WebSocket.onopen"); |
1253 | RFB.updateState('ProtocolVersion', "Starting VNC handshake"); | |
5d8e7ec0 JM |
1254 | RFB.sendID = setInterval(function() { |
1255 | /* | |
1256 | * Send updates either at a rate of one update every 50ms, | |
1257 | * or whatever slower rate the network can handle | |
1258 | */ | |
c4164bda | 1259 | if (RFB.ws.bufferedAmount === 0) { |
56ec48be JM |
1260 | if (RFB.SQ) { |
1261 | RFB.ws.send(RFB.SQ); | |
1262 | RFB.SQ = ""; | |
5d8e7ec0 JM |
1263 | } |
1264 | } else { | |
1265 | console.log("Delaying send"); | |
1266 | } | |
1267 | }, 50); | |
8759ea6f | 1268 | console.log("<< WebSocket.onopen"); |
65e27ddd | 1269 | }; |
cc0410a3 | 1270 | RFB.ws.onclose = function(e) { |
8759ea6f | 1271 | console.log(">> WebSocket.onclose"); |
5d8e7ec0 | 1272 | clearInterval(RFB.sendID); |
753bde8f | 1273 | RFB.updateState('disconnected', 'VNC disconnected'); |
8759ea6f | 1274 | console.log("<< WebSocket.onclose"); |
5d2c3864 JM |
1275 | }; |
1276 | RFB.ws.onerror = function(e) { | |
8759ea6f JM |
1277 | console.error(">> WebSocket.onerror"); |
1278 | console.error(" " + e); | |
1279 | console.error("<< WebSocket.onerror"); | |
5d2c3864 | 1280 | }; |
64ab5c4d | 1281 | |
b7ec5487 | 1282 | console.log("<< init_ws"); |
64ab5c4d | 1283 | }, |
65e27ddd | 1284 | |
5d8e7ec0 JM |
1285 | init_vars: function () { |
1286 | /* Reset state */ | |
56ec48be JM |
1287 | RFB.cuttext = 'none'; |
1288 | RFB.ct_length = 0; | |
1289 | RFB.RQ = []; | |
1290 | RFB.RQ_reorder = []; | |
1291 | RFB.RQ_seq_num = 0; | |
1292 | RFB.SQ = ""; | |
1293 | RFB.FBU.rects = 0; | |
1294 | RFB.FBU.subrects = 0; // RRE and HEXTILE | |
1295 | RFB.FBU.lines = 0; // RAW | |
1296 | RFB.FBU.tiles = 0; // HEXTILE | |
97bfe5ba | 1297 | RFB.mouse_buttonmask = 0; |
56ec48be | 1298 | RFB.mouse_arr = []; |
c4164bda | 1299 | } |
ded9dfae | 1300 | |
64ab5c4d | 1301 | }; /* End of RFB */ |