Files
noVNC/vnc.js
Joel Martin 0f62806499 Refactor processing to allow hextile processing.
With hextile you can't know how many bytes are pending. So restructure
so that all the received data so far is passed to the processsing
routines.
2010-04-12 13:21:44 -05:00

552 lines
15 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.shiftBytes = function (len) {
return this.splice(0, len);
}
/*
* Pending frame buffer update data
*/
var FBU = {
rects : 0,
subrects : 0,
tiles : 0,
bytes : 0,
x : 0,
y : 0,
width : 0,
height : 0,
encoding : 0,
arr : null};
/*
* RFB namespace
*/
RFB = {
ws : null, // Web Socket object
version : "RFB 003.003\n",
state : 'ProtocolVersion',
shared : 1,
poll_rate : 3000,
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 (data) {
debug(">> init_msg: " + RFB.state);
switch (RFB.state) {
case 'ProtocolVersion' :
if (data.length != 12) {
debug("Invalid protocol version from server");
RFB.state = 'reset';
return;
}
debug("Server ProtocolVersion: " + data.shiftStr(11));
debug("Sending ProtocolVersion: " + RFB.version.substr(0,11));
RFB.send_string(RFB.version);
RFB.state = 'Authentication';
break;
case 'Authentication' :
if (data.length < 4) {
debug("Invalid auth frame");
RFB.state = 'reset';
return;
}
var scheme = data.shift32();
debug("Auth scheme: " + scheme);
switch (scheme) {
case 0: // connection failed
var strlen = data.shift32();
var reason = data.shiftStr(strlen);
debug("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 = data.shiftBytes(16);
debug("Password: " + RFB.password);
debug("Challenge: " + challenge + "(" + challenge.length + ")");
passwd = RFB.passwdTwiddle(RFB.password);
//debug("passwd: " + passwd + "(" + passwd.length + ")");
response = des(passwd, challenge, 1);
//debug("reponse: " + response + "(" + response.length + ")");
RFB.send_array(response);
RFB.state = "SecurityResult";
break;
}
break;
case 'SecurityResult' :
if (data.length != 4) {
debug("Invalid server auth response");
RFB.state = 'reset';
return;
}
var resp = data.shift32();
switch (resp) {
case 0: // OK
debug("Authentication OK");
break;
case 1: // failed
debug("Authentication failed");
RFB.state = "reset";
return;
case 2: // too-many
debug("Too many authentication attempts");
RFB.state = "failed";
return;
}
RFB.send_array([RFB.shared]); // ClientInitialisation
RFB.state = "ServerInitialisation";
break;
case 'ServerInitialisation' :
if (data.length < 24) {
debug("Invalid server initialisation");
RFB.state = 'reset';
return;
}
/* Screen size */
//debug("data: " + data);
RFB.fb_width = data.shift16();
RFB.fb_height = data.shift16();
debug("Screen size: " + RFB.fb_width + "x" + RFB.fb_height);
/* PIXEL_FORMAT */
var bpp = data.shift8();
var depth = data.shift8();
var big_endian = data.shift8();
var true_color = data.shift8();
debug("bpp: " + bpp);
debug("depth: " + depth);
debug("big_endian: " + big_endian);
debug("true_color: " + true_color);
/* Connection name/title */
data.shiftStr(12);
var name_length = data.shift32();
RFB.fb_name = data.shiftStr(name_length);
debug("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.setEncodings();
RFB.setPixelFormat();
RFB.fbUpdateRequest(0, 0, 0, RFB.fb_width, RFB.fb_height);
RFB.state = 'normal';
break;
}
debug("<< init_msg (" + RFB.state + ")");
},
/* Framebuffer update display functions */
display_raw: function () {
debug(">> display_raw");
Canvas.rfbImage(FBU.x, FBU.y, FBU.width, FBU.height, FBU.arr);
FBU.rects --;
FBU.arr.splice(0, FBU.width * FBU.height * RFB.fb_Bpp);
},
display_copy_rect: function () {
debug(">> display_copy_rect");
var old_x = FBU.arr.shift16();
var old_y = FBU.arr.shift16();
Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
FBU.rects --;
},
display_rre: function () {
//debug(">> display_rre (" + FBU.arr.length + " bytes)");
if (FBU.subrects == 0) {
FBU.subrects = FBU.arr.shift32();
debug(">> display_rre " + "(" + FBU.subrects + " subrects)");
var color = FBU.arr.shiftBytes(RFB.fb_Bpp); // Background
Canvas.rfbRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
}
while ((FBU.subrects > 0) && (FBU.arr.length >= (RFB.fb_Bpp + 8))) {
FBU.subrects --;
var color = FBU.arr.shiftBytes(RFB.fb_Bpp);
var x = FBU.arr.shift16();
var y = FBU.arr.shift16();
var width = FBU.arr.shift16();
var height = FBU.arr.shift16();
Canvas.rfbRect(FBU.x + x, FBU.y + y, width, height, color);
}
//debug(" 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 --;
}
//debug("<< display_rre, FBU.bytes: " + FBU.bytes);
},
display_hextile: function() {
if (FBU.tiles == 0) {
FBU.tiles = (Math.ceiling(FBU.width/16.0)) * (Math.ceiling(FBU.height/16.0));
}
while (FBU.arr.length) {};
},
/* Normal RFB/VNC messages */
normal_msg: function (data) {
//debug(">> normal_msg");
if ((FBU.rects > 0) || (FBU.bytes > 0)) {
var msg_type = 0;
} else {
var msg_type = data.shift8();
}
switch (msg_type) {
case 0: // FramebufferUpdate
if (FBU.rects == 0) {
data.shift8();
FBU.rects = data.shift16();
debug("FramebufferUpdate, " + FBU.rects + " rects");
FBU.bytes = 0;
FBU.arr = [];
} else {
//debug("FramebufferUpdate continuation");
}
if (data.length > 0 ) {
FBU.arr = FBU.arr.concat(data);
}
while (FBU.arr.length > 0) {
if (FBU.bytes == 0) {
FBU.x = FBU.arr.shift16();
FBU.y = FBU.arr.shift16();
FBU.width = FBU.arr.shift16();
FBU.height = FBU.arr.shift16();
FBU.encoding = parseInt(FBU.arr.shift32(), 10);
debug("encoding: " + FBU.encoding);
switch (FBU.encoding) {
case 0: // Raw
FBU.bytes = FBU.width * FBU.height * RFB.fb_Bpp;
break;
case 1: // Copy-Rect
FBU.bytes = 4;
break;
case 2: // RRE
FBU.bytes = 4 + RFB.fb_Bpp;
break;
case 5: // hextile
FBU.bytes = 1; // No header; get it started
break;
}
}
//debug("FBU.arr.length: " + FBU.arr.length + ", FBU.bytes: " + FBU.bytes);
if (FBU.arr.length >= FBU.bytes) {
//debug('Done rect:');
FBU.bytes = 0;
switch (FBU.encoding) {
case 0: RFB.display_raw(); break; // Raw
case 1: RFB.display_copy_rect(); break; // Copy-Rect
case 2: RFB.display_rre(); break; // RRE
case 5: RFB.display_hextile(); break; // hextile
}
} else {
/* We don't have enough yet */
FBU.bytes = FBU.bytes - data.length;
break;
}
}
//debug("Finished frame buffer update");
break;
case 1: // SetColourMapEntries
debug("SetColourMapEntries");
break;
case 2: // Bell
debug("Bell");
break;
case 3: // ServerCutText
debug("ServerCutText");
break;
default:
debug("Unknown server message type: " + msg_type);
break;
}
//debug("<< normal_msg");
},
/*
* Client message routines
*/
setPixelFormat: function () {
debug(">> setPixelFormat");
var 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
RFB.send_array(arr);
debug("<< setPixelFormat");
},
fixColourMapEntries: function () {
},
setEncodings: function () {
debug(">> setEncodings");
var 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
RFB.send_array(arr);
debug("<< setEncodings");
},
fbUpdateRequest: function (incremental, x, y, xw, yw) {
//debug(">> fbUpdateRequest");
var arr = [3]; // msg-type
arr.push8(incremental);
arr.push16(x);
arr.push16(y);
arr.push16(xw);
arr.push16(yw);
RFB.send_array(arr);
//debug("<< fbUpdateRequest");
},
keyEvent: function (keysym, down) {
debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
var arr = [4]; // msg-type
arr.push8(down);
arr.push16(0);
arr.push32(keysym);
//debug("keyEvent array: " + arr);
RFB.send_array(arr);
RFB.fbUpdateRequest(1, 0, 0, RFB.fb_width, RFB.fb_height);
//debug("<< keyEvent");
},
pointerEvent: function () {
},
clientCutText: function () {
},
/*
* Utility routines
*/
send_string: function (str) {
//debug(">> send_string: " + str);
var arr = str.split('').map(function (chr) {
return chr.charCodeAt(0) } );
RFB.send_array(arr);
},
send_array: function (arr) {
//debug(">> send_array: " + Base64.encode_array(arr));
RFB.ws.send(Base64.encode_array(arr));
},
/* Mirror bits of each character and return as array */
passwdTwiddle: function (passwd) {
var 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;
},
poller: function () {
if (RFB.state == 'normal') {
RFB.fbUpdateRequest(1, 0, 0, RFB.fb_width, RFB.fb_height);
RFB.poller.delay(RFB.poll_rate);
}
},
keyDown: function (e) {
//debug(">> keyDown: " + e.key + "(" + e.code + ")");
e.stop();
RFB.keyEvent(Canvas.getKeysym(e), 1);
},
keyUp: function (e) {
//debug(">> keyUp: " + e.key + "(" + e.code + ")");
e.stop();
RFB.keyEvent(Canvas.getKeysym(e), 0);
},
/*
* Setup routines
*/
init_ws: function () {
debug(">> init_ws");
var uri = "ws://" + RFB.host + ":" + RFB.port;
debug("connecting to " + uri);
RFB.ws = new WebSocket(uri);
RFB.ws.onmessage = function(e) {
//debug(">> onmessage");
var data = Base64.decode_array(e.data);
//debug("decoded array: " + data);
if (RFB.state != 'normal') {
RFB.init_msg(data);
} else {
RFB.normal_msg(data);
}
if (RFB.state == 'reset') {
/* close and reset connection */
RFB.disconnect();
RFB.init_ws();
} else if (RFB.state == 'failed') {
debug("Giving up!");
RFB.disconnect();
}
//debug("<< onmessage");
};
RFB.ws.onopen = function(e) {
debug(">> onopen");
RFB.state = "ProtocolVersion";
debug("<< onopen");
};
RFB.ws.onclose = function(e) {
debug(">> onclose");
RFB.state = "closed";
debug("<< onclose");
}
RFB.poller.delay(RFB.poll_rate);
debug("<< init_ws");
},
connect: function () {
debug(">> connect");
RFB.host = $('host').value;
RFB.port = $('port').value;
RFB.password = $('password').value;
if ((!host) || (!port)) {
debug("must set host and port");
return;
}
if (RFB.ws) {
RFB.ws.close();
}
RFB.init_ws();
$('connectButton').value = "Disconnect";
$('connectButton').onclick = RFB.disconnect;
debug("<< connect");
},
disconnect: function () {
debug(">> disconnect");
if (RFB.ws) {
RFB.ws.close();
}
if (Canvas.ctx) {
Canvas.clear();
}
$('connectButton').value = "Connect";
$('connectButton').onclick = RFB.connect;
$('status').innerHTML = "Disconnected";
debug("<< disconnect");
}
}; /* End of RFB */