diff options
author | Joel Martin <github@martintribe.org> | 2013-04-30 18:34:20 -0500 |
---|---|---|
committer | Joel Martin <github@martintribe.org> | 2013-04-30 18:34:20 -0500 |
commit | 079275210df2e512d6b00e7ccb43156342d52adb (patch) | |
tree | 9b98e6c9e2c6b29e76f69696fabae4d3b706a7e3 | |
parent | 4b374f60ecda16ea5b0ce3d132f09d04825fbb19 (diff) | |
download | novnc-079275210df2e512d6b00e7ccb43156342d52adb.tar.gz |
Xpra: render and interact with one window.
- payload encoding/decoding including uncompressing deflate compressed
payload data.
- png encoding with decode timing feedback via damage-sequence
messages.
- mouse movement and left mouse button click events.
- keyboard input with codes for alphanumeric and some symbols and
special keys (needs full flesh out).
- remove some VNC specific code.
-rw-r--r-- | include/rfb.js | 1661 |
1 files changed, 433 insertions, 1228 deletions
diff --git a/include/rfb.js b/include/rfb.js index b7be99f..4dfd53e 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -21,16 +21,15 @@ var that = {}, // Public API methods // Pre-declare private functions used before definitions (jslint) init_vars, updateState, fail, handle_message, - init_msg, normal_msg, framebufferUpdate, print_stats, - - pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests, - keyEvent, pointerEvent, clientCutText, + framebufferUpdate, print_stats, getTightCLength, extract_data_uri, keyPress, mouseButton, mouseMove, checkEvents, // Overridable for testing + bencode, bdecode, encode_packet, decode_packet, + // // Private RFB namespace variables @@ -46,25 +45,7 @@ var that = {}, // Public API methods rfb_auth_scheme= '', - // In preference order - encodings = [ - ['COPYRECT', 0x01 ], - ['TIGHT', 0x07 ], - ['TIGHT_PNG', -260 ], - ['HEXTILE', 0x05 ], - ['RRE', 0x02 ], - ['RAW', 0x00 ], - ['DesktopSize', -223 ], - ['Cursor', -239 ], - - // Psuedo-encoding settings - //['JPEG_quality_lo', -32 ], - ['JPEG_quality_med', -26 ], - //['JPEG_quality_hi', -23 ], - //['compress_lo', -255 ], - ['compress_hi', -247 ], - ['last_rect', -224 ] - ], + packetHandlers = {}, encHandlers = {}, encNames = {}, @@ -79,21 +60,22 @@ var that = {}, // Public API methods disconnTimer = null, // disconnection timer msgTimer = null, // queued handle_message timer - // Frame buffer update state - FBU = { - rects : 0, - subrects : 0, // RRE - lines : 0, // RAW - tiles : 0, // HEXTILE - bytes : 0, - x : 0, - y : 0, - width : 0, - height : 0, - encoding : 0, - subencoding : -1, - background : null, - zlibs : [] // TIGHT zlib streams + zlib = null, // zlib encoder/decoder + + windows = {}, // managed windows + + send_packets = [], // pending packets to send (mouse movements) + + raw_packets = {}, // partially received raw packets + + cur_packet_recv_time = 0, // record when we received the packet we are currently handling + + keycodes = {}, + + stats = { + last_ping_echoed_time: 0, + server_ping_latency: [], + client_ping_latency: [] }, fb_Bpp = 4, @@ -148,7 +130,7 @@ Util.conf_defaults(conf, that, defaults, [ ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'], ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], - ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], + ['ping_rate', 'rw', 'int', 10000, 'Timing (ms) of ping sending'], // Callback functions ['onUpdateState', 'rw', 'func', function() { }, @@ -195,7 +177,6 @@ that.get_keyboard = function() { return keyboard; }; that.get_mouse = function() { return mouse; }; - // // Setup routines // @@ -206,12 +187,6 @@ function constructor() { var i, rmode; Util.Debug(">> RFB.constructor"); - // Create lookup tables based encoding number - for (i=0; i < encodings.length; i+=1) { - encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]]; - encNames[encodings[i][1]] = encodings[i][0]; - encStats[encodings[i][1]] = [0, 0]; - } // Initialize display, mouse, keyboard, and websock try { display = new Display({'target': conf.target}); @@ -230,8 +205,10 @@ function constructor() { ws = new Websock(); ws.on('message', handle_message); ws.on('open', function() { + Util.Info("connected") if (rfb_state === "connect") { - updateState('ProtocolVersion', "Starting VNC handshake"); + ws.send(helloPacket()); + updateState('hello', "Starting Xpra handshake"); } else { fail("Got unexpected WebSockets connection"); } @@ -314,45 +291,43 @@ init_vars = function() { /* Reset state */ ws.init(); - FBU.rects = 0; - FBU.subrects = 0; // RRE and HEXTILE - FBU.lines = 0; // RAW - FBU.tiles = 0; // HEXTILE - FBU.zlibs = []; // TIGHT zlib encoders mouse_buttonMask = 0; mouse_arr = []; - // Clear the per connection encoding stats - for (i=0; i < encodings.length; i+=1) { - encStats[encodings[i][1]][0] = 0; - } - - for (i=0; i < 4; i++) { - //FBU.zlibs[i] = new InflateStream(); - FBU.zlibs[i] = new TINF(); - FBU.zlibs[i].init(); + zlib = new TINF(); + zlib.init(); + + var keynames = { + 32:'space', 33:'exclam', 35:'numbersign', 36:'dollar', + 37:'percent', 38:'ampersand', 40:'parenleft', 41:'parenright', + 42:'asterisk', 43:'plus', 45:'minus', 61:'equal', + 94:'asciicircum', 95:'underscore', + 96:'grave', 126:'asciitilde' }; + + // Symbols are wrong + for (i=32; i < 127; i++) { + var keyname = keynames[i] ? keynames[i] : String.fromCharCode(i); + keycodes[i] = [i, keyname, i, 0, 0]; } + // Some whacky ones + keycodes[64] = [34, 'at', 34, 0, 0]; + keycodes[126] = [126, 'asciitilde', 49, 0, 0]; + // Special codes + keycodes[65505] = [65505, 'Shift_L', 65505, 0, 0]; + keycodes[65506] = [65506, 'Shift_R', 65506, 0, 0]; + keycodes[65507] = [65507, 'Control_L', 65507, 0, 0]; + keycodes[65508] = [65508, 'Control_R', 65508, 0, 0]; + keycodes[65513] = [65513, 'Alt_L', 65513, 0, 0]; + keycodes[65514] = [65514, 'Alt_R', 65514, 0, 0]; + keycodes[65288] = [65288, 'BackSpace', 65288, 0, 0]; + keycodes[65289] = [65289, 'Tab', 65289, 0, 0]; + keycodes[65293] = [65293, 'Return', 65293, 0, 0]; }; // Print statistics print_stats = function() { var i, s; Util.Info("Encoding stats for this connection:"); - for (i=0; i < encodings.length; i+=1) { - s = encStats[encodings[i][1]]; - if ((s[0] + s[1]) > 0) { - Util.Info(" " + encodings[i][0] + ": " + - s[0] + " rects"); - } - } - Util.Info("Encoding stats since page load:"); - for (i=0; i < encodings.length; i+=1) { - s = encStats[encodings[i][1]]; - if ((s[0] + s[1]) > 0) { - Util.Info(" " + encodings[i][0] + ": " + - s[1] + " rects"); - } - } }; // @@ -370,14 +345,8 @@ print_stats = function() { * failed - abnormal disconnect * fatal - failed to load page, or fatal error * - * RFB protocol initialization states: - * ProtocolVersion - * Security - * Authentication - * password - waiting for password, not part of RFB - * SecurityResult - * ClientInitialization - not triggered by server message - * ServerInitialization (to normal) + * Xpra protocol initialization states: + * hello */ updateState = function(state, statusMsg) { var func, cmsg, oldstate = rfb_state; @@ -527,54 +496,50 @@ fail = function(msg) { handle_message = function() { //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen()); //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); - if (ws.rQlen() === 0) { - Util.Warn("handle_message called on empty receive queue"); - return; + if (cur_packet_recv_time === 0) { + cur_packet_recv_time = (new Date()).getTime(); } - switch (rfb_state) { - case 'disconnected': - case 'failed': - Util.Error("Got data while disconnected"); - break; - case 'normal': - if (normal_msg() && ws.rQlen() > 0) { - // true means we can continue processing - // Give other events a chance to run - if (msgTimer === null) { - Util.Debug("More data to process, creating timer"); - msgTimer = setTimeout(function () { - msgTimer = null; - handle_message(); - }, 10); - } else { - Util.Debug("More data to process, existing timer"); - } + var packet = decode_packet(ws.get_rQ(), ws.get_rQi()); + if (packet.length > 0) { + // Remove the processed data from the queue + ws.set_rQi(ws.get_rQi() + packet.length); + } + if (packet.data) { + // full packet has been received, so process it + var ptype = packet.data[0].replace(/-/, "_"), + handler = packetHandlers[ptype]; + if (handler) { + handler(packet.data); + } else { + Util.Warn("no handler defined for packet type '" + ptype + "'"); } - break; - default: - init_msg(); - break; + // we got a whole packet so reset the packet received timestamp + cur_packet_recv_time = 0; } -}; - -function genDES(password, challenge) { - var i, passwd = []; - for (i=0; i < password.length; i += 1) { - passwd.push(password.charCodeAt(i)); + if (ws.rQlen() >= 8) { + // if we have at least 8 bytes remaining, then requeue + // ourselves, but use setTimeout to give other events a chance + // to run + if (msgTimer === null) { + Util.Debug("More data to process, creating timer"); + msgTimer = setTimeout(function () { + msgTimer = null; + handle_message(); + }, 1); + } else { + Util.Debug("More data to process, existing timer"); + } } - return (new DES(passwd)).encrypt(challenge); -} +}; + function flushClient() { - if (mouse_arr.length > 0) { - //send(mouse_arr.concat(fbUpdateRequests())); - ws.send(mouse_arr); - setTimeout(function() { - ws.send(fbUpdateRequests()); - }, 50); - - mouse_arr = []; + if (send_packets.length > 0) { + for (var i=0; i < send_packets.length; i++) { + ws.send(send_packets[i]); + } + send_packets = []; return true; } else { return false; @@ -585,25 +550,22 @@ function flushClient() { checkEvents = function() { var now; if (rfb_state === 'normal' && !viewportDragging) { - if (! flushClient()) { - now = new Date().getTime(); - if (now > last_req_time + conf.fbu_req_rate) { - last_req_time = now; - ws.send(fbUpdateRequests()); - } - } + flushClient(); } setTimeout(checkEvents, conf.check_rate); }; -keyPress = function(keysym, down) { - var arr; +keyPress = function(keysym, down, evt) { + var arr, packet; if (conf.view_only) { return; } // View only, skip keyboard events - arr = keyEvent(keysym, down); - arr = arr.concat(fbUpdateRequests()); - ws.send(arr); + packet = keyActionPacket(1, keysym, down, evt) + + if (!packet) { return false; } + + send_packets.push(packet); + flushClient(); }; mouseButton = function(x, y, down, bmask) { @@ -628,13 +590,14 @@ mouseButton = function(x, y, down, bmask) { if (conf.view_only) { return; } // View only, skip mouse events - mouse_arr = mouse_arr.concat( - pointerEvent(display.absX(x), display.absY(y)) ); + var button = 1; // TODO: translate bmask to button # + send_packets.push(pointerActionPacket( + 1, button, down, display.absX(x), display.absY(y))); flushClient(); }; mouseMove = function(x, y) { - //Util.Debug('>> mouseMove ' + x + "," + y); + Util.Debug('>> mouseMove ' + x + "," + y); var deltaX, deltaY; if (viewportDragging) { @@ -652,1128 +615,408 @@ mouseMove = function(x, y) { if (conf.view_only) { return; } // View only, skip mouse events - mouse_arr = mouse_arr.concat( - pointerEvent(display.absX(x), display.absY(y)) ); + send_packets.push(pointerPositionPacket( + 1, display.absX(x), display.absY(y))); }; // -// Server message handlers +// Xpra protocol routines // -// RFB/VNC initialisation message handler -init_msg = function() { - //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); - - var strlen, reason, length, sversion, cversion, repeaterID, - i, types, num_types, challenge, response, bpp, depth, - big_endian, red_max, green_max, blue_max, red_shift, - green_shift, blue_shift, true_color, name_length, is_repeater; - - //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0)); - switch (rfb_state) { - - case 'ProtocolVersion' : - if (ws.rQlen() < 12) { - return fail("Incomplete protocol version"); - } - sversion = ws.rQshiftStr(12).substr(4,7); - Util.Info("Server ProtocolVersion: " + sversion); - is_repeater = 0; - switch (sversion) { - case "000.000": is_repeater = 1; break; // UltraVNC repeater - case "003.003": rfb_version = 3.3; break; - case "003.006": rfb_version = 3.3; break; // UltraVNC - case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop - case "003.007": rfb_version = 3.7; break; - case "003.008": rfb_version = 3.8; break; - case "004.000": rfb_version = 3.8; break; // Intel AMT KVM - case "004.001": rfb_version = 3.8; break; // RealVNC 4.6 - default: - return fail("Invalid server version " + sversion); - } - if (is_repeater) { - repeaterID = conf.repeaterID; - while (repeaterID.length < 250) { - repeaterID += "\0"; - } - ws.send_string(repeaterID); - break; - } - if (rfb_version > rfb_max_version) { - rfb_version = rfb_max_version; - } - - if (! test_mode) { - sendTimer = setInterval(function() { - // Send updates either at a rate of one update - // every 50ms, or whatever slower rate the network - // can handle. - ws.flush(); - }, 50); - } - - cversion = "00" + parseInt(rfb_version,10) + - ".00" + ((rfb_version * 10) % 10); - ws.send_string("RFB " + cversion + "\n"); - updateState('Security', "Sent ProtocolVersion: " + cversion); +function bencode(data) { + switch (typeof(data)) { + case "number": + return "i" + data + "e"; break; - - case 'Security' : - if (rfb_version >= 3.7) { - // Server sends supported list, client decides - num_types = ws.rQshift8(); - if (ws.rQwait("security type", num_types, 1)) { return false; } - if (num_types === 0) { - strlen = ws.rQshift32(); - reason = ws.rQshiftStr(strlen); - return fail("Security failure: " + reason); - } - rfb_auth_scheme = 0; - types = ws.rQshiftBytes(num_types); - Util.Debug("Server security types: " + types); - for (i=0; i < types.length; i+=1) { - if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) { - rfb_auth_scheme = types[i]; - } - } - if (rfb_auth_scheme === 0) { - return fail("Unsupported security types: " + types); + case "string": + return data.length + ":" + data; + break; + case "object": + if (Array.isArray(data)) { + var res = "l"; + for (var i = 0; i < data.length; i++) { + res += bencode(data[i]); } - - ws.send([rfb_auth_scheme]); + return res + "e"; } else { - // Server decides - if (ws.rQwait("security scheme", 4)) { return false; } - rfb_auth_scheme = ws.rQshift32(); + var res = "d", + keys = Object.keys(data).sort(); + for (var i = 0; i < keys.length; i++) { + var k = keys[i], + v = data[k]; + res += bencode(k); + res += bencode(v); + } + return res + "e"; } - updateState('Authentication', - "Authenticating using scheme: " + rfb_auth_scheme); - init_msg(); // Recursive fallthrough (workaround JSLint complaint) break; + default: + throw("Unknown encode_data type: " + typeof(data)); + } +} - // Triggered by fallthough, not by server message - case 'Authentication' : - //Util.Debug("Security auth scheme: " + rfb_auth_scheme); - switch (rfb_auth_scheme) { - case 0: // connection failed - if (ws.rQwait("auth reason", 4)) { return false; } - strlen = ws.rQshift32(); - reason = ws.rQshiftStr(strlen); - return fail("Auth failure: " + reason); - case 1: // no authentication - if (rfb_version >= 3.8) { - updateState('SecurityResult'); - return; - } - // Fall through to ClientInitialisation - break; - case 2: // VNC authentication - if (rfb_password.length === 0) { - // Notify via both callbacks since it is kind of - // a RFB state change and a UI interface issue. - updateState('password', "Password Required"); - conf.onPasswordRequired(that); - return; - } - if (ws.rQwait("auth challenge", 16)) { return false; } - challenge = ws.rQshiftBytes(16); - //Util.Debug("Password: " + rfb_password); - //Util.Debug("Challenge: " + challenge + - // " (" + challenge.length + ")"); - response = genDES(rfb_password, challenge); - //Util.Debug("Response: " + response + - // " (" + response.length + ")"); - - //Util.Debug("Sending DES encrypted auth response"); - ws.send(response); - updateState('SecurityResult'); - return; - default: - fail("Unsupported auth scheme: " + rfb_auth_scheme); - return; +function bdecode(raw, f) { + var ret, res; + f = (typeof(f) === "undefined") ? 0 : f; + switch (raw[f]) { + case 'i': + var end = raw.indexOf('e', f+1); + if (end < 0) { + throw("Error decoding integer value"); } - updateState('ClientInitialisation', "No auth required"); - init_msg(); // Recursive fallthrough (workaround JSLint complaint) + var num = parseInt(raw.substring(f+1, end), 10); + res = num; + f = end; break; - - case 'SecurityResult' : - if (ws.rQwait("VNC auth response ", 4)) { return false; } - switch (ws.rQshift32()) { - case 0: // OK - // Fall through to ClientInitialisation - break; - case 1: // failed - if (rfb_version >= 3.8) { - length = ws.rQshift32(); - if (ws.rQwait("SecurityResult reason", length, 8)) { - return false; - } - reason = ws.rQshiftStr(length); - fail(reason); - } else { - fail("Authentication failed"); - } - return; - case 2: // too-many - return fail("Too many auth attempts"); + case 'l': + res = []; + f += 1; + while (raw[f] !== 'e') { + ret = bdecode(raw, f); + res.push(ret[0]); + f = ret[1]; } - updateState('ClientInitialisation', "Authentication OK"); - init_msg(); // Recursive fallthrough (workaround JSLint complaint) - break; - - // Triggered by fallthough, not by server message - case 'ClientInitialisation' : - ws.send([conf.shared ? 1 : 0]); // ClientInitialisation - updateState('ServerInitialisation', "Authentication OK"); break; - - case 'ServerInitialisation' : - if (ws.rQwait("server initialization", 24)) { return false; } - - /* Screen size */ - fb_width = ws.rQshift16(); - fb_height = ws.rQshift16(); - - /* PIXEL_FORMAT */ - bpp = ws.rQshift8(); - depth = ws.rQshift8(); - big_endian = ws.rQshift8(); - true_color = ws.rQshift8(); - - red_max = ws.rQshift16(); - green_max = ws.rQshift16(); - blue_max = ws.rQshift16(); - red_shift = ws.rQshift8(); - green_shift = ws.rQshift8(); - blue_shift = ws.rQshift8(); - ws.rQshiftStr(3); // padding - - Util.Info("Screen: " + fb_width + "x" + fb_height + - ", bpp: " + bpp + ", depth: " + depth + - ", big_endian: " + big_endian + - ", true_color: " + true_color + - ", red_max: " + red_max + - ", green_max: " + green_max + - ", blue_max: " + blue_max + - ", red_shift: " + red_shift + - ", green_shift: " + green_shift + - ", blue_shift: " + blue_shift); - - if (big_endian !== 0) { - Util.Warn("Server native endian is not little endian"); - } - if (red_shift !== 16) { - Util.Warn("Server native red-shift is not 16"); - } - if (blue_shift !== 0) { - Util.Warn("Server native blue-shift is not 0"); - } - - /* Connection name/title */ - name_length = ws.rQshift32(); - fb_name = ws.rQshiftStr(name_length); - - if (conf.true_color && fb_name === "Intel(r) AMT KVM") - { - Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color"); - conf.true_color = false; - } - - display.set_true_color(conf.true_color); - conf.onFBResize(that, fb_width, fb_height); - display.resize(fb_width, fb_height); - keyboard.grab(); - mouse.grab(); - - if (conf.true_color) { - fb_Bpp = 4; - fb_depth = 3; - } else { - fb_Bpp = 1; - fb_depth = 1; - } - - response = pixelFormat(); - response = response.concat(clientEncodings()); - response = response.concat(fbUpdateRequests()); - timing.fbu_rt_start = (new Date()).getTime(); - timing.pixels = 0; - ws.send(response); - - /* Start pushing/polling */ - setTimeout(checkEvents, conf.check_rate); - - if (conf.encrypt) { - updateState('normal', "Connected (encrypted) to: " + fb_name); - } else { - updateState('normal', "Connected (unencrypted) to: " + fb_name); - } - break; - } - //Util.Debug("<< init_msg"); -}; - - -/* Normal RFB/VNC server message handler */ -normal_msg = function() { - //Util.Debug(">> normal_msg"); - - var ret = true, msg_type, length, text, - c, first_colour, num_colours, red, green, blue; - - if (FBU.rects > 0) { - msg_type = 0; - } else { - msg_type = ws.rQshift8(); - } - switch (msg_type) { - case 0: // FramebufferUpdate - ret = framebufferUpdate(); // false means need more data - break; - case 1: // SetColourMapEntries - Util.Debug("SetColourMapEntries"); - ws.rQshift8(); // Padding - first_colour = ws.rQshift16(); // First colour - num_colours = ws.rQshift16(); - if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; } - - for (c=0; c < num_colours; c+=1) { - red = ws.rQshift16(); - //Util.Debug("red before: " + red); - red = parseInt(red / 256, 10); - //Util.Debug("red after: " + red); - green = parseInt(ws.rQshift16() / 256, 10); - blue = parseInt(ws.rQshift16() / 256, 10); - display.set_colourMap([blue, green, red], first_colour + c); + case 'd': + var k, lastkey = null; + res = {}; + f += 1; + while (raw[f] !== 'e') { + ret = bdecode(raw, f); + k = ret[0]; + f = ret[1]; + if (lastkey !== null && lastkey >= k) { + throw("Unsorted keys found while decoding dict type"); + } + lastkey = k; + ret = bdecode(raw, f); + res[k] = ret[0]; + f = ret[1]; } - Util.Debug("colourMap: " + display.get_colourMap()); - Util.Info("Registered " + num_colours + " colourMap entries"); - //Util.Debug("colourMap: " + display.get_colourMap()); - break; - case 2: // Bell - Util.Debug("Bell"); - conf.onBell(that); - break; - case 3: // ServerCutText - Util.Debug("ServerCutText"); - if (ws.rQwait("ServerCutText header", 7, 1)) { return false; } - ws.rQshiftBytes(3); // Padding - length = ws.rQshift32(); - if (ws.rQwait("ServerCutText", length, 8)) { return false; } - - text = ws.rQshiftStr(length); - conf.clipboardReceive(that, text); // Obsolete - conf.onClipboard(that, text); break; default: - fail("Disconnected: illegal server message type " + msg_type); - Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); - break; - } - //Util.Debug("<< normal_msg"); - return ret; -}; - -framebufferUpdate = function() { - var now, hdr, fbu_rt_diff, ret = true; - - if (FBU.rects === 0) { - //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20)); - if (ws.rQwait("FBU header", 3)) { - ws.rQunshift8(0); // FBU msg_type - return false; + if (!raw[f].match(/^[0-9]/)) { + throw("Unknown decoding type: " + raw[f]); } - ws.rQshift8(); // padding - FBU.rects = ws.rQshift16(); - //Util.Debug("FramebufferUpdate, rects:" + FBU.rects); - FBU.bytes = 0; - timing.cur_fbu = 0; - if (timing.fbu_rt_start > 0) { - now = (new Date()).getTime(); - Util.Info("First FBU latency: " + (now - timing.fbu_rt_start)); + var end = raw.indexOf(':', f+1); + if (end < 0) { + throw("Error decoding string value"); } + var len = parseInt(raw.substring(f, end), 10); + res = raw.substr(end+1, len); + f = end+len; } + return [res, f+1]; +} - while (FBU.rects > 0) { - if (rfb_state !== "normal") { - return false; - } - if (ws.rQwait("FBU", FBU.bytes)) { return false; } - if (FBU.bytes === 0) { - if (ws.rQwait("rect header", 12)) { return false; } - /* New FramebufferUpdate */ - - hdr = ws.rQshiftBytes(12); - FBU.x = (hdr[0] << 8) + hdr[1]; - FBU.y = (hdr[2] << 8) + hdr[3]; - FBU.width = (hdr[4] << 8) + hdr[5]; - FBU.height = (hdr[6] << 8) + hdr[7]; - FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + - (hdr[10] << 8) + hdr[11], 10); - - conf.onFBUReceive(that, - {'x': FBU.x, 'y': FBU.y, - 'width': FBU.width, 'height': FBU.height, - 'encoding': FBU.encoding, - 'encodingName': encNames[FBU.encoding]}); - - if (encNames[FBU.encoding]) { - // Debug: - /* - var msg = "FramebufferUpdate rects:" + FBU.rects; - msg += " x: " + FBU.x + " y: " + FBU.y; - msg += " width: " + FBU.width + " height: " + FBU.height; - msg += " encoding:" + FBU.encoding; - msg += "(" + encNames[FBU.encoding] + ")"; - msg += ", ws.rQlen(): " + ws.rQlen(); - Util.Debug(msg); - */ - } else { - fail("Disconnected: unsupported encoding " + - FBU.encoding); - return false; - } - } - - timing.last_fbu = (new Date()).getTime(); - - ret = encHandlers[FBU.encoding](); - - now = (new Date()).getTime(); - timing.cur_fbu += (now - timing.last_fbu); - - if (ret) { - encStats[FBU.encoding][0] += 1; - encStats[FBU.encoding][1] += 1; - timing.pixels += FBU.width * FBU.height; - } - - if (timing.pixels >= (fb_width * fb_height)) { - if (((FBU.width === fb_width) && - (FBU.height === fb_height)) || - (timing.fbu_rt_start > 0)) { - timing.full_fbu_total += timing.cur_fbu; - timing.full_fbu_cnt += 1; - Util.Info("Timing of full FBU, cur: " + - timing.cur_fbu + ", total: " + - timing.full_fbu_total + ", cnt: " + - timing.full_fbu_cnt + ", avg: " + - (timing.full_fbu_total / - timing.full_fbu_cnt)); - } - if (timing.fbu_rt_start > 0) { - fbu_rt_diff = now - timing.fbu_rt_start; - timing.fbu_rt_total += fbu_rt_diff; - timing.fbu_rt_cnt += 1; - Util.Info("full FBU round-trip, cur: " + - fbu_rt_diff + ", total: " + - timing.fbu_rt_total + ", cnt: " + - timing.fbu_rt_cnt + ", avg: " + - (timing.fbu_rt_total / - timing.fbu_rt_cnt)); - timing.fbu_rt_start = 0; - } - } - if (! ret) { - return ret; // false ret means need more data - } +/* +function encode_packet(data) { + var encoded_data = bencode(data), + payload_size = encoded_data.length, + packet_size = 8 + payload_size + (4-payload_size%4), + packet8 = new Uint8Array(packet_size), + payload8 = new Uint8Array(packet8.buffer, 8), + packet32 = new Uint32Array(packet8.buffer, 0); + + packet8[0] = 80; // 'P'.charCodeAt(0) + packet8[1] = 0; // protocol flags + packet8[2] = 0; // compression level + packet8[3] = 0; // packet index + packet32[1] = payload_size; // packet payload size + + for (var i=0; i < payload_size; i++) { + payload8[i] = encoded_data.charCodeAt(i); } - conf.onFBUComplete(that, - {'x': FBU.x, 'y': FBU.y, - 'width': FBU.width, 'height': FBU.height, - 'encoding': FBU.encoding, - 'encodingName': encNames[FBU.encoding]}); - - return true; // We finished this FBU -}; - -// -// FramebufferUpdate encodings -// - -encHandlers.RAW = function display_raw() { - //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)"); - - var cur_y, cur_height; - - if (FBU.lines === 0) { - FBU.lines = FBU.height; - } - FBU.bytes = FBU.width * fb_Bpp; // At least a line - if (ws.rQwait("RAW", FBU.bytes)) { return false; } - cur_y = FBU.y + (FBU.height - FBU.lines); - cur_height = Math.min(FBU.lines, - Math.floor(ws.rQlen()/(FBU.width * fb_Bpp))); - display.blitImage(FBU.x, cur_y, FBU.width, cur_height, - ws.get_rQ(), ws.get_rQi()); - ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp); - FBU.lines -= cur_height; - - if (FBU.lines > 0) { - FBU.bytes = FBU.width * fb_Bpp; // At least another line - } else { - FBU.rects -= 1; - FBU.bytes = 0; + console.log("packet8: ", packet8, ", payload: ", encoded_data); + return packet8; +} +*/ + +function encode_packet(data) { + //console.log("send packet data:", data); + var encoded_data = bencode(data), + payload_size = encoded_data.length, + packet = []; + + packet.push8(80); // 'P'.charCodeAt(0) + packet.push8(0); // protocol flags + packet.push8(0); // compression level + packet.push8(0); // packet index + packet.push32(payload_size); // packet payload size + + // Convert to array + // TODO: remmove this step + for (var i=0; i < payload_size; i++) { + packet.push8(encoded_data.charCodeAt(i)); } - //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)"); - return true; -}; - -encHandlers.COPYRECT = function display_copy_rect() { - //Util.Debug(">> display_copy_rect"); - var old_x, old_y; + return packet; +} - if (ws.rQwait("COPYRECT", 4)) { return false; } - display.renderQ_push({ - 'type': 'copy', - 'old_x': ws.rQshift16(), - 'old_y': ws.rQshift16(), - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height}); - FBU.rects -= 1; - FBU.bytes = 0; - return true; +var inflate = function(data, offset) { + zlib.reset(); + var inflated = zlib.uncompress(data, offset); + if (inflated.status !== 0) { + throw("Invalid data in zlib stream"); + } + return inflated.data; }; -encHandlers.RRE = function display_rre() { - //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)"); - var color, x, y, width, height, chunk; - - if (FBU.subrects === 0) { - if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; } - FBU.subrects = ws.rQshift32(); - color = ws.rQshiftBytes(fb_Bpp); // Background - display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); +function decode_packet(arr, idx) { + var packet = {}; + idx = (typeof(idx) === "undefined") ? 0 : idx; + if (arr.length - idx < 8) { + return {'length': 0}; } - while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) { - color = ws.rQshiftBytes(fb_Bpp); - x = ws.rQshift16(); - y = ws.rQshift16(); - width = ws.rQshift16(); - height = ws.rQshift16(); - display.fillRect(FBU.x + x, FBU.y + y, width, height, color); - FBU.subrects -= 1; + packet.magic = arr[idx]; + packet.flags = arr[idx+1]; + packet.level = arr[idx+2]; + packet.index = arr[idx+3]; + packet.size = (arr[idx+4] << 24) + + (arr[idx+5] << 16) + + (arr[idx+6] << 8) + + (arr[idx+7]); + packet.length = 8 + packet.size; + packet.data = null; + if (arr.length - idx < packet.length) { + return {'length': 0}; } - //Util.Debug(" display_rre: rects: " + FBU.rects + - // ", FBU.subrects: " + FBU.subrects); - if (FBU.subrects > 0) { - chunk = Math.min(rre_chunk_sz, FBU.subrects); - FBU.bytes = (fb_Bpp + 8) * chunk; + var new_arr, offset = idx + 8; + if (packet.level > 0) { + new_arr = inflate(arr, offset); } else { - FBU.rects -= 1; - FBU.bytes = 0; - } - //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); - return true; -}; - -encHandlers.HEXTILE = function display_hextile() { - //Util.Debug(">> display_hextile"); - var subencoding, subrects, color, cur_tile, - tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh, - rQ = ws.get_rQ(), rQi = ws.get_rQi(); - - 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; + // Convert to string + // TODO: remove this step + new_arr = arr.slice(offset, offset + packet.size); } + var str = String.fromCharCode.apply(null, new_arr); - /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */ - while (FBU.tiles > 0) { - FBU.bytes = 1; - if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; } - subencoding = rQ[rQi]; // Peek - if (subencoding > 30) { // Raw - fail("Disconnected: illegal hextile subencoding " + subencoding); - //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); - return false; - } - 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 - //Util.Debug(" Raw subencoding"); - FBU.bytes += w * h * fb_Bpp; - } else { - if (subencoding & 0x02) { // Background - FBU.bytes += fb_Bpp; - } - if (subencoding & 0x04) { // Foreground - FBU.bytes += fb_Bpp; - } - if (subencoding & 0x08) { // AnySubrects - FBU.bytes += 1; // Since we aren't shifting it off - if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; } - subrects = rQ[rQi + FBU.bytes-1]; // Peek - if (subencoding & 0x10) { // SubrectsColoured - FBU.bytes += subrects * (fb_Bpp + 2); - } else { - FBU.bytes += subrects * 2; - } - } - } - - /* - Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + - " (" + tile_x + "," + tile_y + ")" + - " [" + x + "," + y + "]@" + w + "x" + h + - ", subenc:" + subencoding + - "(last: " + FBU.lastsubencoding + "), subrects:" + - subrects + - ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes + - " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) + - " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10)); - */ - if (ws.rQwait("hextile", FBU.bytes)) { return false; } - - /* We know the encoding and have a whole tile */ - FBU.subencoding = rQ[rQi]; - rQi += 1; - if (FBU.subencoding === 0) { - if (FBU.lastsubencoding & 0x01) { - /* Weird: ignore blanks after RAW */ - Util.Debug(" Ignoring blank after RAW"); - } else { - display.fillRect(x, y, w, h, FBU.background); - } - } else if (FBU.subencoding & 0x01) { // Raw - display.blitImage(x, y, w, h, rQ, rQi); - rQi += FBU.bytes - 1; - } else { - if (FBU.subencoding & 0x02) { // Background - FBU.background = rQ.slice(rQi, rQi + fb_Bpp); - rQi += fb_Bpp; - } - if (FBU.subencoding & 0x04) { // Foreground - FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp); - rQi += fb_Bpp; - } - - display.startTile(x, y, w, h, FBU.background); - if (FBU.subencoding & 0x08) { // AnySubrects - subrects = rQ[rQi]; - rQi += 1; - for (s = 0; s < subrects; s += 1) { - if (FBU.subencoding & 0x10) { // SubrectsColoured - color = rQ.slice(rQi, rQi + fb_Bpp); - rQi += fb_Bpp; - } else { - color = FBU.foreground; - } - xy = rQ[rQi]; - rQi += 1; - sx = (xy >> 4); - sy = (xy & 0x0f); - - wh = rQ[rQi]; - rQi += 1; - sw = (wh >> 4) + 1; - sh = (wh & 0x0f) + 1; - - display.subTile(sx, sy, sw, sh, color); - } - } - display.finishTile(); + if (packet.index > 0) { + raw_packets[packet.index] = str; + // packet.data stays null indicating incomplete packet + } else { + packet.data = bdecode(str, 0)[0]; + // Insert any raw packets into place + for (var idx in raw_packets) { + packet.data[idx] = raw_packets[idx]; } - ws.set_rQi(rQi); - FBU.lastsubencoding = FBU.subencoding; - FBU.bytes = 0; - FBU.tiles -= 1; + raw_packets = {}; } + return packet; - if (FBU.tiles === 0) { - FBU.rects -= 1; - } - - //Util.Debug("<< display_hextile"); - return true; -}; +} +// +// Client packet generation routines +// -// Get 'compact length' header and data size -getTightCLength = function (arr) { - var header = 1, data = 0; - data += arr[0] & 0x7f; - if (arr[0] & 0x80) { - header += 1; - data += (arr[1] & 0x7f) << 7; - if (arr[1] & 0x80) { - header += 1; - data += arr[2] << 14; - } +function helloPacket () { + var flat_keycodes = [], + capabilities; + for(var code in keycodes) { + flat_keycodes.push(keycodes[code]); } - return [header, data]; -}; - -function display_tight(isTightPNG) { - //Util.Debug(">> display_tight"); + capabilities = {'encoding': 'png', + 'encodings': ['png', 'jpeg'], + 'version': '0.9.0', + 'xkbmap_keycodes': flat_keycodes}; + return encode_packet(['hello', capabilities]); +} - if (fb_depth === 1) { - fail("Tight protocol handler only implements true color mode"); +function keyActionPacket(wid, keysym, down, evt) { + var keydata = keycodes[keysym], + keyname = "", + modifiers = [], + keyval = 0, + str = "", // not used + keycode = 0, + group = 0, // not used + is_modifier = 0; // not used + if (!keydata) { + Util.Warn("ignoring undefined keysym " + keysym + " full event: " + evt); + return false; } - - var ctl, cmode, clength, color, img, data; - var filterId = -1, resetStreams = 0, streamId = -1; - var rQ = ws.get_rQ(), rQi = ws.get_rQi(); - - FBU.bytes = 1; // compression-control byte - if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; } - - var checksum = function(data) { - var sum=0, i; - for (i=0; i<data.length;i++) { - sum += data[i]; - if (sum > 65536) sum -= 65536; - } - return sum; + keyname = keydata[1]; + keyval = keydata[0]; + keycode = keydata[2]; + if (evt.shiftKey) { + modifiers.push('shift'); } - - var decompress = function(data) { - for (var i=0; i<4; i++) { - if ((resetStreams >> i) & 1) { - FBU.zlibs[i].reset(); - Util.Info("Reset zlib stream " + i); - } - } - var uncompressed = FBU.zlibs[streamId].uncompress(data, 0); - if (uncompressed.status !== 0) { - Util.Error("Invalid data in zlib stream"); - } - //Util.Warn("Decompressed " + data.length + " to " + - // uncompressed.data.length + " checksums " + - // checksum(data) + ":" + checksum(uncompressed.data)); - - return uncompressed.data; + if (evt.ctrlKey) { + modifiers.push('control'); } - - var indexedToRGB = function (data, numColors, palette, width, height) { - // Convert indexed (palette based) image data to RGB - // TODO: reduce number of calculations inside loop - var dest = []; - var x, y, b, w, w1, dp, sp; - if (numColors === 2) { - w = Math.floor((width + 7) / 8); - w1 = Math.floor(width / 8); - for (y = 0; y < height; y++) { - for (x = 0; x < w1; x++) { - for (b = 7; b >= 0; b--) { - dp = (y*width + x*8 + 7-b) * 3; - sp = (data[y*w + x] >> b & 1) * 3; - dest[dp ] = palette[sp ]; - dest[dp+1] = palette[sp+1]; - dest[dp+2] = palette[sp+2]; - } - } - for (b = 7; b >= 8 - width % 8; b--) { - dp = (y*width + x*8 + 7-b) * 3; - sp = (data[y*w + x] >> b & 1) * 3; - dest[dp ] = palette[sp ]; - dest[dp+1] = palette[sp+1]; - dest[dp+2] = palette[sp+2]; - } - } - } else { - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - dp = (y*width + x) * 3; - sp = data[y*width + x] * 3; - dest[dp ] = palette[sp ]; - dest[dp+1] = palette[sp+1]; - dest[dp+2] = palette[sp+2]; - } - } - } - return dest; - }; - var handlePalette = function() { - var numColors = rQ[rQi + 2] + 1; - var paletteSize = numColors * fb_depth; - FBU.bytes += paletteSize; - if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; } - - var bpp = (numColors <= 2) ? 1 : 8; - var rowSize = Math.floor((FBU.width * bpp + 7) / 8); - var raw = false; - if (rowSize * FBU.height < 12) { - raw = true; - clength = [0, rowSize * FBU.height]; - } else { - clength = getTightCLength(ws.rQslice(3 + paletteSize, - 3 + paletteSize + 3)); - } - FBU.bytes += clength[0] + clength[1]; - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - - // Shift ctl, filter id, num colors, palette entries, and clength off - ws.rQshiftBytes(3); - var palette = ws.rQshiftBytes(paletteSize); - ws.rQshiftBytes(clength[0]); - - if (raw) { - data = ws.rQshiftBytes(clength[1]); - } else { - data = decompress(ws.rQshiftBytes(clength[1])); - } - - // Convert indexed (palette based) image data to RGB - var rgb = indexedToRGB(data, numColors, palette, FBU.width, FBU.height); - - // Add it to the render queue - display.renderQ_push({ - 'type': 'blitRgb', - 'data': rgb, - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height}); - return true; + if (evt.altKey) { + modifiers.push('alt'); } + Util.Info("send key-action for window " + wid + ", keyname: " + keyname + ", keyval: " + keyval + ", modifiers: " + modifiers); + return encode_packet(['key-action', wid, keyname, down, + modifiers, keyval, str, keycode, group, is_modifier]); +} - var handleCopy = function() { - var raw = false; - var uncompressedSize = FBU.width * FBU.height * fb_depth; - if (uncompressedSize < 12) { - raw = true; - clength = [0, uncompressedSize]; - } else { - clength = getTightCLength(ws.rQslice(1, 4)); - } - FBU.bytes = 1 + clength[0] + clength[1]; - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - - // Shift ctl, clength off - ws.rQshiftBytes(1 + clength[0]); - - if (raw) { - data = ws.rQshiftBytes(clength[1]); - } else { - data = decompress(ws.rQshiftBytes(clength[1])); - } - - display.renderQ_push({ - 'type': 'blitRgb', - 'data': data, - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height}); - return true; - } - - ctl = ws.rQpeek8(); - - // Keep tight reset bits - resetStreams = ctl & 0xF; - - // Figure out filter - ctl = ctl >> 4; - streamId = ctl & 0x3; +function pointerPositionPacket(wid, x, y) { + var modifiers = [], + buttons = [], + rx = windows[wid].x + x, + ry = windows[wid].y + y; + return encode_packet(['pointer-position', wid, + [rx, ry], modifiers, buttons]); +} - if (ctl === 0x08) cmode = "fill"; - else if (ctl === 0x09) cmode = "jpeg"; - else if (ctl === 0x0A) cmode = "png"; - else if (ctl & 0x04) cmode = "filter"; - else if (ctl < 0x04) cmode = "copy"; - else return fail("Illegal tight compression received, ctl: " + ctl); +function pointerActionPacket(wid, button, down, x, y) { + var modifiers = [], + buttons = [], + rx = windows[wid].x + x, + ry = windows[wid].y + y; + return encode_packet(['button-action', wid, button, down, + [rx, ry], modifiers, buttons]); +} - if (isTightPNG && (cmode === "filter" || cmode === "copy")) { - return fail("filter/copy received in tightPNG mode"); - } +function pingPacket() { + var now_ms = (new Date()).getTime(); + Util.Info("send ping now_ms: " + now_ms); + return encode_packet(['ping', now_ms]); +} - switch (cmode) { - // fill uses fb_depth because TPIXELs drop the padding byte - case "fill": FBU.bytes += fb_depth; break; // TPIXEL - case "jpeg": FBU.bytes += 3; break; // max clength - case "png": FBU.bytes += 3; break; // max clength - case "filter": FBU.bytes += 2; break; // filter id + num colors if palette - case "copy": break; - } +// +// +// - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - - //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); - //Util.Debug(" cmode: " + cmode); - - // Determine FBU.bytes - switch (cmode) { - case "fill": - ws.rQshift8(); // shift off ctl - color = ws.rQshiftBytes(fb_depth); - display.renderQ_push({ - 'type': 'fill', - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height, - 'color': [color[2], color[1], color[0]] }); - break; - case "png": - case "jpeg": - clength = getTightCLength(ws.rQslice(1, 4)); - FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - - // We have everything, render it - //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + - // clength[0] + ", clength[1]: " + clength[1]); - ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length - img = new Image(); - img.src = "data:image/" + cmode + - extract_data_uri(ws.rQshiftBytes(clength[1])); - display.renderQ_push({ - 'type': 'img', - 'img': img, - 'x': FBU.x, - 'y': FBU.y}); - img = null; - break; - case "filter": - filterId = rQ[rQi + 1]; - if (filterId === 1) { - if (!handlePalette()) { return false; } - } else { - // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter - // Filter 2, Gradient is valid but not used if jpeg is enabled - throw("Unsupported tight subencoding received, filter: " + filterId); - } - break; - case "copy": - if (!handleCopy()) { return false; } - break; +function send_ping () { + ws.send(pingPacket()); + if (rfb_state === 'normal') { + setTimeout(send_ping, conf.ping_rate); } - - FBU.bytes = 0; - FBU.rects -= 1; - //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); - //Util.Debug("<< display_tight_png"); - return true; } -extract_data_uri = function(arr) { - //var i, stra = []; - //for (i=0; i< arr.length; i += 1) { - // stra.push(String.fromCharCode(arr[i])); - //} - //return "," + escape(stra.join('')); - return ";base64," + Base64.encode(arr); -}; - -encHandlers.TIGHT = function () { return display_tight(false); }; -encHandlers.TIGHT_PNG = function () { return display_tight(true); }; - -encHandlers.last_rect = function last_rect() { - //Util.Debug(">> last_rect"); - FBU.rects = 0; - //Util.Debug("<< last_rect"); - return true; -}; +// +// Server packet receive handlers +// -encHandlers.DesktopSize = function set_desktopsize() { - Util.Debug(">> set_desktopsize"); - fb_width = FBU.width; - fb_height = FBU.height; - conf.onFBResize(that, fb_width, fb_height); - display.resize(fb_width, fb_height); - timing.fbu_rt_start = (new Date()).getTime(); - // Send a new non-incremental request - ws.send(fbUpdateRequests()); +packetHandlers.hello = function process_hello (data) { + Util.Info("got hello: " + data); + ws.send(encode_packet(["set_deflate", 0])); + updateState('normal', "Connected"); - FBU.bytes = 0; - FBU.rects -= 1; + /* Start pushing/polling */ + setTimeout(checkEvents, conf.check_rate); - Util.Debug("<< set_desktopsize"); - return true; + /* Start sending pings to the server */ + send_ping(); }; -encHandlers.Cursor = function set_cursor() { - var x, y, w, h, pixelslength, masklength; - Util.Debug(">> set_cursor"); - x = FBU.x; // hotspot-x - y = FBU.y; // hotspot-y - w = FBU.width; - h = FBU.height; - - pixelslength = w * h * fb_Bpp; - masklength = Math.floor((w + 7) / 8) * h; - - FBU.bytes = pixelslength + masklength; - if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; } - - //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - - display.changeCursor(ws.rQshiftBytes(pixelslength), - ws.rQshiftBytes(masklength), - x, y, w, h); - - FBU.bytes = 0; - FBU.rects -= 1; - - Util.Debug("<< set_cursor"); - return true; -}; +packetHandlers.new_window = function process_new_window (data) { + Util.Info("got new-window: " + data); + var wid = data[1], + x = data[2], + y = data[3], + w = data[4], + h = data[5], + props = data[6], + loc = data[7]; + if (wid !== 1) { + // TODO: don't ignore other windows + Util.Warn("ignoring new-window for window ID " + wid); + return; + } -encHandlers.JPEG_quality_lo = function set_jpeg_quality() { - Util.Error("Server sent jpeg_quality pseudo-encoding"); -}; + if (w !== fb_width || h !== fb_height) { + fb_width = w; + fb_height = h; + conf.onFBResize(that, fb_width, fb_height); + display.resize(fb_width, fb_height); + timing.fbu_rt_start = (new Date()).getTime(); + } -encHandlers.compress_lo = function set_compress_level() { - Util.Error("Server sent compress level pseudo-encoding"); -}; + windows[wid] = {x: x, y: y, w: w, h: h, props: props, loc: loc}; + //ws.send(encode_packet(["configure-window", wid, x, y, w, h, loc])); + ws.send(encode_packet(["map-window", wid, x, y, w, h, loc])); + ws.send(encode_packet(["focus", wid])); -/* - * Client message routines - */ + display.resize(fb_width, fb_height); + keyboard.grab(); + mouse.grab(); -pixelFormat = function() { - //Util.Debug(">> pixelFormat"); - var arr; - arr = [0]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - - arr.push8(fb_Bpp * 8); // bits-per-pixel - arr.push8(fb_depth * 8); // depth - arr.push8(0); // little-endian - arr.push8(conf.true_color ? 1 : 0); // true-color - - arr.push16(255); // red-max - arr.push16(255); // green-max - arr.push16(255); // blue-max - arr.push8(16); // red-shift - arr.push8(8); // green-shift - arr.push8(0); // blue-shift - - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - //Util.Debug("<< pixelFormat"); - return arr; }; -clientEncodings = function() { - //Util.Debug(">> clientEncodings"); - var arr, i, encList = []; - - for (i=0; i<encodings.length; i += 1) { - if ((encodings[i][0] === "Cursor") && - (! conf.local_cursor)) { - Util.Debug("Skipping Cursor pseudo-encoding"); - - // TODO: remove this when we have tight+non-true-color - } else if ((encodings[i][0] === "TIGHT") && - (! conf.true_color)) { - Util.Warn("Skipping tight, only support with true color"); - } else { - //Util.Debug("Adding encoding: " + encodings[i][0]); - encList.push(encodings[i][1]); - } +packetHandlers.ping_echo = function process_ping_echo (data) { + Util.Info("got ping_echo: " + data); + var echoedtime = data[1], + l1 = data[2], + l2 = data[3], + l3 = data[4], + cl = data[5], + now_ms = (new Date()).getTime(), + sl = -1; + stats.last_ping_echoed_time = echoedtime; + sl = now_ms - echoedtime; + stats.server_ping_latency.push([now_ms, sl]); + // Keep 100 entries + if (stats.server_ping_latency.length > 100) { + stats.server_ping_latency.splice(0, stats.server_ping_latency.length-100); } - arr = [2]; // msg-type - arr.push8(0); // padding - - arr.push16(encList.length); // encoding count - for (i=0; i < encList.length; i += 1) { - arr.push32(encList[i]); + if (cl >= 0) { + stats.client_ping_latency.push([now_ms, cl]); + // Keep 100 entries + if (stats.client_ping_latency.length > 100) { + stats.client_ping_latency.splice(0, stats.client_ping_latency.length-100); + } } - //Util.Debug("<< clientEncodings: " + arr); - return arr; }; -fbUpdateRequest = function(incremental, x, y, xw, yw) { - //Util.Debug(">> fbUpdateRequest"); - if (typeof(x) === "undefined") { x = 0; } - if (typeof(y) === "undefined") { y = 0; } - if (typeof(xw) === "undefined") { xw = fb_width; } - if (typeof(yw) === "undefined") { yw = fb_height; } - var arr; - arr = [3]; // msg-type - arr.push8(incremental); - arr.push16(x); - arr.push16(y); - arr.push16(xw); - arr.push16(yw); - //Util.Debug("<< fbUpdateRequest"); - return arr; -}; - -// Based on clean/dirty areas, generate requests to send -fbUpdateRequests = function() { - var cleanDirty = display.getCleanDirtyReset(), - arr = [], i, cb, db; - - cb = cleanDirty.cleanBox; - if (cb.w > 0 && cb.h > 0) { - // Request incremental for clean box - arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h)); - } - for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) { - db = cleanDirty.dirtyBoxes[i]; - // Force all (non-incremental for dirty box - arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h)); +packetHandlers.ping = function process_ping (data) { + Util.Info("got ping: " + data); + var echotime = data[1], + l1 = 500, l2 = 500, l3 = 500, // fake load-averages + sl = -1, + sl_len = stats.server_ping_latency.length; + if (sl_len > 0) { + sl = stats.server_ping_latency[sl_len-1][1]; } - return arr; -}; - - - -keyEvent = function(keysym, down) { - //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); - var arr; - arr = [4]; // msg-type - arr.push8(down); - arr.push16(0); - arr.push32(keysym); - //Util.Debug("<< keyEvent"); - return arr; + Util.Info("send ping_echo sl: " + sl); + ws.send(encode_packet(["ping_echo", echotime, + l1, l2, l3, sl])); }; -pointerEvent = function(x, y) { - //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + - // " , mask: " + mouse_buttonMask); - var arr; - arr = [5]; // msg-type - arr.push8(mouse_buttonMask); - arr.push16(x); - arr.push16(y); - //Util.Debug("<< pointerEvent"); - return arr; -}; - -clientCutText = function(text) { - //Util.Debug(">> clientCutText"); - var arr, i, n; - arr = [6]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push32(text.length); - n = text.length; - for (i=0; i < n; i+=1) { - arr.push(text.charCodeAt(i)); +packetHandlers.draw = function process_draw (data) { + Util.Info("got draw #" + data[8] + " for window " + data[1] + ": " + data.slice(2,7)); + var wid = data[1], + x = data[2], + y = data[3], + w = data[4], + h = data[5], + coding = data[6], + raw = data[7], + damage_seq = data[8], + rowstride = data[9], + client_opts = data[10], + decode_time, + img; + if (wid !== 1) { + // TODO: handle other windows + Util.Warn("ignoring draw for window ID " + wid); + return; } - //Util.Debug("<< clientCutText:" + arr); - return arr; + img = new Image(); + img.src = "data:image/" + coding + ";base64," + window.btoa(raw); + display.renderQ_push({ + 'type': 'img', + 'img': img, + 'x': x, + 'y': y}); + img = null; + + // based on _do_draw, draw_region, do_draw_region, paint_png, etc + decode_time = ((new Date()).getTime() - cur_packet_recv_time)*1000; + Util.Info("send damage-sequence #" + damage_seq + " for window " + wid + ", w: " + w + ", h: " + h + ", decode_time: " + decode_time); + ws.send(encode_packet(['damage-sequence', damage_seq, + wid, w, h, decode_time])); }; - // // Public API interface functions // @@ -1801,55 +1044,17 @@ that.disconnect = function() { //Util.Debug("<< disconnect"); }; -that.sendPassword = function(passwd) { - rfb_password = passwd; - rfb_state = "Authentication"; - setTimeout(init_msg, 1); -}; - -that.sendCtrlAltDel = function() { - if (rfb_state !== "normal" || conf.view_only) { return false; } - Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(keyEvent(0xFFE3, 1)); // Control - arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt - arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete - arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete - arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt - arr = arr.concat(keyEvent(0xFFE3, 0)); // Control - arr = arr.concat(fbUpdateRequests()); - ws.send(arr); -}; - -// Send a key press. If 'down' is not specified then send a down key -// followed by an up key. -that.sendKey = function(code, down) { - if (rfb_state !== "normal" || conf.view_only) { return false; } - var arr = []; - if (typeof down !== 'undefined') { - Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); - arr = arr.concat(keyEvent(code, down ? 1 : 0)); - } else { - Util.Info("Sending key code (down + up): " + code); - arr = arr.concat(keyEvent(code, 1)); - arr = arr.concat(keyEvent(code, 0)); - } - arr = arr.concat(fbUpdateRequests()); - ws.send(arr); -}; - -that.clipboardPasteFrom = function(text) { - if (rfb_state !== "normal") { return; } - //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); - ws.send(clientCutText(text)); - //Util.Debug("<< clipboardPasteFrom"); -}; - // Override internal functions for testing that.testMode = function(override_send, data_mode) { test_mode = true; that.recv_message = ws.testMode(override_send, data_mode); + // Allow debug calls to this + that.encode_packet = encode_packet; + that.decode_packet = decode_packet; + that.bdecode = bdecode; + that.bencode = bencode; + checkEvents = function () { /* Stub Out */ }; that.connect = function(host, port, password) { rfb_host = host; |