X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=include%2Frfb.js;h=766f6eb9d5aeee5bac69705b2a27b28178490aaa;hb=8db09746b7b96da6bf48d0d7ef22f40df7205f7d;hp=ab0aacf5c2b1fd1b2d703c2806045b777bf5640c;hpb=a7a8962676734041b85a96a0eaa70ac4dc35f545;p=mirror_novnc.git diff --git a/include/rfb.js b/include/rfb.js index ab0aacf..766f6eb 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -7,509 +7,890 @@ */ "use strict"; -/*jslint white: false, nomen: false, browser: true, bitwise: false */ +/*jslint white: false, browser: true, bitwise: false */ /*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */ -// Globals defined here -var RFB; -/* - * RFB namespace - */ - -RFB = { - -/* - * External interface variables and methods - */ -host : '', -port : 5900, -password : '', - -encrypt : true, -true_color : false, -b64encode : true, // false means UTF-8 on the wire -local_cursor : true, -connectTimeout : 2000, // time to wait for connection - - -// In preference order -encodings : [ - ['COPYRECT', 0x01, 'display_copy_rect'], - ['TIGHT_PNG', -260, 'display_tight_png'], - ['HEXTILE', 0x05, 'display_hextile'], - ['RRE', 0x02, 'display_rre'], - ['RAW', 0x00, 'display_raw'], - ['DesktopSize', -223, 'set_desktopsize'], - ['Cursor', -239, 'set_cursor'], - - // Psuedo-encoding settings - ['JPEG_quality_lo', -32, 'set_jpeg_quality'], -// ['JPEG_quality_hi', -23, 'set_jpeg_quality'], - ['compress_lo', -255, 'set_compress_level'] -// ['compress_hi', -247, 'set_compress_level'] - ], - - -setUpdateState: function(externalUpdateState) { - RFB.externalUpdateState = externalUpdateState; -}, - -setClipboardReceive: function(clipReceive) { - RFB.clipboardCopyTo = clipReceive; -}, - -setCanvasID: function(canvasID) { - RFB.canvasID = canvasID; -}, - -setEncrypt: function(encrypt) { - if ((!encrypt) || (encrypt in {'0':1, 'no':1, 'false':1})) { - RFB.encrypt = false; - } else { - RFB.encrypt = true; - } -}, - -setBase64: function(b64) { - if ((!b64) || (b64 in {'0':1, 'no':1, 'false':1})) { - RFB.b64encode = false; - } else { - RFB.b64encode = true; - } - Util.Debug("Set b64encode to: " + RFB.b64encode); -}, - -setTrueColor: function(trueColor) { - if ((!trueColor) || (trueColor in {'0':1, 'no':1, 'false':1})) { - RFB.true_color = false; - } else { - RFB.true_color = true; - } -}, - -setCursor: function(cursor) { +function RFB(conf) { + +conf = conf || {}; // Configuration +var that = {}, // Public API interface + + // Pre-declare private functions used before definitions (jslint) + init_vars, updateState, init_msg, normal_msg, recv_message, + framebufferUpdate, show_timings, + + pixelFormat, clientEncodings, fbUpdateRequest, + keyEvent, pointerEvent, clientCutText, + + extract_data_uri, scan_tight_imgs, + + send_array, checkEvents, // Overridable for testing + + + // + // Private RFB namespace variables + // + rfb_host = '', + rfb_port = 5900, + rfb_password = '', + + rfb_state = 'disconnected', + rfb_version = 0, + rfb_max_version= 3.8, + rfb_auth_scheme= '', + rfb_shared = 1, + + + // In preference order + encodings = [ + ['COPYRECT', 0x01 ], + ['TIGHT_PNG', -260 ], + ['HEXTILE', 0x05 ], + ['RRE', 0x02 ], + ['RAW', 0x00 ], + ['DesktopSize', -223 ], + ['Cursor', -239 ], + + // Psuedo-encoding settings + ['JPEG_quality_lo', -32 ], + //['JPEG_quality_hi', -23 ], + ['compress_lo', -255 ] + //['compress_hi', -247 ] + ], + + encHandlers = {}, + encNames = {}, + + ws = null, // Web Socket object + canvas = null, // Canvas object + sendID = null, // Send Queue check timer + + // Receive and send queues + RQ = [], // Receive Queue + SQ = "", // Send Queue + + // 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, + imgs : [] // TIGHT_PNG image queue + }, + + fb_Bpp = 4, + fb_depth = 3, + fb_width = 0, + fb_height = 0, + fb_name = "", + + cuttext = 'none', // ServerCutText wait state + cuttext_length = 0, + + scan_imgs_rate = 100, + last_req_time = 0, + rre_chunk_sz = 100, + + timing = { + last_fbu : 0, + fbu_total : 0, + fbu_total_cnt : 0, + full_fbu_total : 0, + full_fbu_cnt : 0, + + fbu_rt_start : 0, + fbu_rt_total : 0, + fbu_rt_cnt : 0, + + history : [], + history_start : 0, + h_time : 0, + h_rects : 0, + h_fbus : 0, + h_bytes : 0, + h_pixels : 0 + }, + + test_mode = false, + + /* Mouse state */ + mouse_buttonMask = 0, + mouse_arr = []; + + +// +// Configuration settings +// + +// VNC viewport rendering Canvas +Util.conf_default(conf, that, 'target', 'VNC_canvas'); + +Util.conf_default(conf, that, 'encrypt', false, true); +Util.conf_default(conf, that, 'true_color', true, true); +// false means UTF-8 on the wire +Util.conf_default(conf, that, 'b64encode', true, true); +Util.conf_default(conf, that, 'local_cursor', true, true); + +// time to wait for connection +Util.conf_default(conf, that, 'connectTimeout', 2000); +// frequency to check for send/receive +Util.conf_default(conf, that, 'check_rate', 217); +// frequency to send frameBufferUpdate requests +Util.conf_default(conf, that, 'fbu_req_rate', 1413); + +// state update callback +Util.conf_default(conf, that, 'updateState', function () { + Util.Debug(">> externalUpdateState stub"); }); +// clipboard contents received callback +Util.conf_default(conf, that, 'clipboardReceive', function () { + Util.Debug(">> clipboardReceive stub"); }); + + +// Override/add some specific getters/setters +that.set_local_cursor = function(cursor) { if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { - RFB.local_cursor = false; + conf.local_cursor = false; } else { - if (Canvas.isCursor()) { - RFB.local_cursor = true; + if (canvas.get_cursor_uri()) { + conf.local_cursor = true; } else { Util.Warn("Browser does not support local cursor"); } } -}, +}; -sendPassword: function(passwd) { - RFB.password = passwd; - RFB.state = "Authentication"; - setTimeout(RFB.init_msg, 1); -}, +that.get_canvas = function() { + return canvas; +}; -sendCtrlAltDel: function() { - if (RFB.state !== "normal") { return false; } - Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(RFB.keyEvent(0xFFE3, 1)); // Control - arr = arr.concat(RFB.keyEvent(0xFFE9, 1)); // Alt - arr = arr.concat(RFB.keyEvent(0xFFFF, 1)); // Delete - arr = arr.concat(RFB.keyEvent(0xFFFF, 0)); // Delete - arr = arr.concat(RFB.keyEvent(0xFFE9, 0)); // Alt - arr = arr.concat(RFB.keyEvent(0xFFE3, 0)); // Control - arr = arr.concat(RFB.fbUpdateRequest(1)); - RFB.send_array(arr); -}, - -load: function () { + + + +// +// Private functions +// + +// +// Setup routines +// + +// Create the public API interface +function constructor() { var i; - //Util.Debug(">> load"); + //Util.Debug(">> init"); - /* Load web-socket-js if no builtin WebSocket support */ - if (VNC_native_ws) { - Util.Info("Using native WebSockets"); - RFB.updateState('loaded', 'noVNC ready (using native WebSockets)'); - } else { - Util.Warn("Using web-socket-js flash bridge"); - if ((! Util.Flash) || - (Util.Flash.version < 9)) { - RFB.updateState('fatal', "WebSockets or Adobe Flash is required"); - } else if (document.location.href.substr(0, 7) === "file://") { - RFB.updateState('fatal', - "'file://' URL is incompatible with Adobe Flash"); - } else { - RFB.updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)'); - } + // 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]; } - - // Initialize canvas/fxcanvas + // Initialize canvas try { - Canvas.init(RFB.canvasID); + canvas = new Canvas({'target': conf.target}); } catch (exc) { - RFB.updateState('fatal', "No working Canvas"); + Util.Error("Canvas exception: " + exc); + updateState('fatal', "No working Canvas"); } - // Populate encoding lookup tables - RFB.encHandlers = {}; - RFB.encNames = {}; - for (i=0; i < RFB.encodings.length; i+=1) { - RFB.encHandlers[RFB.encodings[i][1]] = RFB[RFB.encodings[i][2]]; - RFB.encNames[RFB.encodings[i][1]] = RFB.encodings[i][0]; - } - //Util.Debug("<< load"); -}, - -connect: function (host, port, password) { - //Util.Debug(">> connect"); + //Util.Debug("<< init"); + return that; // Return the public API interface +} - RFB.host = host; - RFB.port = port; - RFB.password = (password !== undefined) ? password : ""; +function init_ws() { + //Util.Debug(">> init_ws"); - if ((!RFB.host) || (!RFB.port)) { - RFB.updateState('failed', "Must set host and port"); - return; + var uri = "", vars = []; + if (conf.encrypt) { + uri = "wss://"; + } else { + uri = "ws://"; + } + uri += rfb_host + ":" + rfb_port + "/"; + if (conf.b64encode) { + vars.push("b64encode"); } + if (vars.length > 0) { + uri += "?" + vars.join("&"); + } + Util.Info("connecting to " + uri); + ws = new WebSocket(uri); - RFB.updateState('connect'); - //Util.Debug("<< connect"); + ws.onmessage = recv_message; + ws.onopen = function(e) { + Util.Debug(">> WebSocket.onopen"); + if (rfb_state === "connect") { + updateState('ProtocolVersion', "Starting VNC handshake"); + } else { + updateState('failed', "Got unexpected WebSockets connection"); + } + Util.Debug("<< WebSocket.onopen"); + }; + ws.onclose = function(e) { + Util.Debug(">> WebSocket.onclose"); + if (rfb_state === 'normal') { + updateState('failed', 'Server disconnected'); + } else if (rfb_state === 'ProtocolVersion') { + updateState('failed', 'Failed to connect to server'); + } else { + updateState('disconnected', 'VNC disconnected'); + } + Util.Debug("<< WebSocket.onclose"); + }; + ws.onerror = function(e) { + Util.Debug(">> WebSocket.onerror"); + updateState('failed', "WebSocket error"); + Util.Debug("<< WebSocket.onerror"); + }; -}, + setTimeout(function () { + if (ws.readyState === WebSocket.CONNECTING) { + updateState('failed', "Connect timeout"); + } + }, conf.connectTimeout); -disconnect: function () { - //Util.Debug(">> disconnect"); - RFB.updateState('disconnected', 'Disconnected'); - //Util.Debug("<< disconnect"); -}, + //Util.Debug("<< init_ws"); +} -clipboardPasteFrom: function (text) { - if (RFB.state !== "normal") { return; } - //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); - RFB.send_array(RFB.clientCutText(text)); - //Util.Debug("<< clipboardPasteFrom"); -}, +init_vars = function() { + /* Reset state */ + cuttext = 'none'; + cuttext_length = 0; + RQ = []; + SQ = ""; + FBU.rects = 0; + FBU.subrects = 0; // RRE and HEXTILE + FBU.lines = 0; // RAW + FBU.tiles = 0; // HEXTILE + FBU.imgs = []; // TIGHT_PNG image queue + mouse_buttonMask = 0; + mouse_arr = []; + + timing.history_start = 0; + timing.history = []; + timing.h_fbus = 0; + timing.h_rects = 0; + timing.h_bytes = 0; + timing.h_pixels = 0; +}; + +// +// Utility routines +// /* - * Private variables and methods + * Running states: + * disconnected - idle state + * normal - connected + * + * Page states: + * loaded - page load, equivalent to disconnected + * connect - starting initialization + * password - waiting for password + * failed - abnormal transition to disconnected + * fatal - failed to load page, or fatal error + * + * VNC initialization states: + * ProtocolVersion + * Security + * Authentication + * SecurityResult + * ServerInitialization */ +updateState = function(state, statusMsg) { + var func, cmsg, oldstate = rfb_state; + if (state === oldstate) { + /* Already here, ignore */ + Util.Debug("Already in state '" + state + "', ignoring."); + return; + } -ws : null, // Web Socket object -sendID : null, -scanID : null, // TIGHT_PNG render image scanner - -// Receive and send queues -RQ : [], // Receive Queue -SQ : "", // Send Queue - -encHandlers : {}, -encNames : {}, - -// 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, - imgs : [] // TIGHT_PNG image queue -}, - -fb_Bpp : 4, -fb_depth : 3, - -max_version : 3.8, -version : 0, -auth_scheme : '', -state : 'disconnected', -cuttext : 'none', // ServerCutText wait state -ct_length : 0, - -shared : 1, -check_rate : 217, -scan_imgs_rate : 100, -req_rate : 1413, -last_req : 0, - -canvasID : 'VNC_canvas', -fb_width : 0, -fb_height : 0, -fb_name : "", -rre_chunk : 100, - -timing : { - last_fbu : 0, - fbu_total : 0, - fbu_total_cnt : 0, - full_fbu_total : 0, - full_fbu_cnt : 0, - - fbu_rt_start : 0, - fbu_rt_total : 0, - fbu_rt_cnt : 0, - - history : [], - history_start : 0, - h_time : 0, - h_rects : 0, - h_fbus : 0, - h_bytes : 0, - h_pixels : 0 -}, - -/* Mouse state */ -mouse_buttonmask : 0, -mouse_arr : [], + if (oldstate === 'fatal') { + Util.Error("Fatal error, cannot continue"); + } -/* - * Server message handlers - */ + if ((state === 'failed') || (state === 'fatal')) { + func = Util.Error; + } else { + func = Util.Warn; + } -/* RFB/VNC initialisation */ -init_msg: function () { - //Util.Debug(">> init_msg [RFB.state '" + RFB.state + "']"); + cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; + func("New state '" + state + "', was '" + oldstate + "'." + cmsg); - var RQ = RFB.RQ, strlen, reason, reason_len, sversion, cversion, - i, types, num_types, challenge, response, bpp, depth, - big_endian, true_color, name_length; + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Do disconnect action, but stay in failed state. + rfb_state = 'failed'; + } else { + rfb_state = state; + } - //Util.Debug("RQ (" + RQ.length + ") " + RQ); - switch (RFB.state) { + switch (state) { + case 'loaded': + case 'disconnected': - case 'ProtocolVersion' : - if (RQ.length < 12) { - RFB.updateState('failed', - "Disconnected: incomplete protocol version"); - return; + if (sendID) { + clearInterval(sendID); + sendID = null; } - sversion = RQ.shiftStr(12).substr(4,7); - Util.Info("Server ProtocolVersion: " + sversion); - switch (sversion) { - case "003.003": RFB.version = 3.3; break; - case "003.007": RFB.version = 3.7; break; - case "003.008": RFB.version = 3.8; break; - default: - RFB.updateState('failed', - "Invalid server version " + sversion); - return; + + if (ws) { + if (ws.readyState === WebSocket.OPEN) { + ws.close(); + } + ws.onmessage = function (e) { return; }; } - if (RFB.version > RFB.max_version) { - RFB.version = RFB.max_version; + + if (canvas && canvas.getContext()) { + canvas.stop(); + if (! /__debug__$/i.test(document.location.href)) { + canvas.clear(); + } } - 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 (RFB.SQ) { - RFB.ws.send(RFB.SQ); - RFB.SQ = ""; - } - } else { - Util.Debug("Delaying send"); - } - }, 50); + show_timings(); - cversion = "00" + parseInt(RFB.version,10) + - ".00" + ((RFB.version * 10) % 10); - RFB.send_string("RFB " + cversion + "\n"); - RFB.updateState('Security', "Sent ProtocolVersion: " + sversion); break; - case 'Security' : - if (RFB.version >= 3.7) { - num_types = RQ.shift8(); - if (num_types === 0) { - strlen = RQ.shift32(); - reason = RQ.shiftStr(strlen); - RFB.updateState('failed', - "Disconnected: security failure: " + reason); - return; - } - RFB.auth_scheme = 0; - types = RQ.shiftBytes(num_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) { - RFB.updateState('failed', - "Disconnected: unsupported security types: " + types); - return; - } - - RFB.send_array([RFB.auth_scheme]); - } else { - if (RQ.length < 4) { - RFB.updateState('failed', "Invalid security frame"); - return; - } - RFB.auth_scheme = RQ.shift32(); - } - RFB.updateState('Authentication', - "Authenticating using scheme: " + RFB.auth_scheme); - // Fall through - case 'Authentication' : - //Util.Debug("Security auth scheme: " + RFB.auth_scheme); - switch (RFB.auth_scheme) { - case 0: // connection failed - if (RQ.length < 4) { - //Util.Debug(" waiting for auth reason bytes"); - return; - } - strlen = RQ.shift32(); - reason = RQ.shiftStr(strlen); - RFB.updateState('failed', - "Disconnected: auth failure: " + reason); - return; - case 1: // no authentication - // RFB.send_array([RFB.shared]); // ClientInitialisation - RFB.updateState('SecurityResult'); - break; - case 2: // VNC authentication - if (RFB.password.length === 0) { - RFB.updateState('password', "Password Required"); - return; - } - if (RQ.length < 16) { - //Util.Debug(" waiting for auth challenge bytes"); - return; - } - challenge = RQ.shiftBytes(16); - //Util.Debug("Password: " + RFB.password); - //Util.Debug("Challenge: " + challenge + - // " (" + challenge.length + ")"); - response = RFB.DES(RFB.password, challenge); - //Util.Debug("Response: " + response + - // " (" + response.length + ")"); - - //Util.Debug("Sending DES encrypted auth response"); - RFB.send_array(response); - RFB.updateState('SecurityResult'); - break; - default: - RFB.updateState('failed', - "Disconnected: unsupported auth scheme: " + - RFB.auth_scheme); - return; - } - break; + case 'connect': + init_vars(); - case 'SecurityResult' : - if (RQ.length < 4) { - RFB.updateState('failed', "Invalid VNC auth response"); - return; + if ((ws) && (ws.readyState === WebSocket.OPEN)) { + ws.close(); } - switch (RQ.shift32()) { - case 0: // OK - RFB.updateState('ServerInitialisation', "Authentication OK"); - break; - case 1: // failed - if (RFB.version >= 3.8) { - reason_len = RQ.shift32(); - reason = RQ.shiftStr(reason_len); - RFB.updateState('failed', reason); - } else { - RFB.updateState('failed', "Authentication failed"); - } - return; - case 2: // too-many - RFB.updateState('failed', - "Disconnected: too many auth attempts"); - return; - } - RFB.send_array([RFB.shared]); // ClientInitialisation + init_ws(); // onopen transitions to 'ProtocolVersion' + break; - case 'ServerInitialisation' : - if (RQ.length < 24) { - RFB.updateState('failed', "Invalid server initialisation"); - return; - } - /* Screen size */ - RFB.fb_width = RQ.shift16(); - RFB.fb_height = RQ.shift16(); + case 'password': + // Ignore password state by default + break; - /* PIXEL_FORMAT */ - bpp = RQ.shift8(); - depth = RQ.shift8(); - big_endian = RQ.shift8(); - true_color = RQ.shift8(); - Util.Info("Screen: " + RFB.fb_width + "x" + RFB.fb_height + - ", bpp: " + bpp + ", depth: " + depth + - ", big_endian: " + big_endian + - ", true_color: " + true_color); + case 'normal': + if ((oldstate === 'disconnected') || (oldstate === 'failed')) { + Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); + } - /* Connection name/title */ - RQ.shiftStr(12); - name_length = RQ.shift32(); - RFB.fb_name = RQ.shiftStr(name_length); + break; - Canvas.resize(RFB.fb_width, RFB.fb_height, RFB.true_color); - Canvas.start(RFB.keyPress, RFB.mouseButton, RFB.mouseMove); - if (RFB.true_color) { - RFB.fb_Bpp = 4; - RFB.fb_depth = 3; - } else { - RFB.fb_Bpp = 1; - RFB.fb_depth = 1; + case 'failed': + if (oldstate === 'disconnected') { + Util.Error("Invalid transition from 'disconnected' to 'failed'"); + } + if (oldstate === 'normal') { + Util.Error("Error while connected."); + } + if (oldstate === 'init') { + Util.Error("Error while initializing."); } - response = RFB.pixelFormat(); - response = response.concat(RFB.clientEncodings()); - response = response.concat(RFB.fbUpdateRequest(0)); - RFB.timing.fbu_rt_start = (new Date()).getTime(); - RFB.send_array(response); - - /* Start pushing/polling */ - setTimeout(RFB.checkEvents, RFB.check_rate); - setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate); - RFB.timing.history_start = (new Date()).getTime(); - setTimeout(RFB.update_timings, 1000); - - if (RFB.encrypt) { - RFB.updateState('normal', "Connected (encrypted) to: " + RFB.fb_name); - } else { - RFB.updateState('normal', "Connected (unencrypted) to: " + RFB.fb_name); + if ((ws) && (ws.readyState === WebSocket.OPEN)) { + ws.close(); } + // Make sure we transition to disconnected + setTimeout(function() { updateState('disconnected'); }, 50); + break; - } - //Util.Debug("<< init_msg"); -}, -/* Normal RFB/VNC server messages */ -normal_msg: function () { - //Util.Debug(">> normal_msg"); + default: + // Invalid state transition - var RQ = RFB.RQ, ret = true, msg_type, - c, first_colour, num_colours, red, green, blue; + } - //Util.Debug(">> msg RQ.slice(0,10): " + RQ.slice(0,20)); - //Util.Debug(">> msg RQ.slice(-10,-1): " + RQ.slice(RQ.length-10,RQ.length)); - if (RFB.FBU.rects > 0) { - msg_type = 0; - } else if (RFB.cuttext !== 'none') { + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Leave the failed message + conf.updateState(that, state, oldstate); + } else { + conf.updateState(that, state, oldstate, statusMsg); + } +}; + +function encode_message(arr) { + if (conf.b64encode) { + /* base64 encode */ + SQ = SQ + Base64.encode(arr); + } else { + /* UTF-8 encode. 0 -> 256 to avoid WebSockets framing */ + SQ = SQ + arr.map(function (num) { + if (num === 0) { + return String.fromCharCode(256); + } else { + return String.fromCharCode(num); + } + } ).join(''); + } +} + +function decode_message(data) { + var i, length; + //Util.Debug(">> decode_message: " + data); + if (conf.b64encode) { + /* base64 decode */ + RQ = RQ.concat(Base64.decode(data, 0)); + } else { + /* UTF-8 decode. 256 -> 0 to WebSockets framing */ + length = data.length; + for (i=0; i < length; i += 1) { + RQ.push(data.charCodeAt(i) % 256); + } + } + //Util.Debug(">> decode_message, RQ: " + RQ); +} + +function handle_message() { + //Util.Debug("RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")"); + if (RQ.length == 0) { + Util.Warn("handle_message called on empty receive queue"); + return; + } + switch (rfb_state) { + case 'disconnected': + Util.Error("Got data while disconnected"); + break; + case 'failed': + Util.Warn("Giving up!"); + that.disconnect(); + break; + case 'normal': + if (normal_msg() && RQ.length > 0) { + // true means we can continue processing + Util.Debug("More data to process"); + // Give other events a chance to run + setTimeout(handle_message, 10); + } + break; + default: + init_msg(); + break; + } +} + +recv_message = function(e) { + //Util.Debug(">> recv_message"); + + try { + decode_message(e.data); + if (RQ.length > 0) { + handle_message(); + } else { + Util.Debug("Ignoring empty message"); + } + } catch (exc) { + if (typeof exc.stack !== 'undefined') { + Util.Warn("recv_message, caught exception: " + exc.stack); + } else if (typeof exc.description !== 'undefined') { + Util.Warn("recv_message, caught exception: " + exc.description); + } else { + Util.Warn("recv_message, caught exception:" + exc); + } + if (typeof exc.name !== 'undefined') { + updateState('failed', exc.name + ": " + exc.message); + } else { + updateState('failed', exc); + } + } + //Util.Debug("<< recv_message"); +}; + +// overridable for testing +send_array = function(arr) { + //Util.Debug(">> send_array: " + arr); + encode_message(arr); + if (ws.bufferedAmount === 0) { + //Util.Debug("arr: " + arr); + //Util.Debug("SQ: " + SQ); + ws.send(SQ); + SQ = ""; + } else { + Util.Debug("Delaying send"); + } +}; + +function send_string(str) { + //Util.Debug(">> send_string: " + str); + send_array(str.split('').map( + function (chr) { return chr.charCodeAt(0); } ) ); +} + +function genDES(password, challenge) { + var i, passwd, response; + passwd = []; + response = challenge.slice(); + for (i=0; i < password.length; i += 1) { + passwd.push(password.charCodeAt(i)); + } + + DES.setKeys(passwd); + DES.encrypt(response, 0, response, 0); + DES.encrypt(response, 8, response, 8); + return response; +} + +function flushClient() { + if (mouse_arr.length > 0) { + //send_array(mouse_arr.concat(fbUpdateRequest(1))); + send_array(mouse_arr); + setTimeout(function() { + send_array(fbUpdateRequest(1)); + }, 50); + + mouse_arr = []; + return true; + } else { + return false; + } +} + +// overridable for testing +checkEvents = function() { + var now; + if (rfb_state === 'normal') { + if (! flushClient()) { + now = new Date().getTime(); + if (now > last_req_time + conf.fbu_req_rate) { + last_req_time = now; + send_array(fbUpdateRequest(1)); + } + } + } + setTimeout(checkEvents, conf.check_rate); +}; + +function keyPress(keysym, down) { + var arr; + arr = keyEvent(keysym, down); + arr = arr.concat(fbUpdateRequest(1)); + send_array(arr); +} + +function mouseButton(x, y, down, bmask) { + if (down) { + mouse_buttonMask |= bmask; + } else { + mouse_buttonMask ^= bmask; + } + mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); + flushClient(); +} + +function mouseMove(x, y) { + //Util.Debug('>> mouseMove ' + x + "," + y); + mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); +} + + +function update_timings() { + var now, offset; + now = (new Date()).getTime(); + timing.history.push([now, + timing.h_fbus, + timing.h_rects, + timing.h_bytes, + timing.h_pixels]); + timing.h_fbus = 0; + timing.h_rects = 0; + timing.h_bytes = 0; + timing.h_pixels = 0; + if ((rfb_state !== 'disconnected') && (rfb_state !== 'failed')) { + // Try for every second + offset = (now - timing.history_start) % 1000; + if (offset < 500) { + setTimeout(update_timings, 1000 - offset); + } else { + setTimeout(update_timings, 2000 - offset); + } + } +} + +show_timings = function() { + var i, history, msg, + delta, tot_time = 0, tot_fbus = 0, tot_rects = 0, + tot_bytes = 0, tot_pixels = 0; + if (timing.history_start === 0) { return; } + //Util.Debug(">> show_timings"); + update_timings(); // Final accumulate + msg = "\nTimings\n"; + msg += " time: fbus,rects,bytes,pixels\n"; + for (i=0; i < timing.history.length; i += 1) { + history = timing.history[i]; + delta = ((history[0]-timing.history_start)/1000); + tot_time = delta; + tot_fbus += history[1]; + tot_rects += history[2]; + tot_bytes += history[3]; + tot_pixels += history[4]; + + msg += " " + delta.toFixed(3); + msg += ": " + history.slice(1) + "\n"; + } + msg += "\nTotals:\n"; + msg += " time: fbus,rects,bytes,pixels\n"; + msg += " " + tot_time.toFixed(3); + msg += ": " + tot_fbus + "," + tot_rects; + msg += "," + tot_bytes + "," + tot_pixels; + Util.Info(msg); + //Util.Debug("<< show_timings"); +}; + +// +// Server message handlers +// + +// RFB/VNC initialisation message handler +init_msg = function() { + //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); + + var strlen, reason, reason_len, sversion, cversion, + i, types, num_types, challenge, response, bpp, depth, + big_endian, true_color, name_length; + + //Util.Debug("RQ (" + RQ.length + ") " + RQ); + switch (rfb_state) { + + case 'ProtocolVersion' : + if (RQ.length < 12) { + updateState('failed', + "Disconnected: incomplete protocol version"); + return; + } + sversion = RQ.shiftStr(12).substr(4,7); + Util.Info("Server ProtocolVersion: " + sversion); + switch (sversion) { + case "003.003": rfb_version = 3.3; break; + case "003.007": rfb_version = 3.7; break; + case "003.008": rfb_version = 3.8; break; + default: + updateState('failed', + "Invalid server version " + sversion); + return; + } + if (rfb_version > rfb_max_version) { + rfb_version = rfb_max_version; + } + + if (! test_mode) { + sendID = setInterval(function() { + // Send updates either at a rate of one update + // every 50ms, or whatever slower rate the network + // can handle. + if (ws.bufferedAmount === 0) { + if (SQ) { + ws.send(SQ); + SQ = ""; + } + } else { + Util.Debug("Delaying send"); + } + }, 50); + } + + cversion = "00" + parseInt(rfb_version,10) + + ".00" + ((rfb_version * 10) % 10); + send_string("RFB " + cversion + "\n"); + updateState('Security', "Sent ProtocolVersion: " + sversion); + break; + + case 'Security' : + if (rfb_version >= 3.7) { + num_types = RQ.shift8(); + if (num_types === 0) { + strlen = RQ.shift32(); + reason = RQ.shiftStr(strlen); + updateState('failed', + "Disconnected: security failure: " + reason); + return; + } + rfb_auth_scheme = 0; + types = RQ.shiftBytes(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) { + updateState('failed', + "Disconnected: unsupported security types: " + types); + return; + } + + send_array([rfb_auth_scheme]); + } else { + if (RQ.length < 4) { + updateState('failed', "Invalid security frame"); + return; + } + rfb_auth_scheme = RQ.shift32(); + } + updateState('Authentication', + "Authenticating using scheme: " + rfb_auth_scheme); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + case 'Authentication' : + //Util.Debug("Security auth scheme: " + rfb_auth_scheme); + switch (rfb_auth_scheme) { + case 0: // connection failed + if (RQ.length < 4) { + //Util.Debug(" waiting for auth reason bytes"); + return; + } + strlen = RQ.shift32(); + reason = RQ.shiftStr(strlen); + updateState('failed', + "Disconnected: auth failure: " + reason); + return; + case 1: // no authentication + updateState('SecurityResult'); + break; + case 2: // VNC authentication + if (rfb_password.length === 0) { + updateState('password', "Password Required"); + return; + } + if (RQ.length < 16) { + //Util.Debug(" waiting for auth challenge bytes"); + return; + } + challenge = RQ.shiftBytes(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"); + send_array(response); + updateState('SecurityResult'); + break; + default: + updateState('failed', + "Disconnected: unsupported auth scheme: " + + rfb_auth_scheme); + return; + } + break; + + case 'SecurityResult' : + if (RQ.length < 4) { + updateState('failed', "Invalid VNC auth response"); + return; + } + switch (RQ.shift32()) { + case 0: // OK + updateState('ServerInitialisation', "Authentication OK"); + break; + case 1: // failed + if (rfb_version >= 3.8) { + reason_len = RQ.shift32(); + reason = RQ.shiftStr(reason_len); + updateState('failed', reason); + } else { + updateState('failed', "Authentication failed"); + } + return; + case 2: // too-many + updateState('failed', + "Disconnected: too many auth attempts"); + return; + } + send_array([rfb_shared]); // ClientInitialisation + break; + + case 'ServerInitialisation' : + if (RQ.length < 24) { + updateState('failed', "Invalid server initialisation"); + return; + } + + /* Screen size */ + fb_width = RQ.shift16(); + fb_height = RQ.shift16(); + + /* PIXEL_FORMAT */ + bpp = RQ.shift8(); + depth = RQ.shift8(); + big_endian = RQ.shift8(); + true_color = RQ.shift8(); + + Util.Info("Screen: " + fb_width + "x" + fb_height + + ", bpp: " + bpp + ", depth: " + depth + + ", big_endian: " + big_endian + + ", true_color: " + true_color); + + /* Connection name/title */ + RQ.shiftStr(12); + name_length = RQ.shift32(); + fb_name = RQ.shiftStr(name_length); + + canvas.resize(fb_width, fb_height, conf.true_color); + canvas.start(keyPress, mouseButton, mouseMove); + + 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(fbUpdateRequest(0)); + timing.fbu_rt_start = (new Date()).getTime(); + send_array(response); + + /* Start pushing/polling */ + setTimeout(checkEvents, conf.check_rate); + setTimeout(scan_tight_imgs, scan_imgs_rate); + timing.history_start = (new Date()).getTime(); + setTimeout(update_timings, 1000); + + 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, + c, first_colour, num_colours, red, green, blue; + + //Util.Debug(">> msg RQ.slice(0,10): " + RQ.slice(0,20)); + //Util.Debug(">> msg RQ.slice(-10,-1): " + RQ.slice(RQ.length-10,RQ.length)); + if (FBU.rects > 0) { + msg_type = 0; + } else if (cuttext !== 'none') { msg_type = 3; } else { msg_type = RQ.shift8(); } switch (msg_type) { case 0: // FramebufferUpdate - ret = RFB.framebufferUpdate(); + ret = framebufferUpdate(); // false means need more data break; case 1: // SetColourMapEntries Util.Debug("SetColourMapEntries"); @@ -523,10 +904,10 @@ normal_msg: function () { //Util.Debug("red after: " + red); green = parseInt(RQ.shift16() / 256, 10); blue = parseInt(RQ.shift16() / 256, 10); - Canvas.colourMap[first_colour + c] = [red, green, blue]; + canvas.set_colourMap([red, green, blue], first_colour + c); } Util.Info("Registered " + num_colours + " colourMap entries"); - //Util.Debug("colourMap: " + Canvas.colourMap); + //Util.Debug("colourMap: " + canvas.get_colourMap()); break; case 2: // Bell Util.Warn("Bell (unsupported)"); @@ -534,39 +915,37 @@ normal_msg: function () { case 3: // ServerCutText Util.Debug("ServerCutText"); Util.Debug("RQ:" + RQ.slice(0,20)); - if (RFB.cuttext === 'none') { - RFB.cuttext = 'header'; + if (cuttext === 'none') { + cuttext = 'header'; } - if (RFB.cuttext === 'header') { + if (cuttext === 'header') { if (RQ.length < 7) { //Util.Debug("waiting for ServerCutText header"); return false; } RQ.shiftBytes(3); // Padding - RFB.ct_length = RQ.shift32(); + cuttext_length = RQ.shift32(); } - RFB.cuttext = 'bytes'; - if (RQ.length < RFB.ct_length) { + cuttext = 'bytes'; + if (RQ.length < cuttext_length) { //Util.Debug("waiting for ServerCutText bytes"); return false; } - RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length)); - RFB.cuttext = 'none'; + conf.clipboardReceive(that, RQ.shiftStr(cuttext_length)); + cuttext = 'none'; break; default: - RFB.updateState('failed', + updateState('failed', "Disconnected: illegal server message type " + msg_type); Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); break; } //Util.Debug("<< normal_msg"); return ret; -}, +}; -framebufferUpdate: function() { - var RQ = RFB.RQ, FBU = RFB.FBU, timing = RFB.timing, - now, fbu_rt_diff, last_bytes, last_rects, - ret = true; +framebufferUpdate = function() { + var now, fbu_rt_diff, last_bytes, last_rects, ret = true; if (FBU.rects === 0) { //Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20)); @@ -587,7 +966,13 @@ framebufferUpdate: function() { } } - while ((FBU.rects > 0) && (RQ.length >= FBU.bytes)) { + while (FBU.rects > 0) { + if (rfb_state !== "normal") { + return false; + } + if (RQ.length < FBU.bytes) { + return false; + } if (FBU.bytes === 0) { if (RQ.length < 12) { //Util.Debug(" waiting for rect header bytes"); @@ -601,19 +986,19 @@ framebufferUpdate: function() { FBU.encoding = parseInt(RQ.shift32(), 10); timing.h_bytes += 12; - if (RFB.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 += "(" + RFB.encNames[FBU.encoding] + ")"; + msg += "(" + encNames[FBU.encoding] + ")"; msg += ", RQ.length: " + RQ.length; Util.Debug(msg); */ } else { - RFB.updateState('failed', + updateState('failed', "Disconnected: unsupported encoding " + FBU.encoding); return false; @@ -624,7 +1009,8 @@ framebufferUpdate: function() { last_bytes = RQ.length; last_rects = FBU.rects; - ret = RFB.encHandlers[FBU.encoding](); + // false ret means need more data + ret = encHandlers[FBU.encoding](); now = (new Date()).getTime(); timing.cur_fbu += (now - timing.last_fbu); @@ -637,8 +1023,8 @@ framebufferUpdate: function() { } if (FBU.rects === 0) { - if (((FBU.width === RFB.fb_width) && - (FBU.height === RFB.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; @@ -662,101 +1048,102 @@ framebufferUpdate: function() { timing.fbu_rt_start = 0; } } - - if (RFB.state !== "normal") { return true; } } return ret; -}, +}; -/* - * FramebufferUpdate encodings - */ +// +// FramebufferUpdate encodings +// -display_raw: function () { +encHandlers.RAW = function display_raw() { //Util.Debug(">> display_raw"); - var RQ = RFB.RQ, FBU = RFB.FBU, cur_y, cur_height; + var cur_y, cur_height; if (FBU.lines === 0) { FBU.lines = FBU.height; } - FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line + FBU.bytes = FBU.width * fb_Bpp; // At least a line if (RQ.length < FBU.bytes) { //Util.Debug(" waiting for " + // (FBU.bytes - RQ.length) + " RAW bytes"); - return; + return false; } cur_y = FBU.y + (FBU.height - FBU.lines); cur_height = Math.min(FBU.lines, - Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp))); - Canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); - RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp); + Math.floor(RQ.length/(FBU.width * fb_Bpp))); + canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); + RQ.shiftBytes(FBU.width * cur_height * fb_Bpp); FBU.lines -= cur_height; if (FBU.lines > 0) { - FBU.bytes = FBU.width * RFB.fb_Bpp; // At least another line + FBU.bytes = FBU.width * fb_Bpp; // At least another line } else { FBU.rects -= 1; FBU.bytes = 0; } -}, + return true; +}; -display_copy_rect: function () { +encHandlers.COPYRECT = function display_copy_rect() { //Util.Debug(">> display_copy_rect"); - var RQ = RFB.RQ, FBU = RFB.FBU, old_x, old_y; + var old_x, old_y; if (RQ.length < 4) { //Util.Debug(" waiting for " + // (FBU.bytes - RQ.length) + " COPYRECT bytes"); - return; + return false; } old_x = RQ.shift16(); old_y = RQ.shift16(); - Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); + canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); FBU.rects -= 1; FBU.bytes = 0; -}, + return true; +}; + +encHandlers.RRE = function display_rre() { + //Util.Debug(">> display_rre (" + RQ.length + " bytes)"); + var color, x, y, width, height, chunk; -display_rre: function () { - //Util.Debug(">> display_rre (" + RFB.RQ.length + " bytes)"); - var RQ = RFB.RQ, FBU = RFB.FBU, color, x, y, width, height, chunk; if (FBU.subrects === 0) { - if (RQ.length < 4 + RFB.fb_Bpp) { + if (RQ.length < 4 + fb_Bpp) { //Util.Debug(" waiting for " + - // (4 + RFB.fb_Bpp - RQ.length) + " RRE bytes"); - return; + // (4 + fb_Bpp - RQ.length) + " RRE bytes"); + return false; } FBU.subrects = RQ.shift32(); - color = RQ.shiftBytes(RFB.fb_Bpp); // Background - Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + color = RQ.shiftBytes(fb_Bpp); // Background + canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); } - while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) { - color = RQ.shiftBytes(RFB.fb_Bpp); + while ((FBU.subrects > 0) && (RQ.length >= (fb_Bpp + 8))) { + color = RQ.shiftBytes(fb_Bpp); x = RQ.shift16(); y = RQ.shift16(); width = RQ.shift16(); height = RQ.shift16(); - Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); + canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); FBU.subrects -= 1; } //Util.Debug(" display_rre: rects: " + FBU.rects + // ", FBU.subrects: " + FBU.subrects); if (FBU.subrects > 0) { - chunk = Math.min(RFB.rre_chunk, FBU.subrects); - FBU.bytes = (RFB.fb_Bpp + 8) * chunk; + chunk = Math.min(rre_chunk_sz, FBU.subrects); + FBU.bytes = (fb_Bpp + 8) * chunk; } else { FBU.rects -= 1; FBU.bytes = 0; } //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); -}, + return true; +}; -display_hextile: function() { +encHandlers.HEXTILE = function display_hextile() { //Util.Debug(">> display_hextile"); - var RQ = RFB.RQ, FBU = RFB.FBU, - subencoding, subrects, idx, tile, color, cur_tile, + var subencoding, subrects, idx, tile, color, cur_tile, tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh; if (FBU.tiles === 0) { @@ -771,14 +1158,14 @@ display_hextile: function() { FBU.bytes = 1; if (RQ.length < FBU.bytes) { //Util.Debug(" waiting for HEXTILE subencoding byte"); - return; + return false; } subencoding = RQ[0]; // Peek if (subencoding > 30) { // Raw - RFB.updateState('failed', + updateState('failed', "Disconnected: illegal hextile subencoding " + subencoding); //Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); - return; + return false; } subrects = 0; cur_tile = FBU.total_tiles - FBU.tiles; @@ -792,24 +1179,24 @@ display_hextile: function() { /* Figure out how much we are expecting */ if (subencoding & 0x01) { // Raw //Util.Debug(" Raw subencoding"); - FBU.bytes += w * h * RFB.fb_Bpp; + FBU.bytes += w * h * fb_Bpp; } else { if (subencoding & 0x02) { // Background - FBU.bytes += RFB.fb_Bpp; + FBU.bytes += fb_Bpp; } if (subencoding & 0x04) { // Foreground - FBU.bytes += RFB.fb_Bpp; + FBU.bytes += fb_Bpp; } if (subencoding & 0x08) { // AnySubrects FBU.bytes += 1; // Since we aren't shifting it off if (RQ.length < FBU.bytes) { /* Wait for subrects byte */ //Util.Debug(" waiting for hextile subrects header byte"); - return; + return false; } subrects = RQ[FBU.bytes-1]; // Peek if (subencoding & 0x10) { // SubrectsColoured - FBU.bytes += subrects * (RFB.fb_Bpp + 2); + FBU.bytes += subrects * (fb_Bpp + 2); } else { FBU.bytes += subrects * 2; } @@ -827,7 +1214,7 @@ display_hextile: function() { if (RQ.length < FBU.bytes) { //Util.Debug(" waiting for " + // (FBU.bytes - RQ.length) + " hextile bytes"); - return; + return false; } /* We know the encoding and have a whole tile */ @@ -838,28 +1225,28 @@ display_hextile: function() { /* Weird: ignore blanks after RAW */ Util.Debug(" Ignoring blank after RAW"); } else { - Canvas.fillRect(x, y, w, h, FBU.background); + canvas.fillRect(x, y, w, h, FBU.background); } } else if (FBU.subencoding & 0x01) { // Raw - Canvas.blitImage(x, y, w, h, RQ, idx); + canvas.blitImage(x, y, w, h, RQ, idx); } else { if (FBU.subencoding & 0x02) { // Background - FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp); - idx += RFB.fb_Bpp; + FBU.background = RQ.slice(idx, idx + fb_Bpp); + idx += fb_Bpp; } if (FBU.subencoding & 0x04) { // Foreground - FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp); - idx += RFB.fb_Bpp; + FBU.foreground = RQ.slice(idx, idx + fb_Bpp); + idx += fb_Bpp; } - tile = Canvas.getTile(x, y, w, h, FBU.background); + tile = canvas.getTile(x, y, w, h, FBU.background); if (FBU.subencoding & 0x08) { // AnySubrects subrects = RQ[idx]; idx += 1; for (s = 0; s < subrects; s += 1) { if (FBU.subencoding & 0x10) { // SubrectsColoured - color = RQ.slice(idx, idx + RFB.fb_Bpp); - idx += RFB.fb_Bpp; + color = RQ.slice(idx, idx + fb_Bpp); + idx += fb_Bpp; } else { color = FBU.foreground; } @@ -873,10 +1260,10 @@ display_hextile: function() { sw = (wh >> 4) + 1; sh = (wh & 0x0f) + 1; - Canvas.setSubTile(tile, sx, sy, sw, sh, color); + canvas.setSubTile(tile, sx, sy, sw, sh, color); } } - Canvas.putTile(tile); + canvas.putTile(tile); } RQ.shiftBytes(FBU.bytes); FBU.lastsubencoding = FBU.subencoding; @@ -889,13 +1276,13 @@ display_hextile: function() { } //Util.Debug("<< display_hextile"); -}, + return true; +}; -display_tight_png: function() { +encHandlers.TIGHT_PNG = function display_tight_png() { //Util.Debug(">> display_tight_png"); - var RQ = RFB.RQ, FBU = RFB.FBU, - ctl, cmode, clength, getCLength, color, img; + var ctl, cmode, clength, getCLength, color, img; //Util.Debug(" FBU.rects: " + FBU.rects); //Util.Debug(" RQ.length: " + RQ.length); //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20)); @@ -904,7 +1291,7 @@ display_tight_png: function() { FBU.bytes = 1; // compression-control byte if (RQ.length < FBU.bytes) { Util.Debug(" waiting for TIGHT compression-control byte"); - return; + return false; } // Get 'compact length' header and data size @@ -931,25 +1318,25 @@ display_tight_png: function() { } switch (cmode) { // fill uses fb_depth because TPIXELs drop the padding byte - case "fill": FBU.bytes += RFB.fb_depth; break; // TPIXEL + case "fill": FBU.bytes += fb_depth; break; // TPIXEL case "jpeg": FBU.bytes += 3; break; // max clength case "png": FBU.bytes += 3; break; // max clength } if (RQ.length < FBU.bytes) { Util.Debug(" waiting for TIGHT " + cmode + " bytes"); - return; + return false; } - //Util.Debug(" RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")"); + //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")"); //Util.Debug(" cmode: " + cmode); // Determine FBU.bytes switch (cmode) { case "fill": RQ.shift8(); // shift off ctl - color = RQ.shiftBytes(RFB.fb_depth); - Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + color = RQ.shiftBytes(fb_depth); + canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); break; case "jpeg": case "png": @@ -957,17 +1344,17 @@ display_tight_png: function() { FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data if (RQ.length < FBU.bytes) { Util.Debug(" waiting for TIGHT " + cmode + " bytes"); - return; + return false; } // We have everything, render it //Util.Debug(" png, RQ.length: " + RQ.length + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]); RQ.shiftBytes(1 + clength[0]); // shift off ctl + compact length img = new Image(); - img.onload = RFB.scan_tight_imgs; + img.onload = scan_tight_imgs; FBU.imgs.push([img, FBU.x, FBU.y]); img.src = "data:image/" + cmode + - RFB.extract_data_uri(RQ.shiftBytes(clength[1])); + extract_data_uri(RQ.shiftBytes(clength[1])); img = null; break; } @@ -976,643 +1363,294 @@ display_tight_png: function() { //Util.Debug(" ending RQ.length: " + RQ.length); //Util.Debug(" ending RQ.slice(0,20): " + RQ.slice(0,20)); //Util.Debug("<< display_tight_png"); -}, + return true; +}; -extract_data_uri : function (arr) { +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); -}, +}; -scan_tight_imgs : function () { - var img, imgs; - if (RFB.state === 'normal') { - imgs = RFB.FBU.imgs; +scan_tight_imgs = function() { + var img, imgs, ctx; + ctx = canvas.getContext(); + if (rfb_state === 'normal') { + imgs = FBU.imgs; while ((imgs.length > 0) && (imgs[0][0].complete)) { - img = imgs.shift(); - Canvas.ctx.drawImage(img[0], img[1], img[2]); - } - setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate); - } -}, - -set_desktopsize : function () { - Util.Debug(">> set_desktopsize"); - RFB.fb_width = RFB.FBU.width; - RFB.fb_height = RFB.FBU.height; - Canvas.clear(); - Canvas.resize(RFB.fb_width, RFB.fb_height); - RFB.timing.fbu_rt_start = (new Date()).getTime(); - // Send a new non-incremental request - RFB.send_array(RFB.fbUpdateRequest(0)); - - RFB.FBU.bytes = 0; - RFB.FBU.rects -= 1; - - Util.Debug("<< set_desktopsize"); -}, - -set_cursor: function () { - var x, y, w, h, pixelslength, masklength; - //Util.Debug(">> set_cursor"); - x = RFB.FBU.x; // hotspot-x - y = RFB.FBU.y; // hotspot-y - w = RFB.FBU.width; - h = RFB.FBU.height; - - pixelslength = w * h * RFB.fb_Bpp; - masklength = Math.floor((w + 7) / 8) * h; - - if (RFB.RQ.length < (pixelslength + masklength)) { - //Util.Debug("waiting for cursor encoding bytes"); - RFB.FBU.bytes = pixelslength + masklength; - return false; - } - - //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - - Canvas.changeCursor(RFB.RQ.shiftBytes(pixelslength), - RFB.RQ.shiftBytes(masklength), - x, y, w, h); - - RFB.FBU.bytes = 0; - RFB.FBU.rects -= 1; - - //Util.Debug("<< set_cursor"); -}, - -set_jpeg_quality : function () { - Util.Debug(">> set_jpeg_quality"); -}, -set_compress_level: function () { - Util.Debug(">> set_compress_level"); -}, - -/* - * Client message routines - */ - -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(RFB.fb_Bpp * 8); // bits-per-pixel - arr.push8(RFB.fb_depth * 8); // depth - arr.push8(0); // little-endian - arr.push8(RFB.true_color); // 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 - //Util.Debug("<< pixelFormat"); - return arr; -}, - -fixColourMapEntries: function () { -}, - -clientEncodings: function () { - //Util.Debug(">> clientEncodings"); - var arr, i, encList = []; - - for (i=0; i> 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); - //Util.Debug("<< fbUpdateRequest"); - 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; -}, - -pointerEvent: function (x, y) { - //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + - // " , mask: " + RFB.mouse_buttonMask); - var arr; - arr = [5]; // msg-type - arr.push8(RFB.mouse_buttonMask); - arr.push16(x); - arr.push16(y); - //Util.Debug("<< pointerEvent"); - return arr; -}, - -clientCutText: function (text) { - //Util.Debug(">> 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); - //Util.Debug("<< clientCutText:" + arr); - return arr; -}, - - -/* - * Utility routines - */ - -encode_message: function(arr) { - if (RFB.b64encode) { - /* base64 encode */ - RFB.SQ = RFB.SQ + Base64.encode(arr); - } else { - /* UTF-8 encode. 0 -> 256 to avoid WebSockets framing */ - RFB.SQ = RFB.SQ + arr.map(function (num) { - if (num === 0) { - return String.fromCharCode(256); - } else { - return String.fromCharCode(num); - } - } ).join(''); - } -}, - -decode_message: function(data) { - var i, length, RQ = RFB.RQ; - //Util.Debug(">> decode_message: " + data); - if (RFB.b64encode) { - /* base64 decode */ - RFB.RQ = RFB.RQ.concat(Base64.decode(data, 0)); - } else { - /* UTF-8 decode. 256 -> 0 to WebSockets framing */ - length = data.length; - for (i=0; i < length; i += 1) { - RQ.push(data.charCodeAt(i) % 256); - } - } - //Util.Debug(">> decode_message, RQ: " + RFB.RQ); -}, - -recv_message: function(e) { - //Util.Debug(">> recv_message"); - - try { - RFB.decode_message(e.data); - if (RFB.RQ.length > 0) { - RFB.handle_message(); - } else { - Util.Debug("Ignoring empty message"); - } - } catch (exc) { - if (typeof exc.stack !== 'undefined') { - Util.Warn("recv_message, caught exception: " + exc.stack); - } else if (typeof exc.description !== 'undefined') { - Util.Warn("recv_message, caught exception: " + exc.description); - } else { - Util.Warn("recv_message, caught exception:" + exc); - } - if (typeof exc.name !== 'undefined') { - RFB.updateState('failed', exc.name + ": " + exc.message); - } else { - RFB.updateState('failed', exc); - } - } - //Util.Debug("<< recv_message"); -}, - -handle_message: function () { - //Util.Debug("RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")"); - switch (RFB.state) { - case 'disconnected': - Util.Error("Got data while disconnected"); - break; - case 'failed': - Util.Warn("Giving up!"); - RFB.disconnect(); - break; - case 'normal': - RFB.normal_msg(); - /* - while (RFB.RQ.length > 0) { - if (RFB.normal_msg() && RFB.state === 'normal') { - Util.Debug("More to process"); - } else { - break; - } - } - */ - break; - default: - RFB.init_msg(); - break; - } -}, - -send_string: function (str) { - //Util.Debug(">> send_string: " + str); - RFB.send_array(str.split('').map( - function (chr) { return chr.charCodeAt(0); } ) ); -}, - -send_array: function (arr) { - //Util.Debug(">> send_array: " + arr); - RFB.encode_message(arr); - if (RFB.ws.bufferedAmount === 0) { - //Util.Debug("arr: " + arr); - //Util.Debug("RFB.SQ: " + RFB.SQ); - RFB.ws.send(RFB.SQ); - RFB.SQ = ""; - } else { - Util.Debug("Delaying send"); - } -}, - -DES: function (password, challenge) { - var i, passwd, response; - passwd = []; - response = challenge.slice(); - for (i=0; i < password.length; i += 1) { - passwd.push(password.charCodeAt(i)); - } - - DES.setKeys(passwd); - DES.encrypt(response, 0, response, 0); - DES.encrypt(response, 8, response, 8); - return response; -}, - -flushClient: function () { - if (RFB.mouse_arr.length > 0) { - //RFB.send_array(RFB.mouse_arr.concat(RFB.fbUpdateRequest(1))); - RFB.send_array(RFB.mouse_arr); - setTimeout(function() { - RFB.send_array(RFB.fbUpdateRequest(1)); - }, 50); - - RFB.mouse_arr = []; - return true; - } else { - return false; - } -}, - -checkEvents: function () { - var now; - if (RFB.state === 'normal') { - if (! RFB.flushClient()) { - now = new Date().getTime(); - if (now > RFB.last_req + RFB.req_rate) { - RFB.last_req = now; - RFB.send_array(RFB.fbUpdateRequest(1)); - } - } - } - setTimeout(RFB.checkEvents, RFB.check_rate); -}, - -keyPress: function (keysym, down) { - var arr; - arr = RFB.keyEvent(keysym, down); - arr = arr.concat(RFB.fbUpdateRequest(1)); - RFB.send_array(arr); -}, - -mouseButton: function(x, y, down, bmask) { - if (down) { - RFB.mouse_buttonMask |= bmask; - } else { - RFB.mouse_buttonMask ^= bmask; + img = imgs.shift(); + ctx.drawImage(img[0], img[1], img[2]); + } + setTimeout(scan_tight_imgs, scan_imgs_rate); } - RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); - RFB.flushClient(); -}, +}; -mouseMove: function(x, y) { - //Util.Debug('>> mouseMove ' + x + "," + y); - RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); -}, +encHandlers.DesktopSize = function set_desktopsize() { + Util.Debug(">> set_desktopsize"); + fb_width = FBU.width; + fb_height = FBU.height; + canvas.clear(); + canvas.resize(fb_width, fb_height); + timing.fbu_rt_start = (new Date()).getTime(); + // Send a new non-incremental request + send_array(fbUpdateRequest(0)); -clipboardCopyTo: function (text) { - Util.Debug(">> clipboardCopyTo stub"); - // Stub -}, + FBU.bytes = 0; + FBU.rects -= 1; -externalUpdateState: function(state, msg) { - Util.Debug(">> externalUpdateState stub"); - // Stub -}, + Util.Debug("<< set_desktopsize"); + return true; +}; -/* - * Running states: - * disconnected - idle state - * normal - connected - * - * Page states: - * loaded - page load, equivalent to disconnected - * connect - starting initialization - * password - waiting for password - * failed - abnormal transition to disconnected - * fatal - failed to load page, or fatal error - * - * VNC initialization states: - * ProtocolVersion - * Security - * Authentication - * SecurityResult - * ServerInitialization - */ -updateState: function(state, statusMsg) { - var func, cmsg, oldstate = RFB.state; - if (state === oldstate) { - /* Already here, ignore */ - Util.Debug("Already in state '" + state + "', ignoring."); - return; - } +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; - if (oldstate === 'fatal') { - Util.Error("Fatal error, cannot continue"); - } + pixelslength = w * h * fb_Bpp; + masklength = Math.floor((w + 7) / 8) * h; - if ((state === 'failed') || (state === 'fatal')) { - func = Util.Error; - } else { - func = Util.Warn; + if (RQ.length < (pixelslength + masklength)) { + //Util.Debug("waiting for cursor encoding bytes"); + FBU.bytes = pixelslength + masklength; + return false; } - cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; - func("New state '" + state + "', was '" + oldstate + "'." + cmsg); + //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - if ((oldstate === 'failed') && (state === 'disconnected')) { - // Do disconnect action, but stay in failed state. - RFB.state = 'failed'; - } else { - RFB.state = state; - } + canvas.changeCursor(RQ.shiftBytes(pixelslength), + RQ.shiftBytes(masklength), + x, y, w, h); - switch (state) { - case 'loaded': - case 'disconnected': + FBU.bytes = 0; + FBU.rects -= 1; - if (RFB.sendID) { - clearInterval(RFB.sendID); - RFB.sendID = null; - } + //Util.Debug("<< set_cursor"); + return true; +}; - if (RFB.ws) { - if (RFB.ws.readyState === WebSocket.OPEN) { - RFB.ws.close(); - } - RFB.ws.onmessage = function (e) { return; }; - } +encHandlers.JPEG_quality_lo = function set_jpeg_quality() { + Util.Error("Server sent jpeg_quality pseudo-encoding"); +}; - if (Canvas.ctx) { - Canvas.stop(); - if (! /__debug__$/i.test(document.location.href)) { - Canvas.clear(); - } - } +encHandlers.compress_lo = function set_compress_level() { + Util.Error("Server sent compress level pseudo-encoding"); +}; - RFB.show_timings(); +/* + * Client message routines + */ - break; +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 - case 'connect': - RFB.init_vars(); + 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 - if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { - RFB.ws.close(); - } - RFB.init_ws(); // onopen transitions to 'ProtocolVersion' + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + //Util.Debug("<< pixelFormat"); + return arr; +}; - break; +clientEncodings = function() { + //Util.Debug(">> clientEncodings"); + var arr, i, encList = []; + for (i=0; i> fbUpdateRequest"); + if (!x) { x = 0; } + if (!y) { y = 0; } + if (!xw) { xw = fb_width; } + if (!yw) { 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; +}; - break; +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; +}; +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; +}; - case 'failed': - if (oldstate === 'disconnected') { - Util.Error("Invalid transition from 'disconnected' to 'failed'"); - } - if (oldstate === 'normal') { - Util.Error("Error while connected."); - } - if (oldstate === 'init') { - Util.Error("Error while initializing."); - } +clientCutText = function(text) { + //Util.Debug(">> 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); + //Util.Debug("<< clientCutText:" + arr); + return arr; +}; - if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { - RFB.ws.close(); - } - // Make sure we transition to disconnected - setTimeout(function() { RFB.updateState('disconnected'); }, 50); - break; +// +// Public API interface functions +// - default: - // Invalid state transition +that.init = function () { - } + init_vars(); - if ((oldstate === 'failed') && (state === 'disconnected')) { - // Leave the failed message - RFB.externalUpdateState(state); + /* Check web-socket-js if no builtin WebSocket support */ + if (VNC_native_ws) { + Util.Info("Using native WebSockets"); + updateState('loaded', 'noVNC ready (using native WebSockets)'); } else { - RFB.externalUpdateState(state, statusMsg); - } -}, - -update_timings: function() { - var now, timing = RFB.timing, offset; - now = (new Date()).getTime(); - timing.history.push([now, - timing.h_fbus, - timing.h_rects, - timing.h_bytes, - timing.h_pixels]); - timing.h_fbus = 0; - timing.h_rects = 0; - timing.h_bytes = 0; - timing.h_pixels = 0; - if ((RFB.state !== 'disconnected') && (RFB.state !== 'failed')) { - // Try for every second - offset = (now - timing.history_start) % 1000; - if (offset < 500) { - setTimeout(RFB.update_timings, 1000 - offset); + Util.Warn("Using web-socket-js flash bridge"); + if ((! Util.Flash) || + (Util.Flash.version < 9)) { + updateState('fatal', "WebSockets or Adobe Flash is required"); + } else if (document.location.href.substr(0, 7) === "file://") { + updateState('fatal', + "'file://' URL is incompatible with Adobe Flash"); } else { - setTimeout(RFB.update_timings, 2000 - offset); + updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)'); } } -}, +}; -show_timings: function() { - var i, timing = RFB.timing, history, msg, - delta, tot_time = 0, tot_fbus = 0, tot_rects = 0, - tot_bytes = 0, tot_pixels = 0; - if (timing.history_start === 0) { return; } - //Util.Debug(">> show_timings"); - RFB.update_timings(); // Final accumulate - msg = "\nTimings\n"; - msg += " time: fbus,rects,bytes,pixels\n"; - for (i=0; i < timing.history.length; i += 1) { - history = timing.history[i]; - delta = ((history[0]-timing.history_start)/1000); - tot_time = delta; - tot_fbus += history[1]; - tot_rects += history[2]; - tot_bytes += history[3]; - tot_pixels += history[4]; +that.connect = function(host, port, password) { + //Util.Debug(">> connect"); - msg += " " + delta.toFixed(3); - msg += ": " + history.slice(1) + "\n"; + // Make sure we have done init checks + if ((rfb_state !== 'loaded') && (rfb_state !== 'fatal')) { + that.init(); } - msg += "\nTotals:\n"; - msg += " time: fbus,rects,bytes,pixels\n"; - msg += " " + tot_time.toFixed(3); - msg += ": " + tot_fbus + "," + tot_rects; - msg += "," + tot_bytes + "," + tot_pixels; - Util.Info(msg); - //Util.Debug("<< show_timings"); -}, - -/* - * Setup routines - */ -init_ws: function () { - //Util.Debug(">> init_ws"); + rfb_host = host; + rfb_port = port; + rfb_password = (password !== undefined) ? password : ""; - var uri = "", vars = []; - if (RFB.encrypt) { - uri = "wss://"; - } else { - uri = "ws://"; - } - uri += RFB.host + ":" + RFB.port + "/"; - if (RFB.b64encode) { - vars.push("b64encode"); - } - if (vars.length > 0) { - uri += "?" + vars.join("&"); + if ((!rfb_host) || (!rfb_port)) { + updateState('failed', "Must set host and port"); + return; } - Util.Info("connecting to " + uri); - RFB.ws = new WebSocket(uri); - RFB.ws.onmessage = RFB.recv_message; - RFB.ws.onopen = function(e) { - Util.Debug(">> WebSocket.onopen"); - if (RFB.state === "connect") { - RFB.updateState('ProtocolVersion', "Starting VNC handshake"); - } else { - RFB.updateState('failed', "Got unexpected WebSockets connection"); - } - Util.Debug("<< WebSocket.onopen"); - }; - RFB.ws.onclose = function(e) { - Util.Debug(">> WebSocket.onclose"); - if (RFB.state === 'normal') { - RFB.updateState('failed', 'Server disconnected'); - } else if (RFB.state === 'ProtocolVersion') { - RFB.updateState('failed', 'Failed to connect to server'); - } else { - RFB.updateState('disconnected', 'VNC disconnected'); - } - Util.Debug("<< WebSocket.onclose"); - }; - RFB.ws.onerror = function(e) { - Util.Debug(">> WebSocket.onerror"); - RFB.updateState('failed', "WebSocket error"); - Util.Debug("<< WebSocket.onerror"); - }; + updateState('connect'); + //Util.Debug("<< connect"); - setTimeout(function () { - if (RFB.ws.readyState === WebSocket.CONNECTING) { - RFB.updateState('failed', "Connect timeout"); - } - }, RFB.connectTimeout); +}; - //Util.Debug("<< init_ws"); -}, +that.disconnect = function() { + //Util.Debug(">> disconnect"); + updateState('disconnected', 'Disconnected'); + //Util.Debug("<< disconnect"); +}; -init_vars: function () { - /* Reset state */ - RFB.cuttext = 'none'; - RFB.ct_length = 0; - RFB.RQ = []; - RFB.SQ = ""; - RFB.FBU.rects = 0; - RFB.FBU.subrects = 0; // RRE and HEXTILE - RFB.FBU.lines = 0; // RAW - RFB.FBU.tiles = 0; // HEXTILE - RFB.FBU.imgs = []; // TIGHT_PNG image queue - RFB.mouse_buttonmask = 0; - RFB.mouse_arr = []; - - RFB.timing.history_start = 0; - RFB.timing.history = []; - RFB.timing.h_fbus = 0; - RFB.timing.h_rects = 0; - RFB.timing.h_bytes = 0; - RFB.timing.h_pixels = 0; -} +that.sendPassword = function(passwd) { + rfb_password = passwd; + rfb_state = "Authentication"; + setTimeout(init_msg, 1); +}; + +that.sendCtrlAltDel = function() { + if (rfb_state !== "normal") { 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(fbUpdateRequest(1)); + send_array(arr); +}; + +that.clipboardPasteFrom = function(text) { + if (rfb_state !== "normal") { return; } + //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); + send_array(clientCutText(text)); + //Util.Debug("<< clipboardPasteFrom"); +}; + +that.testMode = function(override_send_array) { + // Overridable internal functions for testing + test_mode = true; + send_array = override_send_array; + that.recv_message = recv_message; // Expose it + + checkEvents = function () { /* Stub Out */ }; + that.connect = function(host, port, password) { + rfb_host = host; + rfb_port = port; + rfb_password = password; + updateState('ProtocolVersion', "Starting VNC handshake"); + }; +}; + + +return constructor(); // Return the public API interface -}; /* End of RFB */ +} // End of RFB()