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