- web-socket-js is from http://github.com/gimite/web-socket-js. It is a flash object that emultates WebSockets. Unfortunately, events (or packets) from the web-socket-js object can get re-ordered so we need to know the packet order. - So wsproxy.py prepends the sequence number of the packet when sending. - If the client receives packets out of order it queues them up and scans the queue for the sequence number it's looking for until things are back on track. Gross, but hey: It works! - Also, add packet sequence checking to wstest.*
987 lines
29 KiB
JavaScript
987 lines
29 KiB
JavaScript
Array.prototype.shift8 = function () {
|
|
return this.shift();
|
|
}
|
|
Array.prototype.push8 = function (num) {
|
|
this.push(num & 0xFF);
|
|
}
|
|
|
|
Array.prototype.shift16 = function () {
|
|
return (this.shift() << 8) +
|
|
(this.shift() );
|
|
}
|
|
Array.prototype.push16 = function (num) {
|
|
this.push((num >> 8) & 0xFF,
|
|
(num ) & 0xFF );
|
|
}
|
|
|
|
|
|
Array.prototype.shift32 = function () {
|
|
return (this.shift() << 24) +
|
|
(this.shift() << 16) +
|
|
(this.shift() << 8) +
|
|
(this.shift() );
|
|
}
|
|
Array.prototype.push32 = function (num) {
|
|
this.push((num >> 24) & 0xFF,
|
|
(num >> 16) & 0xFF,
|
|
(num >> 8) & 0xFF,
|
|
(num ) & 0xFF );
|
|
}
|
|
|
|
Array.prototype.shiftStr = function (len) {
|
|
var arr = this.splice(0, len);
|
|
return arr.map(function (num) {
|
|
return String.fromCharCode(num); } ).join('');
|
|
}
|
|
Array.prototype.pushStr = function (str) {
|
|
var n = str.length;
|
|
for (var i=0; i < n; i++) {
|
|
this.push(str.charCodeAt(i));
|
|
}
|
|
}
|
|
|
|
Array.prototype.shiftBytes = function (len) {
|
|
return this.splice(0, len);
|
|
}
|
|
|
|
/*
|
|
* Frame buffer update state
|
|
*/
|
|
FBU = {
|
|
rects : 0,
|
|
subrects : 0, // RRE and HEXTILE
|
|
lines : 0, // RAW
|
|
tiles : 0, // HEXTILE
|
|
bytes : 0,
|
|
x : 0,
|
|
y : 0,
|
|
width : 0,
|
|
height : 0,
|
|
encoding : 0,
|
|
subencoding : -1,
|
|
background: null};
|
|
|
|
/*
|
|
* Mouse state
|
|
*/
|
|
Mouse = {
|
|
buttonmask : 0,
|
|
arr : []
|
|
};
|
|
|
|
|
|
/*
|
|
* RFB namespace
|
|
*/
|
|
|
|
RQ = []; // Receive Queue
|
|
RQ_reorder = []; // Receive Queue re-order list
|
|
RQ_seq_num = 0; // Expected sequence number
|
|
SQ = ""; // Send Queue
|
|
|
|
RFB = {
|
|
|
|
ws : null, // Web Socket object
|
|
sendID : null,
|
|
force_copy : false,
|
|
|
|
version : "RFB 003.003\n",
|
|
state : 'ProtocolVersion',
|
|
cuttext : 'none', // ServerCutText wait state
|
|
ct_length : 0,
|
|
clipboardFocus: false,
|
|
|
|
shared : 1,
|
|
check_rate : 217,
|
|
req_rate : 1413,
|
|
last_req : 0,
|
|
|
|
host : '',
|
|
port : 5900,
|
|
password : '',
|
|
|
|
fb_width : 0,
|
|
fb_height : 0,
|
|
fb_name : "",
|
|
fb_Bpp : 4,
|
|
rre_chunk : 100,
|
|
|
|
|
|
/*
|
|
* Server message handlers
|
|
*/
|
|
|
|
/* RFB/VNC initialisation */
|
|
init_msg: function () {
|
|
console.log(">> init_msg: " + RFB.state);
|
|
|
|
switch (RFB.state) {
|
|
|
|
case 'ProtocolVersion' :
|
|
if (RQ.length != 12) {
|
|
console.log("Invalid protocol version from server");
|
|
//RFB.state = 'reset';
|
|
RFB.state = 'failed';
|
|
return;
|
|
}
|
|
var server_version = RQ.shiftStr(12);
|
|
console.log("Server ProtocolVersion: " + server_version.substr(0,11));
|
|
console.log("Sending ProtocolVersion: " + RFB.version.substr(0,11));
|
|
RFB.send_string(RFB.version);
|
|
RFB.state = 'Authentication';
|
|
break;
|
|
|
|
case 'Authentication' :
|
|
if (RQ.length < 4) {
|
|
console.log("Invalid auth frame");
|
|
RFB.state = 'reset';
|
|
return;
|
|
}
|
|
var scheme = RQ.shift32();
|
|
console.log("Auth scheme: " + scheme);
|
|
switch (scheme) {
|
|
case 0: // connection failed
|
|
var strlen = RQ.shift32();
|
|
var reason = RQ.shiftStr(strlen);
|
|
console.log("auth failed: " + reason);
|
|
RFB.state = "failed";
|
|
return;
|
|
case 1: // no authentication
|
|
RFB.send_array([RFB.shared]); // ClientInitialisation
|
|
RFB.state = "ServerInitialisation";
|
|
break;
|
|
case 2: // VNC authentication
|
|
var challenge = RQ.shiftBytes(16);
|
|
console.log("Password: " + RFB.password);
|
|
console.log("Challenge: " + challenge + "(" + challenge.length + ")");
|
|
passwd = RFB.passwdTwiddle(RFB.password);
|
|
//console.log("passwd: " + passwd + "(" + passwd.length + ")");
|
|
response = des(passwd, challenge, 1);
|
|
//console.log("reponse: " + response + "(" + response.length + ")");
|
|
|
|
RFB.send_array(response);
|
|
RFB.state = "SecurityResult";
|
|
break;
|
|
default:
|
|
console.log("Unsupported auth scheme");
|
|
RFB.state = "failed";
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'SecurityResult' :
|
|
if (RQ.length != 4) {
|
|
console.log("Invalid server auth response");
|
|
RFB.state = 'reset';
|
|
return;
|
|
}
|
|
var resp = RQ.shift32();
|
|
switch (resp) {
|
|
case 0: // OK
|
|
console.log("Authentication OK");
|
|
break;
|
|
case 1: // failed
|
|
console.log("Authentication failed");
|
|
RFB.state = "reset";
|
|
return;
|
|
case 2: // too-many
|
|
console.log("Too many authentication attempts");
|
|
RFB.state = "failed";
|
|
return;
|
|
}
|
|
RFB.send_array([RFB.shared]); // ClientInitialisation
|
|
RFB.state = "ServerInitialisation";
|
|
break;
|
|
|
|
case 'ServerInitialisation' :
|
|
if (RQ.length < 24) {
|
|
console.log("Invalid server initialisation");
|
|
RFB.state = 'reset';
|
|
return;
|
|
}
|
|
|
|
/* Screen size */
|
|
//console.log("RQ: " + RQ);
|
|
RFB.fb_width = RQ.shift16();
|
|
RFB.fb_height = RQ.shift16();
|
|
|
|
console.log("Screen size: " + RFB.fb_width + "x" + RFB.fb_height);
|
|
|
|
/* PIXEL_FORMAT */
|
|
var bpp = RQ.shift8();
|
|
var depth = RQ.shift8();
|
|
var big_endian = RQ.shift8();
|
|
var true_color = RQ.shift8();
|
|
|
|
console.log("bpp: " + bpp);
|
|
console.log("depth: " + depth);
|
|
console.log("big_endian: " + big_endian);
|
|
console.log("true_color: " + true_color);
|
|
|
|
/* Connection name/title */
|
|
RQ.shiftStr(12);
|
|
var name_length = RQ.shift32();
|
|
RFB.fb_name = RQ.shiftStr(name_length);
|
|
|
|
console.log("Name: " + RFB.fb_name);
|
|
$('status').innerHTML = "Connected to: " + RFB.fb_name;
|
|
|
|
Canvas.init('vnc', RFB.fb_width, RFB.fb_height,
|
|
RFB.keyDown, RFB.keyUp,
|
|
RFB.mouseDown, RFB.mouseUp, RFB.mouseMove);
|
|
|
|
var init = [];
|
|
init = init.concat(RFB.pixelFormat());
|
|
init = init.concat(RFB.encodings());
|
|
init = init.concat(RFB.fbUpdateRequest(0));
|
|
RFB.send_array(init);
|
|
|
|
/* Start pushing/polling */
|
|
RFB.checkEvents.delay(RFB.check_rate);
|
|
|
|
RFB.state = 'normal';
|
|
break;
|
|
}
|
|
console.log("<< init_msg (" + RFB.state + ")");
|
|
},
|
|
|
|
|
|
/* Normal RFB/VNC server messages */
|
|
normal_msg: function () {
|
|
//console.log(">> normal_msg");
|
|
var ret = true;
|
|
if (FBU.rects > 0) {
|
|
var msg_type = 0;
|
|
} else if (RFB.cuttext != 'none') {
|
|
var msg_type = 3;
|
|
} else {
|
|
var msg_type = RQ.shift8();
|
|
}
|
|
switch (msg_type) {
|
|
case 0: // FramebufferUpdate
|
|
if (FBU.rects == 0) {
|
|
if (RQ.length < 3) {
|
|
RQ.unshift(msg_type);
|
|
console.log(" waiting for FBU header bytes");
|
|
return false;
|
|
}
|
|
RQ.shift8();
|
|
FBU.rects = RQ.shift16();
|
|
//console.log("FramebufferUpdate, rects:" + FBU.rects);
|
|
FBU.bytes = 0;
|
|
}
|
|
|
|
while ((FBU.rects > 0) && (RQ.length >= FBU.bytes)) {
|
|
if (FBU.bytes == 0) {
|
|
if (RQ.length < 12) {
|
|
console.log(" waiting for rect header bytes");
|
|
return false;
|
|
}
|
|
/* New FramebufferUpdate */
|
|
FBU.x = RQ.shift16();
|
|
FBU.y = RQ.shift16();
|
|
FBU.width = RQ.shift16();
|
|
FBU.height = RQ.shift16();
|
|
FBU.encoding = parseInt(RQ.shift32(), 10);
|
|
|
|
// Debug:
|
|
/*
|
|
var msg = "FramebufferUpdate rects:" + FBU.rects + " encoding:" + FBU.encoding
|
|
switch (FBU.encoding) {
|
|
case 0: msg += "(RAW)"; break;
|
|
case 1: msg += "(COPY-RECT)"; break;
|
|
case 2: msg += "(RRE)"; break;
|
|
case 5: msg += "(HEXTILE " + FBU.tiles + " tiles)"; break;
|
|
default:
|
|
console.log("Unsupported encoding " + FBU.encoding);
|
|
RFB.state = "failed";
|
|
return false;
|
|
}
|
|
msg += ", RQ.length: " + RQ.length
|
|
console.log(msg);
|
|
*/
|
|
}
|
|
|
|
//console.log("> RQ.length: " + RQ.length + ", arr[0..30]: " + RQ.slice(0,30));
|
|
switch (FBU.encoding) {
|
|
case 0: ret = RFB.display_raw(); break; // Raw
|
|
case 1: ret = RFB.display_copy_rect(); break; // Copy-Rect
|
|
case 2: ret = RFB.display_rre(); break; // RRE
|
|
case 5: ret = RFB.display_hextile(); break; // hextile
|
|
}
|
|
//console.log("< RQ.length: " + RQ.length + ", FBU.bytes: " + FBU.bytes);
|
|
if (RFB.state != "normal") return true;
|
|
}
|
|
|
|
//console.log("Finished frame buffer update");
|
|
break;
|
|
case 1: // SetColourMapEntries
|
|
console.log("SetColourMapEntries (unsupported)");
|
|
RQ.shift8(); // Padding
|
|
RQ.shift16(); // First colour
|
|
var num_colours = RQ.shift16();
|
|
RQ.shiftBytes(num_colours * 6);
|
|
break;
|
|
case 2: // Bell
|
|
console.log("Bell (unsupported)");
|
|
break;
|
|
case 3: // ServerCutText
|
|
console.log("ServerCutText");
|
|
console.log("RQ:" + RQ.slice(0,20));
|
|
if (RFB.cuttext == 'none') {
|
|
RFB.cuttext = 'header';
|
|
}
|
|
if (RFB.cuttext == 'header') {
|
|
if (RQ.length < 7) {
|
|
console.log("waiting for ServerCutText header");
|
|
return false;
|
|
}
|
|
RQ.shiftBytes(3); // Padding
|
|
RFB.ct_length = RQ.shift32();
|
|
}
|
|
RFB.cuttext = 'bytes';
|
|
if (RQ.length < RFB.ct_length) {
|
|
console.log("waiting for ServerCutText bytes");
|
|
return false;
|
|
}
|
|
RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length));
|
|
RFB.cuttext = 'none';
|
|
break;
|
|
default:
|
|
console.error("Illegal server message type: " + msg_type);
|
|
console.log("RQ.slice(0,30):" + RQ.slice(0,30));
|
|
RFB.state = "failed";
|
|
break;
|
|
}
|
|
//console.log("<< normal_msg");
|
|
return ret;
|
|
},
|
|
|
|
|
|
/*
|
|
* FramebufferUpdate encodings
|
|
*/
|
|
|
|
display_raw: function () {
|
|
//console.log(">> display_raw");
|
|
if (FBU.lines == 0) {
|
|
FBU.lines = FBU.height;
|
|
}
|
|
FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line
|
|
if (RQ.length < FBU.bytes) {
|
|
//console.log(" waiting for " + (FBU.bytes - RQ.length) + " RAW bytes");
|
|
return;
|
|
}
|
|
var cur_y = FBU.y + (FBU.height - FBU.lines);
|
|
var cur_height = Math.min(FBU.lines, Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp)));
|
|
//console.log("cur_y:" + cur_y + ", cur_height:" + cur_height);
|
|
Canvas.rgbxImage(FBU.x, cur_y, FBU.width, cur_height, RQ);
|
|
RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp);
|
|
FBU.lines -= cur_height;
|
|
|
|
if (FBU.lines > 0) {
|
|
FBU.bytes = FBU.width * RFB.fb_Bpp; // At least another line
|
|
} else {
|
|
FBU.rects --;
|
|
FBU.bytes = 0;
|
|
}
|
|
},
|
|
|
|
display_copy_rect: function () {
|
|
//console.log(">> display_copy_rect");
|
|
if (RQ.length < 4) {
|
|
//console.log(" waiting for " + (FBU.bytes - RQ.length) + " COPY-RECT bytes");
|
|
return;
|
|
}
|
|
var old_x = RQ.shift16();
|
|
var old_y = RQ.shift16();
|
|
Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
|
|
FBU.rects --;
|
|
FBU.bytes = 0;
|
|
},
|
|
|
|
display_rre: function () {
|
|
//console.log(">> display_rre (" + RQ.length + " bytes)");
|
|
if (FBU.subrects == 0) {
|
|
;
|
|
if (RQ.length < 4 + RFB.fb_Bpp) {
|
|
//console.log(" waiting for " + (4 + RFB.fb_Bpp - RQ.length) + " RRE bytes");
|
|
return;
|
|
}
|
|
FBU.subrects = RQ.shift32();
|
|
//console.log(">> display_rre " + "(" + FBU.subrects + " subrects)");
|
|
var color = RQ.shiftBytes(RFB.fb_Bpp); // Background
|
|
Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
|
|
}
|
|
while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) {
|
|
var color = RQ.shiftBytes(RFB.fb_Bpp);
|
|
var x = RQ.shift16();
|
|
var y = RQ.shift16();
|
|
var width = RQ.shift16();
|
|
var height = RQ.shift16();
|
|
Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color);
|
|
FBU.subrects --;
|
|
}
|
|
//console.log(" display_rre: rects: " + FBU.rects + ", FBU.subrects: " + FBU.subrects);
|
|
|
|
if (FBU.subrects > 0) {
|
|
var chunk = Math.min(RFB.rre_chunk, FBU.subrects);
|
|
FBU.bytes = (RFB.fb_Bpp + 8) * chunk;
|
|
} else {
|
|
FBU.rects --;
|
|
FBU.bytes = 0;
|
|
}
|
|
//console.log("<< display_rre, FBU.bytes: " + FBU.bytes);
|
|
},
|
|
|
|
display_hextile: function() {
|
|
//console.log(">> display_hextile, tiles: " + FBU.tiles + ", arr.length: " + RQ.length + ", bytes: " + FBU.bytes);
|
|
var subencoding, subrects, cur_tile, tile_x, x, w, tile_y, y, h;
|
|
|
|
if (FBU.tiles == 0) {
|
|
FBU.tiles_x = Math.ceil(FBU.width/16);
|
|
FBU.tiles_y = Math.ceil(FBU.height/16);
|
|
FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
|
|
FBU.tiles = FBU.total_tiles;
|
|
}
|
|
|
|
/* FBU.bytes comes in as 1, RQ.length at least 1 */
|
|
while (FBU.tiles > 0) {
|
|
FBU.bytes = 1;
|
|
if (RQ.length < FBU.bytes) {
|
|
console.log(" waiting for HEXTILE subencoding byte");
|
|
return;
|
|
}
|
|
subencoding = RQ[0]; // Peek
|
|
if (subencoding > 30) { // Raw
|
|
console.error("Illegal subencoding " + subencoding);
|
|
console.log("RQ.slice(0,30):" + RQ.slice(0,30));
|
|
RFB.state = "failed";
|
|
return;
|
|
}
|
|
subrects = 0;
|
|
cur_tile = FBU.total_tiles - FBU.tiles;
|
|
tile_x = cur_tile % FBU.tiles_x;
|
|
tile_y = Math.floor(cur_tile / FBU.tiles_x);
|
|
x = FBU.x + tile_x * 16;
|
|
y = FBU.y + tile_y * 16;
|
|
w = Math.min(16, (FBU.x + FBU.width) - x)
|
|
h = Math.min(16, (FBU.y + FBU.height) - y)
|
|
|
|
/* Figure out how much we are expecting */
|
|
if (subencoding & 0x01) { // Raw
|
|
//console.log(" Raw subencoding");
|
|
FBU.bytes += w * h * RFB.fb_Bpp;
|
|
} else {
|
|
if (subencoding & 0x02) { // Background
|
|
FBU.bytes += RFB.fb_Bpp;
|
|
}
|
|
if (subencoding & 0x04) { // Foreground
|
|
FBU.bytes += RFB.fb_Bpp;
|
|
}
|
|
if (subencoding & 0x08) { // AnySubrects
|
|
FBU.bytes++; // Since we aren't shifting it off
|
|
if (RQ.length < FBU.bytes) {
|
|
/* Wait for subrects byte */
|
|
console.log(" waiting for hextile subrects header byte");
|
|
return;
|
|
}
|
|
subrects = RQ[FBU.bytes-1]; // Peek
|
|
if (subencoding & 0x10) { // SubrectsColoured
|
|
FBU.bytes += subrects * (RFB.fb_Bpp + 2);
|
|
} else {
|
|
FBU.bytes += subrects * 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
//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));
|
|
//console.log(" arr[0..30]: " + RQ.slice(0,30));
|
|
if (RQ.length < FBU.bytes) {
|
|
//console.log(" waiting for " + (FBU.bytes - RQ.length) + " hextile bytes");
|
|
return;
|
|
}
|
|
|
|
/* We know the encoding and have a whole tile */
|
|
FBU.subencoding = RQ.shift8();
|
|
FBU.bytes--;
|
|
if (FBU.subencoding == 0) {
|
|
if (FBU.lastsubencoding & 0x01) {
|
|
/* Weird: ignore blanks after RAW */
|
|
console.log(" Ignoring blank after RAW");
|
|
continue;
|
|
}
|
|
Canvas.fillRect(x, y, w, h, FBU.background);
|
|
} else if (FBU.subencoding & 0x01) { // Raw
|
|
Canvas.rgbxImage(x, y, w, h, RQ);
|
|
} else {
|
|
var idx = 0;
|
|
if (FBU.subencoding & 0x02) { // Background
|
|
FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp);
|
|
idx += RFB.fb_Bpp;
|
|
//console.log(" background: " + FBU.background);
|
|
}
|
|
if (FBU.subencoding & 0x04) { // Foreground
|
|
FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp);
|
|
idx += RFB.fb_Bpp;
|
|
//console.log(" foreground: " + FBU.foreground);
|
|
}
|
|
Canvas.fillRect(x, y, w, h, FBU.background);
|
|
if (FBU.subencoding & 0x08) { // AnySubrects
|
|
subrects = RQ[idx];
|
|
idx++;
|
|
var color, xy, sx, sy, wh, sw, sh;
|
|
for (var i = 0; i < subrects; i ++) {
|
|
if (FBU.subencoding & 0x10) { // SubrectsColoured
|
|
color = RQ.slice(idx, idx + RFB.fb_Bpp);
|
|
idx += RFB.fb_Bpp;
|
|
} else {
|
|
color = FBU.foreground;
|
|
}
|
|
xy = RQ[idx];
|
|
idx++;
|
|
sx = x + (xy >> 4);
|
|
sy = y + (xy & 0x0f);
|
|
|
|
wh = RQ[idx];
|
|
idx++;
|
|
sw = (wh >> 4) + 1;
|
|
sh = (wh & 0x0f) + 1;
|
|
|
|
Canvas.fillRect(sx, sy, sw, sh, color);
|
|
}
|
|
}
|
|
}
|
|
RQ.shiftBytes(FBU.bytes);
|
|
FBU.lastsubencoding = FBU.subencoding;
|
|
FBU.bytes = 0;
|
|
FBU.tiles --;
|
|
}
|
|
|
|
if (FBU.tiles == 0) {
|
|
FBU.rects --;
|
|
}
|
|
|
|
//console.log("<< display_hextile, rects:" + FBU.rects, " d:" + RQ.slice(0,40));
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
* Client message routines
|
|
*/
|
|
|
|
pixelFormat: function () {
|
|
console.log(">> setPixelFormat");
|
|
var arr;
|
|
arr = [0]; // msg-type
|
|
arr.push8(0); // padding
|
|
arr.push8(0); // padding
|
|
arr.push8(0); // padding
|
|
|
|
arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel
|
|
arr.push8(24); // depth
|
|
arr.push8(0); // little-endian
|
|
arr.push8(1); // true-color
|
|
|
|
arr.push16(255); // red-max
|
|
arr.push16(255); // green-max
|
|
arr.push16(255); // blue-max
|
|
arr.push8(0); // red-shift
|
|
arr.push8(8); // green-shift
|
|
arr.push8(16); // blue-shift
|
|
|
|
arr.push8(0); // padding
|
|
arr.push8(0); // padding
|
|
arr.push8(0); // padding
|
|
console.log("<< setPixelFormat");
|
|
return arr;
|
|
},
|
|
|
|
fixColourMapEntries: function () {
|
|
},
|
|
|
|
encodings: function () {
|
|
console.log(">> setEncodings");
|
|
var arr;
|
|
arr = [2]; // msg-type
|
|
arr.push8(0); // padding
|
|
|
|
//arr.push16(3); // encoding count
|
|
arr.push16(4); // encoding count
|
|
arr.push32(5); // hextile encoding
|
|
|
|
arr.push32(2); // RRE encoding
|
|
arr.push32(1); // copy-rect encoding
|
|
arr.push32(0); // raw encoding
|
|
console.log("<< setEncodings");
|
|
return arr;
|
|
},
|
|
|
|
fbUpdateRequest: function (incremental, x, y, xw, yw) {
|
|
//console.log(">> fbUpdateRequest");
|
|
if (!x) x = 0;
|
|
if (!y) y = 0;
|
|
if (!xw) xw = RFB.fb_width;
|
|
if (!yw) yw = RFB.fb_height;
|
|
var arr;
|
|
arr = [3]; // msg-type
|
|
arr.push8(incremental);
|
|
arr.push16(x);
|
|
arr.push16(y);
|
|
arr.push16(xw);
|
|
arr.push16(yw);
|
|
//console.log("<< fbUpdateRequest");
|
|
return arr;
|
|
},
|
|
|
|
keyEvent: function (keysym, down) {
|
|
//console.log(">> keyEvent, keysym: " + keysym + ", down: " + down);
|
|
var arr;
|
|
arr = [4]; // msg-type
|
|
arr.push8(down);
|
|
arr.push16(0);
|
|
arr.push32(keysym);
|
|
//console.log("keyEvent array: " + arr);
|
|
//console.log("<< keyEvent");
|
|
return arr;
|
|
},
|
|
|
|
pointerEvent: function (x, y) {
|
|
//console.log(">> pointerEvent, x,y: " + x + "," + y + " , mask: " + Mouse.buttonMask);
|
|
var arr;
|
|
arr = [5]; // msg-type
|
|
arr.push8(Mouse.buttonMask);
|
|
arr.push16(x);
|
|
arr.push16(y);
|
|
//console.log("<< pointerEvent");
|
|
return arr;
|
|
},
|
|
|
|
clientCutText: function (text) {
|
|
console.log(">> clientCutText");
|
|
var arr;
|
|
arr = [6]; // msg-type
|
|
arr.push8(0); // padding
|
|
arr.push8(0); // padding
|
|
arr.push8(0); // padding
|
|
arr.push32(text.length);
|
|
arr.pushStr(text);
|
|
console.log("<< clientCutText");
|
|
return arr;
|
|
},
|
|
|
|
|
|
/*
|
|
* Utility routines
|
|
*/
|
|
|
|
send_string: function (str) {
|
|
//console.log(">> send_string: " + str);
|
|
RFB.send_array(str.split('').map(
|
|
function (chr) { return chr.charCodeAt(0) } ) );
|
|
},
|
|
|
|
send_array: function (arr) {
|
|
//console.log(">> send_array: " + arr);
|
|
//console.log(">> send_array: " + Base64.encode(arr));
|
|
SQ = SQ + Base64.encode(arr);
|
|
if (RFB.ws.bufferedAmount == 0) {
|
|
RFB.ws.send(SQ);
|
|
SQ = ""
|
|
} else {
|
|
console.log("Delaying send");
|
|
}
|
|
},
|
|
|
|
/* Mirror bits of each character and return as array */
|
|
passwdTwiddle: function (passwd) {
|
|
var arr;
|
|
arr = [];
|
|
for (var i=0; i< passwd.length; i++) {
|
|
var c = passwd.charCodeAt(i);
|
|
arr.push( ((c & 0x80) >> 7) +
|
|
((c & 0x40) >> 5) +
|
|
((c & 0x20) >> 3) +
|
|
((c & 0x10) >> 1) +
|
|
((c & 0x08) << 1) +
|
|
((c & 0x04) << 3) +
|
|
((c & 0x02) << 5) +
|
|
((c & 0x01) << 7) );
|
|
}
|
|
return arr;
|
|
},
|
|
|
|
flushClient: function () {
|
|
var arr = [];
|
|
if (Mouse.arr.length > 0) {
|
|
//RFB.send_array(Mouse.arr.concat(RFB.fbUpdateRequest(1)));
|
|
RFB.send_array(Mouse.arr)
|
|
setTimeout(function() {
|
|
RFB.send_array(RFB.fbUpdateRequest(1));
|
|
}, 50);
|
|
|
|
Mouse.arr = [];
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
checkEvents: function () {
|
|
if (RFB.state == 'normal') {
|
|
if (! RFB.flushClient()) {
|
|
var now = new Date().getTime();
|
|
if (now > RFB.last_req + RFB.req_rate) {
|
|
RFB.last_req = now;
|
|
RFB.send_array(RFB.fbUpdateRequest(1));
|
|
}
|
|
}
|
|
}
|
|
RFB.checkEvents.delay(RFB.check_rate);
|
|
},
|
|
|
|
_keyX: function (e, down) {
|
|
if (RFB.clipboardFocus) {
|
|
return true;
|
|
}
|
|
e.stop();
|
|
var arr = RFB.keyEvent(Canvas.getKeysym(e), down);
|
|
arr = arr.concat(RFB.fbUpdateRequest(1));
|
|
RFB.send_array(arr);
|
|
},
|
|
|
|
keyDown: function (e) {
|
|
//console.log(">> keyDown: " + Canvas.getKeysym(e));
|
|
RFB._keyX(e, 1);
|
|
},
|
|
|
|
keyUp: function (e) {
|
|
//console.log(">> keyUp: " + Canvas.getKeysym(e));
|
|
RFB._keyX(e, 0);
|
|
},
|
|
|
|
mouseDown: function(e) {
|
|
var evt = e.event || window.event;
|
|
var x, y;
|
|
x = (evt.clientX - Canvas.c_x);
|
|
y = (evt.clientY - Canvas.c_y);
|
|
//console.log('>> mouseDown ' + evt.which + '/' + evt.button + " " + x + "," + y);
|
|
Mouse.buttonMask |= 1 << evt.button;
|
|
Mouse.arr = Mouse.arr.concat( RFB.pointerEvent(x, y) );
|
|
|
|
RFB.flushClient();
|
|
},
|
|
|
|
mouseUp: function(e) {
|
|
var evt = e.event || window.event;
|
|
var x, y;
|
|
x = (evt.clientX - Canvas.c_x);
|
|
y = (evt.clientY - Canvas.c_y);
|
|
//console.log('>> mouseUp ' + evt.which + '/' + evt.button + " " + x + "," + y);
|
|
Mouse.buttonMask ^= 1 << evt.button;
|
|
Mouse.arr = Mouse.arr.concat( RFB.pointerEvent(x, y) );
|
|
|
|
RFB.flushClient();
|
|
},
|
|
|
|
mouseMove: function(e) {
|
|
var evt = e.event || window.event;
|
|
var x, y;
|
|
x = (evt.clientX - Canvas.c_x);
|
|
y = (evt.clientY - Canvas.c_y);
|
|
//console.log('>> mouseMove ' + x + "," + y);
|
|
Mouse.arr = Mouse.arr.concat( RFB.pointerEvent(x, y) );
|
|
},
|
|
|
|
clipboardCopyTo: function (text) {
|
|
console.log(">> clipboardCopyTo: " + text.substr(0,40) + "...");
|
|
$('clipboard').value = text;
|
|
console.log("<< clipboardCopyTo");
|
|
},
|
|
|
|
clipboardPasteFrom: function () {
|
|
if (RFB.state != "normal") return;
|
|
var text = $('clipboard').value;
|
|
console.log(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
|
|
RFB.send_array(RFB.clientCutText(text));
|
|
console.log("<< clipboardPasteFrom");
|
|
},
|
|
|
|
clipboardClear: function () {
|
|
$('clipboard').value = '';
|
|
RFB.clipboardPasteFrom();
|
|
},
|
|
|
|
|
|
/*
|
|
* Setup routines
|
|
*/
|
|
|
|
init_ws: function () {
|
|
|
|
console.log(">> init_ws");
|
|
var uri = "ws://" + RFB.host + ":" + RFB.port;
|
|
console.log("connecting to " + uri);
|
|
RFB.ws = new WebSocket(uri);
|
|
RFB.ws.onmessage = function(e) {
|
|
//console.log(">> WebSockets.onmessage");
|
|
|
|
//console.log(e.data);
|
|
var offset = e.data.indexOf(":") + 1;
|
|
var seq_num = parseInt(e.data.substr(0, offset-1));
|
|
//console.log("RQ_seq_num:" + RQ_seq_num + ", seq_num:" + seq_num);
|
|
if (RQ_seq_num == seq_num) {
|
|
RQ = RQ.concat(Base64.decode(e.data, offset));
|
|
RQ_seq_num++;
|
|
} else {
|
|
console.warn("sequence number mismatch RQ_seq_num:" + RQ_seq_num + ", seq_num:" + seq_num);
|
|
if (RQ_reorder.length > 20) {
|
|
console.log("Re-order queue too long");
|
|
RFB.state = 'failed';
|
|
} else {
|
|
RQ_reorder = RQ_reorder.concat(e.data.substr(0));
|
|
var i = 0;
|
|
while (i < RQ_reorder.length) {
|
|
var offset = RQ_reorder[i].indexOf(":") + 1;
|
|
var seq_num = parseInt(RQ_reorder[i].substr(0, offset-1));
|
|
console.log("Searching reorder list item " + i + ", seq_num " + seq_num);
|
|
if (seq_num == RQ_seq_num) {
|
|
/* Remove it from reorder queue, decode it and
|
|
* add it to the receive queue */
|
|
console.log("Found re-ordered packet seq_num " + seq_num);
|
|
RQ = RQ.concat(Base64.decode(RQ_reorder.splice(i, 1)[0], offset));
|
|
RQ_seq_num++;
|
|
i = 0; // Start search again for next one
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
switch (RFB.state) {
|
|
case 'closed':
|
|
console.log("onmessage while closed");
|
|
break;
|
|
case 'reset':
|
|
/* close and reset connection */
|
|
RFB.disconnect();
|
|
RFB.init_ws();
|
|
break;
|
|
case 'failed':
|
|
console.log("Giving up!");
|
|
RFB.disconnect();
|
|
break;
|
|
case 'normal':
|
|
RFB.normal_msg();
|
|
/*
|
|
while (RQ.length > 0) {
|
|
if (RFB.normal_msg() && RFB.state == 'normal') {
|
|
console.log("More to process");
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
break;
|
|
default:
|
|
RFB.init_msg();
|
|
break;
|
|
}
|
|
//console.log("<< WebSockets.onmessage");
|
|
};
|
|
RFB.ws.onopen = function(e) {
|
|
console.log(">> WebSockets.onopen");
|
|
RFB.state = "ProtocolVersion";
|
|
RFB.sendID = setInterval(function() {
|
|
/*
|
|
* Send updates either at a rate of one update every 50ms,
|
|
* or whatever slower rate the network can handle
|
|
*/
|
|
if (RFB.ws.bufferedAmount == 0) {
|
|
if (SQ) {
|
|
RFB.ws.send(SQ);
|
|
SQ = "";
|
|
}
|
|
} else {
|
|
console.log("Delaying send");
|
|
}
|
|
}, 50);
|
|
console.log("<< WebSockets.onopen");
|
|
};
|
|
RFB.ws.onclose = function(e) {
|
|
console.log(">> WebSockets.onclose");
|
|
clearInterval(RFB.sendID);
|
|
RFB.state = "closed";
|
|
console.log("<< WebSockets.onclose");
|
|
};
|
|
RFB.ws.onerror = function(e) {
|
|
console.log(">> WebSockets.onerror");
|
|
console.log(" " + e);
|
|
console.log("<< WebSockets.onerror");
|
|
};
|
|
|
|
console.log("<< init_ws");
|
|
},
|
|
|
|
init_vars: function () {
|
|
/* Reset state */
|
|
RFB.cuttext = 'none';
|
|
RFB.ct_length = 0;
|
|
RQ = [];
|
|
RQ_seq_num = 0;
|
|
SQ = "";
|
|
FBU.rects = 0;
|
|
FBU.subrects = 0; // RRE and HEXTILE
|
|
FBU.lines = 0, // RAW
|
|
FBU.tiles = 0, // HEXTILE
|
|
Mouse.buttonmask = 0;
|
|
Mouse.arr = [];
|
|
},
|
|
|
|
|
|
connect: function () {
|
|
console.log(">> connect");
|
|
RFB.host = $('host').value;
|
|
RFB.port = $('port').value;
|
|
RFB.password = $('password').value;
|
|
if ((!RFB.host) || (!RFB.port)) {
|
|
console.log("must set host and port");
|
|
return;
|
|
}
|
|
|
|
RFB.init_vars();
|
|
|
|
if (RFB.ws) {
|
|
RFB.ws.close();
|
|
}
|
|
RFB.init_ws();
|
|
|
|
$('connectButton').value = "Disconnect";
|
|
$('connectButton').onclick = RFB.disconnect;
|
|
console.log("<< connect");
|
|
|
|
},
|
|
|
|
disconnect: function () {
|
|
console.log(">> disconnect");
|
|
if (RFB.ws) {
|
|
RFB.state = "closed";
|
|
RFB.ws.close();
|
|
}
|
|
if (Canvas.ctx) {
|
|
Canvas.stop();
|
|
if (! /__debug__$/i.test(document.location.href)) {
|
|
Canvas.clear();
|
|
}
|
|
}
|
|
$('connectButton').value = "Connect";
|
|
$('connectButton').onclick = RFB.connect;
|
|
$('status').innerHTML = "Disconnected";
|
|
console.log("<< disconnect");
|
|
}
|
|
|
|
}; /* End of RFB */
|