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