]>
Commit | Line | Data |
---|---|---|
64ab5c4d JM |
1 | Array.prototype.shift8 = function () { |
2 | return this.shift(); | |
65e27ddd | 3 | } |
64ab5c4d | 4 | Array.prototype.push8 = function (num) { |
489d1676 | 5 | this.push(num & 0xFF); |
65e27ddd JM |
6 | } |
7 | ||
64ab5c4d JM |
8 | Array.prototype.shift16 = function () { |
9 | return (this.shift() << 8) + | |
10 | (this.shift() ); | |
65e27ddd | 11 | } |
64ab5c4d | 12 | Array.prototype.push16 = function (num) { |
489d1676 JM |
13 | this.push((num >> 8) & 0xFF, |
14 | (num ) & 0xFF ); | |
65e27ddd JM |
15 | } |
16 | ||
489d1676 | 17 | |
64ab5c4d JM |
18 | Array.prototype.shift32 = function () { |
19 | return (this.shift() << 24) + | |
20 | (this.shift() << 16) + | |
21 | (this.shift() << 8) + | |
22 | (this.shift() ); | |
489d1676 | 23 | } |
64ab5c4d | 24 | Array.prototype.push32 = function (num) { |
489d1676 JM |
25 | this.push((num >> 24) & 0xFF, |
26 | (num >> 16) & 0xFF, | |
27 | (num >> 8) & 0xFF, | |
28 | (num ) & 0xFF ); | |
65e27ddd | 29 | } |
489d1676 | 30 | |
64ab5c4d JM |
31 | Array.prototype.shiftStr = function (len) { |
32 | var arr = this.splice(0, len); | |
33 | return arr.map(function (num) { | |
34 | return String.fromCharCode(num); } ).join(''); | |
35 | } | |
36 | ||
37 | Array.prototype.shiftBytes = function (len) { | |
38 | return this.splice(0, len); | |
65e27ddd JM |
39 | } |
40 | ||
cc0410a3 JM |
41 | /* |
42 | * Pending frame buffer update data | |
43 | */ | |
44 | var FBU = { | |
45 | rects : 0, | |
46 | bytes : 0, | |
47 | x : 0, | |
48 | y : 0, | |
49 | width : 0, | |
50 | height : 0, | |
51 | encoding : 0, | |
52 | arr : null}; | |
53 | ||
d9cbdc7d | 54 | |
65e27ddd | 55 | /* |
cc0410a3 | 56 | * RFB namespace |
65e27ddd JM |
57 | */ |
58 | ||
64ab5c4d JM |
59 | RFB = { |
60 | ||
cc0410a3 JM |
61 | ws : null, // Web Socket object |
62 | ||
63 | version : "RFB 003.003\n", | |
64 | state : 'ProtocolVersion', | |
65 | shared : 1, | |
64ab5c4d JM |
66 | poll_rate : 3000, |
67 | ||
cc0410a3 JM |
68 | host : '', |
69 | port : 5900, | |
70 | password : '', | |
71 | ||
72 | fb_width : 0, | |
73 | fb_height : 0, | |
74 | fb_name : "", | |
75 | fb_Bpp : 4, | |
76 | ||
77 | ||
78 | /* | |
79 | * Server message handlers | |
80 | */ | |
81 | ||
65e27ddd | 82 | /* RFB/VNC initialisation */ |
64ab5c4d | 83 | init_msg: function (data) { |
cc0410a3 | 84 | debug(">> init_msg: " + RFB.state); |
489d1676 | 85 | |
64ab5c4d | 86 | switch (RFB.state) { |
65e27ddd JM |
87 | |
88 | case 'ProtocolVersion' : | |
65e27ddd JM |
89 | if (data.length != 12) { |
90 | debug("Invalid protocol version from server"); | |
64ab5c4d | 91 | RFB.state = 'reset'; |
65e27ddd JM |
92 | return; |
93 | } | |
cc0410a3 JM |
94 | debug("Server ProtocolVersion: " + data.shiftStr(11)); |
95 | debug("Sending ProtocolVersion: " + RFB.version.substr(0,11)); | |
96 | RFB.send_string(RFB.version); | |
64ab5c4d | 97 | RFB.state = 'Authentication'; |
65e27ddd JM |
98 | break; |
99 | ||
100 | case 'Authentication' : | |
64ab5c4d JM |
101 | if (data.length < 4) { |
102 | debug("Invalid auth frame"); | |
103 | RFB.state = 'reset'; | |
65e27ddd JM |
104 | return; |
105 | } | |
64ab5c4d | 106 | var scheme = data.shift32(); |
65e27ddd JM |
107 | debug("Auth scheme: " + scheme); |
108 | switch (scheme) { | |
109 | case 0: // connection failed | |
64ab5c4d JM |
110 | var strlen = data.shift32(); |
111 | var reason = data.shiftStr(strlen); | |
65e27ddd | 112 | debug("auth failed: " + reason); |
64ab5c4d | 113 | RFB.state = "failed"; |
65e27ddd JM |
114 | return; |
115 | case 1: // no authentication | |
64ab5c4d JM |
116 | RFB.send_array([RFB.shared]); // ClientInitialisation |
117 | RFB.state = "ServerInitialisation"; | |
65e27ddd JM |
118 | break; |
119 | case 2: // VNC authentication | |
8580b989 | 120 | var challenge = data.shiftBytes(16); |
cc0410a3 JM |
121 | debug("Password: " + RFB.password); |
122 | debug("Challenge: " + challenge + "(" + challenge.length + ")"); | |
123 | passwd = RFB.passwdTwiddle(RFB.password); | |
124 | //debug("passwd: " + passwd + "(" + passwd.length + ")"); | |
5aeb9880 | 125 | response = des(passwd, challenge, 1); |
cc0410a3 | 126 | //debug("reponse: " + response + "(" + response.length + ")"); |
8580b989 JM |
127 | |
128 | RFB.send_array(response); | |
129 | RFB.state = "SecurityResult"; | |
65e27ddd JM |
130 | break; |
131 | } | |
132 | break; | |
133 | ||
64ab5c4d | 134 | case 'SecurityResult' : |
65e27ddd JM |
135 | if (data.length != 4) { |
136 | debug("Invalid server auth response"); | |
64ab5c4d | 137 | RFB.state = 'reset'; |
65e27ddd JM |
138 | return; |
139 | } | |
64ab5c4d | 140 | var resp = data.shift32(); |
65e27ddd JM |
141 | switch (resp) { |
142 | case 0: // OK | |
143 | debug("Authentication OK"); | |
144 | break; | |
145 | case 1: // failed | |
146 | debug("Authentication failed"); | |
64ab5c4d | 147 | RFB.state = "reset"; |
65e27ddd JM |
148 | return; |
149 | case 2: // too-many | |
150 | debug("Too many authentication attempts"); | |
64ab5c4d | 151 | RFB.state = "failed"; |
65e27ddd JM |
152 | return; |
153 | } | |
64ab5c4d JM |
154 | RFB.send_array([RFB.shared]); // ClientInitialisation |
155 | RFB.state = "ServerInitialisation"; | |
65e27ddd JM |
156 | break; |
157 | ||
158 | case 'ServerInitialisation' : | |
65e27ddd JM |
159 | if (data.length < 24) { |
160 | debug("Invalid server initialisation"); | |
64ab5c4d | 161 | RFB.state = 'reset'; |
65e27ddd JM |
162 | return; |
163 | } | |
489d1676 JM |
164 | |
165 | /* Screen size */ | |
64ab5c4d | 166 | //debug("data: " + data); |
cc0410a3 JM |
167 | RFB.fb_width = data.shift16(); |
168 | RFB.fb_height = data.shift16(); | |
489d1676 | 169 | |
cc0410a3 | 170 | debug("Screen size: " + RFB.fb_width + "x" + RFB.fb_height); |
489d1676 JM |
171 | |
172 | /* PIXEL_FORMAT */ | |
64ab5c4d JM |
173 | var bpp = data.shift8(); |
174 | var depth = data.shift8(); | |
175 | var big_endian = data.shift8(); | |
176 | var true_color = data.shift8(); | |
489d1676 | 177 | |
64ab5c4d | 178 | debug("bpp: " + bpp); |
489d1676 JM |
179 | debug("depth: " + depth); |
180 | debug("big_endian: " + big_endian); | |
181 | debug("true_color: " + true_color); | |
182 | ||
183 | /* Connection name/title */ | |
64ab5c4d JM |
184 | data.shiftStr(12); |
185 | var name_length = data.shift32(); | |
cc0410a3 | 186 | RFB.fb_name = data.shiftStr(name_length); |
489d1676 | 187 | |
cc0410a3 JM |
188 | debug("Name: " + RFB.fb_name); |
189 | $('status').innerHTML = "Connected to: " + RFB.fb_name; | |
489d1676 | 190 | |
cc0410a3 | 191 | Canvas.init('vnc', RFB.fb_width, RFB.fb_height, RFB.keyDown, RFB.keyUp); |
64ab5c4d JM |
192 | |
193 | RFB.setEncodings(); | |
194 | RFB.setPixelFormat(); | |
489d1676 | 195 | |
cc0410a3 | 196 | RFB.fbUpdateRequest(0, 0, 0, RFB.fb_width, RFB.fb_height); |
489d1676 | 197 | |
64ab5c4d | 198 | RFB.state = 'normal'; |
65e27ddd JM |
199 | break; |
200 | } | |
cc0410a3 | 201 | debug("<< init_msg (" + RFB.state + ")"); |
64ab5c4d | 202 | }, |
65e27ddd JM |
203 | |
204 | /* Normal RFB/VNC messages */ | |
64ab5c4d JM |
205 | normal_msg: function (data) { |
206 | //debug(">> normal_msg"); | |
cc0410a3 | 207 | if ((FBU.rects > 0) || (FBU.bytes > 0)) { |
64ab5c4d | 208 | var msg_type = 0; |
c8460b03 | 209 | } else { |
64ab5c4d | 210 | var msg_type = data.shift8(); |
489d1676 | 211 | } |
65e27ddd JM |
212 | switch (msg_type) { |
213 | case 0: // FramebufferUpdate | |
cc0410a3 | 214 | if (FBU.rects == 0) { |
64ab5c4d | 215 | data.shift8(); |
cc0410a3 JM |
216 | FBU.rects = data.shift16(); |
217 | debug("FramebufferUpdate, " + FBU.rects + " rects"); | |
218 | FBU.bytes = 0; | |
219 | FBU.arr = []; | |
64ab5c4d JM |
220 | } else { |
221 | //debug("FramebufferUpdate continuation"); | |
222 | } | |
223 | ||
224 | while (data.length > 0) { | |
225 | //debug("data.length: " + data.length); | |
cc0410a3 JM |
226 | if (FBU.bytes == 0) { |
227 | FBU.x = data.shift16(); | |
228 | FBU.y = data.shift16(); | |
229 | FBU.width = data.shift16(); | |
230 | FBU.height = data.shift16(); | |
48617e27 JM |
231 | FBU.encoding = parseInt(data.shift32(), 10); |
232 | //debug("encoding: " + FBU.encoding); | |
cc0410a3 JM |
233 | //debug('New rect: ' + FBU.x + "," + FBU.y + " -> " + (FBU.x + FBU.width) + "," + (FBU.y + FBU.height)); |
234 | switch (FBU.encoding) { | |
64ab5c4d | 235 | case 0: // Raw |
cc0410a3 | 236 | FBU.bytes = FBU.width * FBU.height * RFB.fb_Bpp; |
64ab5c4d JM |
237 | break; |
238 | case 1: // Copy-Rect | |
48617e27 | 239 | FBU.bytes = 4; |
64ab5c4d JM |
240 | break; |
241 | } | |
242 | } else { | |
cc0410a3 | 243 | if (data.length >= FBU.bytes) { |
48617e27 | 244 | //debug('Done rect:'); |
cc0410a3 JM |
245 | FBU.arr = FBU.arr.concat(data.shiftBytes(FBU.bytes)) |
246 | FBU.bytes = 0; | |
64ab5c4d | 247 | |
cc0410a3 | 248 | switch (FBU.encoding) { |
64ab5c4d | 249 | case 0: // Raw |
48617e27 | 250 | //debug('Raw-Rect: (' + FBU.x + "," + FBU.y + ")X(" + (FBU.x + FBU.width) + "," + (FBU.y + FBU.height) + ")"); |
cc0410a3 | 251 | Canvas.rfbImage(FBU.x, FBU.y, FBU.width, FBU.height, FBU.arr); |
64ab5c4d JM |
252 | break; |
253 | case 1: // Copy-Rect | |
48617e27 JM |
254 | var old_x = FBU.arr.shift16(); |
255 | var old_y = FBU.arr.shift16(); | |
256 | //debug('Copy-Rect: (' + old_x + "," + old_y + ")X(" + (FBU.x + FBU.width) + "," + (FBU.y + FBU.height) + ") -> (" + FBU.x + "," + FBU.y + ")"); | |
257 | ||
258 | Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); | |
64ab5c4d JM |
259 | break; |
260 | } | |
cc0410a3 JM |
261 | FBU.arr = []; |
262 | FBU.rects --; | |
64ab5c4d | 263 | } else { |
cc0410a3 JM |
264 | //debug('Part rect: ' + FBU.x + "," + FBU.y + " -> " + (FBU.x + FBU.width) + "," + (FBU.y + FBU.height)); |
265 | FBU.bytes = FBU.bytes - data.length; | |
266 | FBU.arr = FBU.arr.concat(data.shiftBytes(data.length)) | |
64ab5c4d JM |
267 | } |
268 | } | |
269 | ||
cc0410a3 | 270 | //debug("Bytes remaining: " + FBU.bytes); |
64ab5c4d JM |
271 | } |
272 | //debug("Finished frame buffer update"); | |
65e27ddd JM |
273 | break; |
274 | case 1: // SetColourMapEntries | |
275 | debug("SetColourMapEntries"); | |
276 | break; | |
277 | case 2: // Bell | |
278 | debug("Bell"); | |
279 | break; | |
280 | case 3: // ServerCutText | |
281 | debug("ServerCutText"); | |
282 | break; | |
283 | default: | |
284 | debug("Unknown server message type: " + msg_type); | |
285 | break; | |
286 | } | |
64ab5c4d JM |
287 | //debug("<< normal_msg"); |
288 | }, | |
65e27ddd JM |
289 | |
290 | /* | |
291 | * Client message routines | |
292 | */ | |
293 | ||
64ab5c4d JM |
294 | setPixelFormat: function () { |
295 | debug(">> setPixelFormat"); | |
296 | var arr = [0]; // msg-type | |
297 | arr.push8(0); // padding | |
298 | arr.push8(0); // padding | |
299 | arr.push8(0); // padding | |
300 | ||
cc0410a3 | 301 | arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel |
64ab5c4d JM |
302 | arr.push8(24); // depth |
303 | arr.push8(0); // little-endian | |
304 | arr.push8(1); // true-color | |
305 | ||
306 | arr.push16(255); // red-max | |
307 | arr.push16(255); // green-max | |
308 | arr.push16(255); // blue-max | |
309 | arr.push8(16); // red-shift | |
310 | arr.push8(8); // green-shift | |
311 | arr.push8(0); // blue-shift | |
312 | ||
313 | arr.push8(0); // padding | |
314 | arr.push8(0); // padding | |
315 | arr.push8(0); // padding | |
316 | RFB.send_array(arr); | |
317 | debug("<< setPixelFormat"); | |
318 | }, | |
319 | ||
320 | fixColourMapEntries: function () { | |
321 | }, | |
322 | ||
323 | setEncodings: function () { | |
489d1676 JM |
324 | debug(">> setEncodings"); |
325 | var arr = [2]; // msg-type | |
64ab5c4d JM |
326 | arr.push8(0); // padding |
327 | arr.push16(2); // encoding count | |
328 | arr.push32(1); // copy-rect encoding | |
329 | arr.push32(0); // raw encoding | |
330 | RFB.send_array(arr); | |
489d1676 | 331 | debug("<< setEncodings"); |
64ab5c4d | 332 | }, |
65e27ddd | 333 | |
64ab5c4d | 334 | fbUpdateRequest: function (incremental, x, y, xw, yw) { |
d9cbdc7d | 335 | //debug(">> fbUpdateRequest"); |
489d1676 | 336 | var arr = [3]; // msg-type |
64ab5c4d JM |
337 | arr.push8(incremental); |
338 | arr.push16(x); | |
339 | arr.push16(y); | |
340 | arr.push16(xw); | |
341 | arr.push16(yw); | |
342 | RFB.send_array(arr); | |
d9cbdc7d | 343 | //debug("<< fbUpdateRequest"); |
64ab5c4d | 344 | }, |
65e27ddd | 345 | |
d9cbdc7d JM |
346 | keyEvent: function (keysym, down) { |
347 | debug(">> keyEvent, keysym: " + keysym + ", down: " + down); | |
64ab5c4d JM |
348 | var arr = [4]; // msg-type |
349 | arr.push8(down); | |
350 | arr.push16(0); | |
d9cbdc7d JM |
351 | arr.push32(keysym); |
352 | //debug("keyEvent array: " + arr); | |
64ab5c4d | 353 | RFB.send_array(arr); |
cc0410a3 | 354 | RFB.fbUpdateRequest(1, 0, 0, RFB.fb_width, RFB.fb_height); |
d9cbdc7d | 355 | //debug("<< keyEvent"); |
64ab5c4d | 356 | }, |
65e27ddd | 357 | |
64ab5c4d JM |
358 | pointerEvent: function () { |
359 | }, | |
65e27ddd | 360 | |
64ab5c4d JM |
361 | clientCutText: function () { |
362 | }, | |
363 | ||
364 | ||
365 | /* | |
366 | * Utility routines | |
367 | */ | |
368 | ||
369 | send_string: function (str) { | |
d9cbdc7d | 370 | //debug(">> send_string: " + str); |
cc0410a3 JM |
371 | var arr = str.split('').map(function (chr) { |
372 | return chr.charCodeAt(0) } ); | |
373 | RFB.send_array(arr); | |
64ab5c4d JM |
374 | }, |
375 | ||
376 | send_array: function (arr) { | |
d9cbdc7d | 377 | //debug(">> send_array: " + Base64.encode_array(arr)); |
cc0410a3 | 378 | RFB.ws.send(Base64.encode_array(arr)); |
64ab5c4d JM |
379 | }, |
380 | ||
532a9fd9 JM |
381 | /* Mirror bits of each character and return as array */ |
382 | passwdTwiddle: function (passwd) { | |
383 | var arr = []; | |
384 | for (var i=0; i< passwd.length; i++) { | |
385 | var c = passwd.charCodeAt(i); | |
386 | arr.push( ((c & 0x80) >> 7) + | |
387 | ((c & 0x40) >> 5) + | |
388 | ((c & 0x20) >> 3) + | |
389 | ((c & 0x10) >> 1) + | |
390 | ((c & 0x08) << 1) + | |
391 | ((c & 0x04) << 3) + | |
392 | ((c & 0x02) << 5) + | |
393 | ((c & 0x01) << 7) ); | |
394 | } | |
395 | return arr; | |
396 | }, | |
397 | ||
64ab5c4d JM |
398 | poller: function () { |
399 | if (RFB.state == 'normal') { | |
cc0410a3 | 400 | RFB.fbUpdateRequest(1, 0, 0, RFB.fb_width, RFB.fb_height); |
64ab5c4d JM |
401 | RFB.poller.delay(RFB.poll_rate); |
402 | } | |
403 | }, | |
404 | ||
405 | keyDown: function (e) { | |
d9cbdc7d | 406 | //debug(">> keyDown: " + e.key + "(" + e.code + ")"); |
64ab5c4d | 407 | e.stop(); |
d9cbdc7d | 408 | RFB.keyEvent(Canvas.getKeysym(e), 1); |
64ab5c4d JM |
409 | }, |
410 | ||
411 | keyUp: function (e) { | |
d9cbdc7d | 412 | //debug(">> keyUp: " + e.key + "(" + e.code + ")"); |
64ab5c4d | 413 | e.stop(); |
d9cbdc7d | 414 | RFB.keyEvent(Canvas.getKeysym(e), 0); |
64ab5c4d | 415 | }, |
65e27ddd JM |
416 | |
417 | ||
418 | /* | |
419 | * Setup routines | |
420 | */ | |
421 | ||
532a9fd9 JM |
422 | init_ws: function () { |
423 | debug(">> init_ws"); | |
cc0410a3 | 424 | var uri = "ws://" + RFB.host + ":" + RFB.port; |
65e27ddd | 425 | debug("connecting to " + uri); |
cc0410a3 JM |
426 | RFB.ws = new WebSocket(uri); |
427 | RFB.ws.onmessage = function(e) { | |
64ab5c4d | 428 | //debug(">> onmessage"); |
489d1676 JM |
429 | var data = Base64.decode_array(e.data); |
430 | //debug("decoded array: " + data); | |
64ab5c4d JM |
431 | if (RFB.state != 'normal') { |
432 | RFB.init_msg(data); | |
65e27ddd | 433 | } else { |
64ab5c4d | 434 | RFB.normal_msg(data); |
65e27ddd | 435 | } |
64ab5c4d | 436 | if (RFB.state == 'reset') { |
65e27ddd | 437 | /* close and reset connection */ |
532a9fd9 JM |
438 | RFB.disconnect(); |
439 | RFB.init_ws(); | |
64ab5c4d JM |
440 | } else if (RFB.state == 'failed') { |
441 | debug("Giving up!"); | |
532a9fd9 | 442 | RFB.disconnect(); |
65e27ddd | 443 | } |
64ab5c4d | 444 | //debug("<< onmessage"); |
65e27ddd | 445 | }; |
cc0410a3 | 446 | RFB.ws.onopen = function(e) { |
65e27ddd | 447 | debug(">> onopen"); |
64ab5c4d | 448 | RFB.state = "ProtocolVersion"; |
65e27ddd JM |
449 | debug("<< onopen"); |
450 | }; | |
cc0410a3 | 451 | RFB.ws.onclose = function(e) { |
65e27ddd | 452 | debug(">> onclose"); |
64ab5c4d | 453 | RFB.state = "closed"; |
65e27ddd JM |
454 | debug("<< onclose"); |
455 | } | |
64ab5c4d JM |
456 | RFB.poller.delay(RFB.poll_rate); |
457 | ||
532a9fd9 | 458 | debug("<< init_ws"); |
64ab5c4d | 459 | }, |
65e27ddd | 460 | |
532a9fd9 JM |
461 | connect: function () { |
462 | debug(">> connect"); | |
cc0410a3 JM |
463 | RFB.host = $('host').value; |
464 | RFB.port = $('port').value; | |
465 | RFB.password = $('password').value; | |
532a9fd9 JM |
466 | if ((!host) || (!port)) { |
467 | debug("must set host and port"); | |
468 | return; | |
469 | } | |
cc0410a3 JM |
470 | if (RFB.ws) { |
471 | RFB.ws.close(); | |
65e27ddd | 472 | } |
532a9fd9 JM |
473 | RFB.init_ws(); |
474 | $('connectButton').value = "Disconnect"; | |
475 | $('connectButton').onclick = RFB.disconnect; | |
476 | debug("<< connect"); | |
477 | ||
478 | }, | |
65e27ddd | 479 | |
532a9fd9 JM |
480 | disconnect: function () { |
481 | debug(">> disconnect"); | |
cc0410a3 JM |
482 | if (RFB.ws) { |
483 | RFB.ws.close(); | |
532a9fd9 JM |
484 | } |
485 | if (Canvas.ctx) { | |
486 | Canvas.clear(); | |
487 | } | |
488 | $('connectButton').value = "Connect"; | |
489 | $('connectButton').onclick = RFB.connect; | |
490 | $('status').innerHTML = "Disconnected"; | |
491 | debug("<< disconnect"); | |
65e27ddd JM |
492 | } |
493 | ||
64ab5c4d | 494 | }; /* End of RFB */ |