Browse Source

Convert to Websock library.

Copy in include/websock.js from websockify and use that instead. Still
some cleanup of network code but it's a good start.
Joel Martin 14 years ago
parent
commit
72f1348b35
3 changed files with 468 additions and 309 deletions
  1. 154 298
      include/rfb.js
  2. 1 11
      include/vnc.js
  3. 313 0
      include/websock.js

+ 154 - 298
include/rfb.js

@@ -17,7 +17,7 @@ conf               = conf || {}; // Configuration
 var that           = {},         // Public API interface
 var that           = {},         // Public API interface
 
 
     // Pre-declare private functions used before definitions (jslint)
     // Pre-declare private functions used before definitions (jslint)
-    init_vars, updateState, init_msg, normal_msg, recv_message,
+    init_vars, updateState, init_msg, normal_msg,
     framebufferUpdate, print_stats,
     framebufferUpdate, print_stats,
 
 
     pixelFormat, clientEncodings, fbUpdateRequest,
     pixelFormat, clientEncodings, fbUpdateRequest,
@@ -25,7 +25,7 @@ var that           = {},         // Public API interface
 
 
     extract_data_uri, scan_tight_imgQ,
     extract_data_uri, scan_tight_imgQ,
 
 
-    send_array, checkEvents,  // Overridable for testing
+    checkEvents,  // Overridable for testing
 
 
 
 
     //
     //
@@ -62,19 +62,13 @@ var that           = {},         // Public API interface
     encNames       = {}, 
     encNames       = {}, 
     encStats       = {},     // [rectCnt, rectCntTot]
     encStats       = {},     // [rectCnt, rectCntTot]
 
 
-    ws             = null,   // Web Socket object
+    ws             = null,   // Websock object
     canvas         = null,   // Canvas object
     canvas         = null,   // Canvas object
     sendTimer      = null,   // Send Queue check timer
     sendTimer      = null,   // Send Queue check timer
     connTimer      = null,   // connection timer
     connTimer      = null,   // connection timer
     disconnTimer   = null,   // disconnection timer
     disconnTimer   = null,   // disconnection timer
     msgTimer       = null,   // queued handle_message timer
     msgTimer       = null,   // queued handle_message timer
 
 
-    // Receive and send queues
-    rQ             = [],     // Receive Queue
-    rQi            = 0,      // Receive Queue Index
-    rQmax          = 100000, // Max size before compacting
-    sQ             = [],     // Send Queue
-
     // Frame buffer update state
     // Frame buffer update state
     FBU            = {
     FBU            = {
         rects          : 0,
         rects          : 0,
@@ -168,59 +162,6 @@ that.get_canvas = function() {
 
 
 
 
 
 
-//
-// Private functions
-//
-
-//
-// Receive Queue functions
-//
-function rQlen() {
-    return rQ.length - rQi;
-}
-
-function rQshift16() {
-    return (rQ[rQi++] <<  8) +
-           (rQ[rQi++]      );
-}
-function rQshift32() {
-    return (rQ[rQi++] << 24) +
-           (rQ[rQi++] << 16) +
-           (rQ[rQi++] <<  8) +
-           (rQ[rQi++]      );
-}
-function rQshiftStr(len) {
-    var arr = rQ.slice(rQi, rQi + len);
-    rQi += len;
-    return arr.map(function (num) {
-            return String.fromCharCode(num); } ).join('');
-
-}
-function rQshiftBytes(len) {
-    rQi += len;
-    return rQ.slice(rQi-len, rQi);
-}
-
-// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
-// to be available in the receive queue. Return true if we need to
-// wait (and possibly print a debug message), otherwise false.
-function rQwait(msg, num, goback) {
-    if (typeof num !== 'number') { num = FBU.bytes; }
-    var rQlen = rQ.length - rQi; // Skip rQlen() function call
-    if (rQlen < num) {
-        if (goback) {
-            if (rQi < goback) {
-                throw("rQwait cannot backup " + goback + " bytes");
-            }
-            rQi -= goback;
-        }
-        //Util.Debug("   waiting for " + (num-rQlen) +
-        //           " " + msg + " byte(s)");
-        return true;  // true means need more data
-    }
-    return false;
-}
-
 
 
 //
 //
 // Setup routines
 // Setup routines
@@ -250,7 +191,7 @@ function constructor() {
     init_vars();
     init_vars();
 
 
     /* Check web-socket-js if no builtin WebSocket support */
     /* Check web-socket-js if no builtin WebSocket support */
-    if (VNC_native_ws) {
+    if (Websock_native) {
         Util.Info("Using native WebSockets");
         Util.Info("Using native WebSockets");
         updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
         updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
     } else {
     } else {
@@ -271,8 +212,8 @@ function constructor() {
     return that;  // Return the public API interface
     return that;  // Return the public API interface
 }
 }
 
 
-function init_ws() {
-    Util.Debug(">> RFB.init_ws");
+function connect() {
+    Util.Debug(">> RFB.connect");
 
 
     var uri = "";
     var uri = "";
     if (conf.encrypt) {
     if (conf.encrypt) {
@@ -282,20 +223,25 @@ function init_ws() {
     }
     }
     uri += rfb_host + ":" + rfb_port + "/";
     uri += rfb_host + ":" + rfb_port + "/";
     Util.Info("connecting to " + uri);
     Util.Info("connecting to " + uri);
-    ws = new WebSocket(uri);
+    ws.open(uri);
 
 
-    ws.onmessage = recv_message;
-    ws.onopen = function(e) {
-        Util.Debug(">> WebSocket.onopen");
+    Util.Debug("<< RFB.connect");
+}
+
+init_vars = function() {
+    /* Reset state */
+    ws = new Websock();
+    ws.init();
+
+    ws.on('message', handle_message);
+    ws.on('open', function() {
         if (rfb_state === "connect") {
         if (rfb_state === "connect") {
             updateState('ProtocolVersion', "Starting VNC handshake");
             updateState('ProtocolVersion', "Starting VNC handshake");
         } else {
         } else {
             fail("Got unexpected WebSockets connection");
             fail("Got unexpected WebSockets connection");
         }
         }
-        Util.Debug("<< WebSocket.onopen");
-    };
-    ws.onclose = function(e) {
-        Util.Debug(">> WebSocket.onclose");
+    });
+    ws.on('close', function() {
         if (rfb_state === 'disconnect') {
         if (rfb_state === 'disconnect') {
             updateState('disconnected', 'VNC disconnected');
             updateState('disconnected', 'VNC disconnected');
         } else if (rfb_state === 'ProtocolVersion') {
         } else if (rfb_state === 'ProtocolVersion') {
@@ -305,22 +251,11 @@ function init_ws() {
         } else  {
         } else  {
             fail('Server disconnected');
             fail('Server disconnected');
         }
         }
-        Util.Debug("<< WebSocket.onclose");
-    };
-    ws.onerror = function(e) {
-        Util.Debug(">> WebSocket.onerror");
-        fail("WebSocket error");
-        Util.Debug("<< WebSocket.onerror");
-    };
+    });
+    ws.on('error', function(e) {
+        fail("WebSock error: " + e);
+    });
 
 
-    Util.Debug("<< RFB.init_ws");
-}
-
-init_vars = function() {
-    /* Reset state */
-    rQ               = [];
-    rQi              = 0;
-    sQ               = [];
     FBU.rects        = 0;
     FBU.rects        = 0;
     FBU.subrects     = 0;  // RRE and HEXTILE
     FBU.subrects     = 0;  // RRE and HEXTILE
     FBU.lines        = 0;  // RAW
     FBU.lines        = 0;  // RAW
@@ -414,14 +349,7 @@ updateState = function(state, statusMsg) {
             }
             }
         }
         }
 
 
-        if (ws) {
-            if ((ws.readyState === WebSocket.OPEN) || 
-               (ws.readyState === WebSocket.CONNECTING)) {
-                Util.Info("Closing WebSocket connection");
-                ws.close();
-            }
-            ws.onmessage = function (e) { return; };
-        }
+        ws.close();
     }
     }
 
 
     if (oldstate === 'fatal') {
     if (oldstate === 'fatal') {
@@ -472,7 +400,7 @@ updateState = function(state, statusMsg) {
             }, conf.connectTimeout * 1000);
             }, conf.connectTimeout * 1000);
 
 
         init_vars();
         init_vars();
-        init_ws();
+        connect();
 
 
         // WebSocket.onopen transitions to 'ProtocolVersion'
         // WebSocket.onopen transitions to 'ProtocolVersion'
         break;
         break;
@@ -526,21 +454,10 @@ function fail(msg) {
     return false;
     return false;
 }
 }
 
 
-function encode_message() {
-    /* base64 encode */
-    return Base64.encode(sQ);
-}
-
-function decode_message(data) {
-    //Util.Debug(">> decode_message: " + data);
-    /* base64 decode */
-    rQ = rQ.concat(Base64.decode(data, 0));
-    //Util.Debug(">> decode_message, rQ: " + rQ);
-}
-
 function handle_message() {
 function handle_message() {
-    //Util.Debug("rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
-    if (rQlen() === 0) {
+    //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");
         Util.Warn("handle_message called on empty receive queue");
         return;
         return;
     }
     }
@@ -550,7 +467,7 @@ function handle_message() {
         Util.Error("Got data while disconnected");
         Util.Error("Got data while disconnected");
         break;
         break;
     case 'normal':
     case 'normal':
-        if (normal_msg() && rQlen() > 0) {
+        if (normal_msg() && ws.rQlen() > 0) {
             // true means we can continue processing
             // true means we can continue processing
             // Give other events a chance to run
             // Give other events a chance to run
             if (msgTimer === null) {
             if (msgTimer === null) {
@@ -563,12 +480,6 @@ function handle_message() {
                 Util.Debug("More data to process, existing timer");
                 Util.Debug("More data to process, existing timer");
             }
             }
         }
         }
-        // Compact the queue
-        if (rQ.length > rQmax) {
-            //Util.Debug("Compacting receive queue");
-            rQ = rQ.slice(rQi);
-            rQi = 0;
-        }
         break;
         break;
     default:
     default:
         init_msg();
         init_msg();
@@ -576,52 +487,6 @@ function handle_message() {
     }
     }
 }
 }
 
 
-recv_message = function(e) {
-    //Util.Debug(">> recv_message: " + e.data.length);
-
-    try {
-        decode_message(e.data);
-        if (rQlen() > 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') {
-            fail(exc.name + ": " + exc.message);
-        } else {
-            fail(exc);
-        }
-    }
-    //Util.Debug("<< recv_message");
-};
-
-// overridable for testing
-send_array = function(arr) {
-    //Util.Debug(">> send_array: " + arr);
-    sQ = sQ.concat(arr);
-    if (ws.bufferedAmount === 0) {
-        //Util.Debug("arr: " + arr);
-        //Util.Debug("sQ: " + sQ);
-        ws.send(encode_message(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) {
 function genDES(password, challenge) {
     var i, passwd = [], des;
     var i, passwd = [], des;
@@ -633,10 +498,10 @@ function genDES(password, challenge) {
 
 
 function flushClient() {
 function flushClient() {
     if (mouse_arr.length > 0) {
     if (mouse_arr.length > 0) {
-        //send_array(mouse_arr.concat(fbUpdateRequest(1)));
-        send_array(mouse_arr);
+        //send(mouse_arr.concat(fbUpdateRequest(1)));
+        ws.send(mouse_arr);
         setTimeout(function() {
         setTimeout(function() {
-                send_array(fbUpdateRequest(1));
+                ws.send(fbUpdateRequest(1));
             }, 50);
             }, 50);
 
 
         mouse_arr = [];
         mouse_arr = [];
@@ -654,7 +519,7 @@ checkEvents = function() {
             now = new Date().getTime();
             now = new Date().getTime();
             if (now > last_req_time + conf.fbu_req_rate) {
             if (now > last_req_time + conf.fbu_req_rate) {
                 last_req_time = now;
                 last_req_time = now;
-                send_array(fbUpdateRequest(1));
+                ws.send(fbUpdateRequest(1));
             }
             }
         }
         }
     }
     }
@@ -665,7 +530,7 @@ function keyPress(keysym, down) {
     var arr;
     var arr;
     arr = keyEvent(keysym, down);
     arr = keyEvent(keysym, down);
     arr = arr.concat(fbUpdateRequest(1));
     arr = arr.concat(fbUpdateRequest(1));
-    send_array(arr);
+    ws.send(arr);
 }
 }
 
 
 function mouseButton(x, y, down, bmask) {
 function mouseButton(x, y, down, bmask) {
@@ -696,14 +561,14 @@ init_msg = function() {
         i, types, num_types, challenge, response, bpp, depth,
         i, types, num_types, challenge, response, bpp, depth,
         big_endian, true_color, name_length;
         big_endian, true_color, name_length;
 
 
-    //Util.Debug("rQ (" + rQlen() + ") " + rQ);
+    //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
     switch (rfb_state) {
     switch (rfb_state) {
 
 
     case 'ProtocolVersion' :
     case 'ProtocolVersion' :
-        if (rQlen() < 12) {
+        if (ws.rQlen() < 12) {
             return fail("Incomplete protocol version");
             return fail("Incomplete protocol version");
         }
         }
-        sversion = rQshiftStr(12).substr(4,7);
+        sversion = ws.rQshiftStr(12).substr(4,7);
         Util.Info("Server ProtocolVersion: " + sversion);
         Util.Info("Server ProtocolVersion: " + sversion);
         switch (sversion) {
         switch (sversion) {
             case "003.003": rfb_version = 3.3; break;
             case "003.003": rfb_version = 3.3; break;
@@ -722,35 +587,28 @@ init_msg = function() {
                     // Send updates either at a rate of one update
                     // Send updates either at a rate of one update
                     // every 50ms, or whatever slower rate the network
                     // every 50ms, or whatever slower rate the network
                     // can handle.
                     // can handle.
-                    if (ws.bufferedAmount === 0) {
-                        if (sQ) {
-                            ws.send(encode_message(sQ));
-                            sQ = [];
-                        }
-                    } else {
-                        Util.Debug("Delaying send");
-                    }
+                    ws.flush();
                 }, 50);
                 }, 50);
         }
         }
 
 
         cversion = "00" + parseInt(rfb_version,10) +
         cversion = "00" + parseInt(rfb_version,10) +
                    ".00" + ((rfb_version * 10) % 10);
                    ".00" + ((rfb_version * 10) % 10);
-        send_string("RFB " + cversion + "\n");
+        ws.send_string("RFB " + cversion + "\n");
         updateState('Security', "Sent ProtocolVersion: " + cversion);
         updateState('Security', "Sent ProtocolVersion: " + cversion);
         break;
         break;
 
 
     case 'Security' :
     case 'Security' :
         if (rfb_version >= 3.7) {
         if (rfb_version >= 3.7) {
             // Server sends supported list, client decides 
             // Server sends supported list, client decides 
-            num_types = rQ[rQi++];
-            if (rQwait("security type", num_types, 1)) { return false; }
+            num_types = ws.rQshift8();
+            if (ws.rQwait("security type", num_types, 1)) { return false; }
             if (num_types === 0) {
             if (num_types === 0) {
-                strlen = rQshift32();
-                reason = rQshiftStr(strlen);
+                strlen = ws.rQshift32();
+                reason = ws.rQshiftStr(strlen);
                 return fail("Security failure: " + reason);
                 return fail("Security failure: " + reason);
             }
             }
             rfb_auth_scheme = 0;
             rfb_auth_scheme = 0;
-            types = rQshiftBytes(num_types);
+            types = ws.rQshiftBytes(num_types);
             Util.Debug("Server security types: " + types);
             Util.Debug("Server security types: " + types);
             for (i=0; i < types.length; i+=1) {
             for (i=0; i < types.length; i+=1) {
                 if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
                 if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
@@ -761,11 +619,11 @@ init_msg = function() {
                 return fail("Unsupported security types: " + types);
                 return fail("Unsupported security types: " + types);
             }
             }
             
             
-            send_array([rfb_auth_scheme]);
+            ws.send([rfb_auth_scheme]);
         } else {
         } else {
             // Server decides
             // Server decides
-            if (rQwait("security scheme", 4)) { return false; }
-            rfb_auth_scheme = rQshift32();
+            if (ws.rQwait("security scheme", 4)) { return false; }
+            rfb_auth_scheme = ws.rQshift32();
         }
         }
         updateState('Authentication',
         updateState('Authentication',
                 "Authenticating using scheme: " + rfb_auth_scheme);
                 "Authenticating using scheme: " + rfb_auth_scheme);
@@ -777,9 +635,9 @@ init_msg = function() {
         //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
         //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
         switch (rfb_auth_scheme) {
         switch (rfb_auth_scheme) {
             case 0:  // connection failed
             case 0:  // connection failed
-                if (rQwait("auth reason", 4)) { return false; }
-                strlen = rQshift32();
-                reason = rQshiftStr(strlen);
+                if (ws.rQwait("auth reason", 4)) { return false; }
+                strlen = ws.rQshift32();
+                reason = ws.rQshiftStr(strlen);
                 return fail("Auth failure: " + reason);
                 return fail("Auth failure: " + reason);
             case 1:  // no authentication
             case 1:  // no authentication
                 if (rfb_version >= 3.8) {
                 if (rfb_version >= 3.8) {
@@ -794,8 +652,8 @@ init_msg = function() {
                     updateState('password', "Password Required");
                     updateState('password', "Password Required");
                     return;
                     return;
                 }
                 }
-                if (rQwait("auth challenge", 16)) { return false; }
-                challenge = rQshiftBytes(16);
+                if (ws.rQwait("auth challenge", 16)) { return false; }
+                challenge = ws.rQshiftBytes(16);
                 //Util.Debug("Password: " + rfb_password);
                 //Util.Debug("Password: " + rfb_password);
                 //Util.Debug("Challenge: " + challenge +
                 //Util.Debug("Challenge: " + challenge +
                 //           " (" + challenge.length + ")");
                 //           " (" + challenge.length + ")");
@@ -804,7 +662,7 @@ init_msg = function() {
                 //           " (" + response.length + ")");
                 //           " (" + response.length + ")");
                 
                 
                 //Util.Debug("Sending DES encrypted auth response");
                 //Util.Debug("Sending DES encrypted auth response");
-                send_array(response);
+                ws.send(response);
                 updateState('SecurityResult');
                 updateState('SecurityResult');
                 return;
                 return;
             default:
             default:
@@ -816,18 +674,18 @@ init_msg = function() {
         break;
         break;
 
 
     case 'SecurityResult' :
     case 'SecurityResult' :
-        if (rQwait("VNC auth response ", 4)) { return false; }
-        switch (rQshift32()) {
+        if (ws.rQwait("VNC auth response ", 4)) { return false; }
+        switch (ws.rQshift32()) {
             case 0:  // OK
             case 0:  // OK
                 // Fall through to ClientInitialisation
                 // Fall through to ClientInitialisation
                 break;
                 break;
             case 1:  // failed
             case 1:  // failed
                 if (rfb_version >= 3.8) {
                 if (rfb_version >= 3.8) {
-                    length = rQshift32();
-                    if (rQwait("SecurityResult reason", length, 8)) {
+                    length = ws.rQshift32();
+                    if (ws.rQwait("SecurityResult reason", length, 8)) {
                         return false;
                         return false;
                     }
                     }
-                    reason = rQshiftStr(length);
+                    reason = ws.rQshiftStr(length);
                     fail(reason);
                     fail(reason);
                 } else {
                 } else {
                     fail("Authentication failed");
                     fail("Authentication failed");
@@ -842,22 +700,22 @@ init_msg = function() {
 
 
     // Triggered by fallthough, not by server message
     // Triggered by fallthough, not by server message
     case 'ClientInitialisation' :
     case 'ClientInitialisation' :
-        send_array([conf.shared ? 1 : 0]); // ClientInitialisation
+        ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
         updateState('ServerInitialisation', "Authentication OK");
         updateState('ServerInitialisation', "Authentication OK");
         break;
         break;
 
 
     case 'ServerInitialisation' :
     case 'ServerInitialisation' :
-        if (rQwait("server initialization", 24)) { return false; }
+        if (ws.rQwait("server initialization", 24)) { return false; }
 
 
         /* Screen size */
         /* Screen size */
-        fb_width  = rQshift16();
-        fb_height = rQshift16();
+        fb_width  = ws.rQshift16();
+        fb_height = ws.rQshift16();
 
 
         /* PIXEL_FORMAT */
         /* PIXEL_FORMAT */
-        bpp            = rQ[rQi++];
-        depth          = rQ[rQi++];
-        big_endian     = rQ[rQi++];
-        true_color     = rQ[rQi++];
+        bpp            = ws.rQshift8();
+        depth          = ws.rQshift8();
+        big_endian     = ws.rQshift8();
+        true_color     = ws.rQshift8();
 
 
         Util.Info("Screen: " + fb_width + "x" + fb_height + 
         Util.Info("Screen: " + fb_width + "x" + fb_height + 
                   ", bpp: " + bpp + ", depth: " + depth +
                   ", bpp: " + bpp + ", depth: " + depth +
@@ -865,9 +723,9 @@ init_msg = function() {
                   ", true_color: " + true_color);
                   ", true_color: " + true_color);
 
 
         /* Connection name/title */
         /* Connection name/title */
-        rQshiftStr(12);
-        name_length   = rQshift32();
-        fb_name = rQshiftStr(name_length);
+        ws.rQshiftStr(12);
+        name_length   = ws.rQshift32();
+        fb_name = ws.rQshiftStr(name_length);
 
 
         canvas.resize(fb_width, fb_height, conf.true_color);
         canvas.resize(fb_width, fb_height, conf.true_color);
         canvas.start(keyPress, mouseButton, mouseMove);
         canvas.start(keyPress, mouseButton, mouseMove);
@@ -884,7 +742,7 @@ init_msg = function() {
         response = response.concat(clientEncodings());
         response = response.concat(clientEncodings());
         response = response.concat(fbUpdateRequest(0));
         response = response.concat(fbUpdateRequest(0));
         timing.fbu_rt_start = (new Date()).getTime();
         timing.fbu_rt_start = (new Date()).getTime();
-        send_array(response);
+        ws.send(response);
         
         
         /* Start pushing/polling */
         /* Start pushing/polling */
         setTimeout(checkEvents, conf.check_rate);
         setTimeout(checkEvents, conf.check_rate);
@@ -911,7 +769,7 @@ normal_msg = function() {
     if (FBU.rects > 0) {
     if (FBU.rects > 0) {
         msg_type = 0;
         msg_type = 0;
     } else {
     } else {
-        msg_type = rQ[rQi++];
+        msg_type = ws.rQshift8();
     }
     }
     switch (msg_type) {
     switch (msg_type) {
     case 0:  // FramebufferUpdate
     case 0:  // FramebufferUpdate
@@ -919,16 +777,16 @@ normal_msg = function() {
         break;
         break;
     case 1:  // SetColourMapEntries
     case 1:  // SetColourMapEntries
         Util.Debug("SetColourMapEntries");
         Util.Debug("SetColourMapEntries");
-        rQi++;  // Padding
-        first_colour = rQshift16(); // First colour
-        num_colours = rQshift16();
+        ws.rQshift8();  // Padding
+        first_colour = ws.rQshift16(); // First colour
+        num_colours = ws.rQshift16();
         for (c=0; c < num_colours; c+=1) { 
         for (c=0; c < num_colours; c+=1) { 
-            red = rQshift16();
+            red = ws.rQshift16();
             //Util.Debug("red before: " + red);
             //Util.Debug("red before: " + red);
             red = parseInt(red / 256, 10);
             red = parseInt(red / 256, 10);
             //Util.Debug("red after: " + red);
             //Util.Debug("red after: " + red);
-            green = parseInt(rQshift16() / 256, 10);
-            blue = parseInt(rQshift16() / 256, 10);
+            green = parseInt(ws.rQshift16() / 256, 10);
+            blue = parseInt(ws.rQshift16() / 256, 10);
             canvas.set_colourMap([red, green, blue], first_colour + c);
             canvas.set_colourMap([red, green, blue], first_colour + c);
         }
         }
         Util.Info("Registered " + num_colours + " colourMap entries");
         Util.Info("Registered " + num_colours + " colourMap entries");
@@ -939,16 +797,16 @@ normal_msg = function() {
         break;
         break;
     case 3:  // ServerCutText
     case 3:  // ServerCutText
         Util.Debug("ServerCutText");
         Util.Debug("ServerCutText");
-        if (rQwait("ServerCutText header", 7, 1)) { return false; }
-        rQshiftBytes(3);  // Padding
-        length = rQshift32();
-        if (rQwait("ServerCutText", length, 8)) { return false; }
+        if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
+        ws.rQshiftBytes(3);  // Padding
+        length = ws.rQshift32();
+        if (ws.rQwait("ServerCutText", length, 8)) { return false; }
 
 
-        conf.clipboardReceive(that, rQshiftStr(length));
+        conf.clipboardReceive(that, ws.rQshiftStr(length));
         break;
         break;
     default:
     default:
         fail("Disconnected: illegal server message type " + msg_type);
         fail("Disconnected: illegal server message type " + msg_type);
-        Util.Debug("rQ.slice(0,30):" + rQ.slice(0,30));
+        Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
         break;
         break;
     }
     }
     //Util.Debug("<< normal_msg");
     //Util.Debug("<< normal_msg");
@@ -959,17 +817,13 @@ framebufferUpdate = function() {
     var now, hdr, fbu_rt_diff, ret = true;
     var now, hdr, fbu_rt_diff, ret = true;
 
 
     if (FBU.rects === 0) {
     if (FBU.rects === 0) {
-        //Util.Debug("New FBU: rQ.slice(0,20): " + rQ.slice(0,20));
-        if (rQwait("FBU header", 3)) {
-            if (rQi === 0) {
-                rQ.unshift(0);  // FBU msg_type
-            } else {
-                rQi -= 1;
-            }
+        //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;
             return false;
         }
         }
-        rQi++;
-        FBU.rects = rQshift16();
+        ws.rQshift8();  // padding
+        FBU.rects = ws.rQshift16();
         //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
         //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
         FBU.bytes = 0;
         FBU.bytes = 0;
         timing.cur_fbu = 0;
         timing.cur_fbu = 0;
@@ -983,12 +837,12 @@ framebufferUpdate = function() {
         if (rfb_state !== "normal") {
         if (rfb_state !== "normal") {
             return false;
             return false;
         }
         }
-        if (rQwait("FBU")) { return false; }
+        if (ws.rQwait("FBU", FBU.bytes)) { return false; }
         if (FBU.bytes === 0) {
         if (FBU.bytes === 0) {
-            if (rQwait("rect header", 12)) { return false; }
+            if (ws.rQwait("rect header", 12)) { return false; }
             /* New FramebufferUpdate */
             /* New FramebufferUpdate */
 
 
-            hdr = rQshiftBytes(12);
+            hdr = ws.rQshiftBytes(12);
             FBU.x      = (hdr[0] << 8) + hdr[1];
             FBU.x      = (hdr[0] << 8) + hdr[1];
             FBU.y      = (hdr[2] << 8) + hdr[3];
             FBU.y      = (hdr[2] << 8) + hdr[3];
             FBU.width  = (hdr[4] << 8) + hdr[5];
             FBU.width  = (hdr[4] << 8) + hdr[5];
@@ -1004,7 +858,7 @@ framebufferUpdate = function() {
                 msg += " width: " + FBU.width + " height: " + FBU.height;
                 msg += " width: " + FBU.width + " height: " + FBU.height;
                 msg += " encoding:" + FBU.encoding;
                 msg += " encoding:" + FBU.encoding;
                 msg += "(" + encNames[FBU.encoding] + ")";
                 msg += "(" + encNames[FBU.encoding] + ")";
-                msg += ", rQlen(): " + rQlen();
+                msg += ", ws.rQlen(): " + ws.rQlen();
                 Util.Debug(msg);
                 Util.Debug(msg);
                 */
                 */
             } else {
             } else {
@@ -1064,20 +918,21 @@ framebufferUpdate = function() {
 //
 //
 
 
 encHandlers.RAW = function display_raw() {
 encHandlers.RAW = function display_raw() {
-    //Util.Debug(">> display_raw (" + rQlen() + " bytes)");
+    //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
 
 
-    var cur_y, cur_height; 
+    var cur_y, cur_height;
 
 
     if (FBU.lines === 0) {
     if (FBU.lines === 0) {
         FBU.lines = FBU.height;
         FBU.lines = FBU.height;
     }
     }
     FBU.bytes = FBU.width * fb_Bpp; // At least a line
     FBU.bytes = FBU.width * fb_Bpp; // At least a line
-    if (rQwait("RAW")) { return false; }
+    if (ws.rQwait("RAW", FBU.bytes)) { return false; }
     cur_y = FBU.y + (FBU.height - FBU.lines);
     cur_y = FBU.y + (FBU.height - FBU.lines);
     cur_height = Math.min(FBU.lines,
     cur_height = Math.min(FBU.lines,
-                          Math.floor(rQlen()/(FBU.width * fb_Bpp)));
-    canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, rQ, rQi);
-    rQshiftBytes(FBU.width * cur_height * fb_Bpp);
+                          Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
+    canvas.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;
     FBU.lines -= cur_height;
 
 
     if (FBU.lines > 0) {
     if (FBU.lines > 0) {
@@ -1086,7 +941,7 @@ encHandlers.RAW = function display_raw() {
         FBU.rects -= 1;
         FBU.rects -= 1;
         FBU.bytes = 0;
         FBU.bytes = 0;
     }
     }
-    //Util.Debug("<< display_raw (" + rQlen() + " bytes)");
+    //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
     return true;
     return true;
 };
 };
 
 
@@ -1095,9 +950,9 @@ encHandlers.COPYRECT = function display_copy_rect() {
 
 
     var old_x, old_y;
     var old_x, old_y;
 
 
-    if (rQwait("COPYRECT", 4)) { return false; }
-    old_x = rQshift16();
-    old_y = rQshift16();
+    if (ws.rQwait("COPYRECT", 4)) { return false; }
+    old_x = ws.rQshift16();
+    old_y = ws.rQshift16();
     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.rects -= 1;
     FBU.bytes = 0;
     FBU.bytes = 0;
@@ -1105,21 +960,21 @@ encHandlers.COPYRECT = function display_copy_rect() {
 };
 };
 
 
 encHandlers.RRE = function display_rre() {
 encHandlers.RRE = function display_rre() {
-    //Util.Debug(">> display_rre (" + rQlen() + " bytes)");
+    //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
     var color, x, y, width, height, chunk;
     var color, x, y, width, height, chunk;
 
 
     if (FBU.subrects === 0) {
     if (FBU.subrects === 0) {
-        if (rQwait("RRE", 4+fb_Bpp)) { return false; }
-        FBU.subrects = rQshift32();
-        color = rQshiftBytes(fb_Bpp); // Background
+        if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
+        FBU.subrects = ws.rQshift32();
+        color = ws.rQshiftBytes(fb_Bpp); // Background
         canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
         canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
     }
     }
-    while ((FBU.subrects > 0) && (rQlen() >= (fb_Bpp + 8))) {
-        color = rQshiftBytes(fb_Bpp);
-        x = rQshift16();
-        y = rQshift16();
-        width = rQshift16();
-        height = rQshift16();
+    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();
         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;
         FBU.subrects -= 1;
     }
     }
@@ -1140,7 +995,8 @@ encHandlers.RRE = function display_rre() {
 encHandlers.HEXTILE = function display_hextile() {
 encHandlers.HEXTILE = function display_hextile() {
     //Util.Debug(">> display_hextile");
     //Util.Debug(">> display_hextile");
     var subencoding, subrects, tile, color, cur_tile,
     var subencoding, subrects, tile, color, cur_tile,
-        tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh;
+        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) {
     if (FBU.tiles === 0) {
         FBU.tiles_x = Math.ceil(FBU.width/16);
         FBU.tiles_x = Math.ceil(FBU.width/16);
@@ -1149,15 +1005,14 @@ encHandlers.HEXTILE = function display_hextile() {
         FBU.tiles = FBU.total_tiles;
         FBU.tiles = FBU.total_tiles;
     }
     }
 
 
-    /* FBU.bytes comes in as 1, rQlen() at least 1 */
+    /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
     while (FBU.tiles > 0) {
     while (FBU.tiles > 0) {
         FBU.bytes = 1;
         FBU.bytes = 1;
-        if (rQwait("HEXTILE subencoding")) { return false; }
-        //Util.Debug("   2 rQ length: " + rQlen() + " rQ[rQi]: " + rQ[rQi] + " rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + ", FBU.rects: " + FBU.rects + ", FBU.tiles: " + FBU.tiles);
+        if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
         subencoding = rQ[rQi];  // Peek
         subencoding = rQ[rQi];  // Peek
         if (subencoding > 30) { // Raw
         if (subencoding > 30) { // Raw
             fail("Disconnected: illegal hextile subencoding " + subencoding);
             fail("Disconnected: illegal hextile subencoding " + subencoding);
-            //Util.Debug("rQ.slice(0,30):" + rQ.slice(0,30));
+            //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
             return false;
             return false;
         }
         }
         subrects = 0;
         subrects = 0;
@@ -1182,7 +1037,7 @@ encHandlers.HEXTILE = function display_hextile() {
             }
             }
             if (subencoding & 0x08) { // AnySubrects
             if (subencoding & 0x08) { // AnySubrects
                 FBU.bytes += 1;   // Since we aren't shifting it off
                 FBU.bytes += 1;   // Since we aren't shifting it off
-                if (rQwait("hextile subrects header")) { return false; }
+                if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
                 subrects = rQ[rQi + FBU.bytes-1]; // Peek
                 subrects = rQ[rQi + FBU.bytes-1]; // Peek
                 if (subencoding & 0x10) { // SubrectsColoured
                 if (subencoding & 0x10) { // SubrectsColoured
                     FBU.bytes += subrects * (fb_Bpp + 2);
                     FBU.bytes += subrects * (fb_Bpp + 2);
@@ -1199,11 +1054,11 @@ encHandlers.HEXTILE = function display_hextile() {
               ", subenc:" + subencoding +
               ", subenc:" + subencoding +
               "(last: " + FBU.lastsubencoding + "), subrects:" +
               "(last: " + FBU.lastsubencoding + "), subrects:" +
               subrects +
               subrects +
-              ", rQlen():" + rQlen() + ", FBU.bytes:" + FBU.bytes +
-              " last:" + rQ.slice(FBU.bytes-10, FBU.bytes) +
-              " next:" + rQ.slice(FBU.bytes-1, FBU.bytes+10));
+              ", 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 (rQwait("hextile")) { return false; }
+        if (ws.rQwait("hextile", FBU.bytes)) { return false; }
 
 
         /* We know the encoding and have a whole tile */
         /* We know the encoding and have a whole tile */
         FBU.subencoding = rQ[rQi];
         FBU.subencoding = rQ[rQi];
@@ -1254,7 +1109,7 @@ encHandlers.HEXTILE = function display_hextile() {
             }
             }
             canvas.putTile(tile);
             canvas.putTile(tile);
         }
         }
-        //rQshiftBytes(FBU.bytes);
+        ws.set_rQi(rQi);
         FBU.lastsubencoding = FBU.subencoding;
         FBU.lastsubencoding = FBU.subencoding;
         FBU.bytes = 0;
         FBU.bytes = 0;
         FBU.tiles -= 1;
         FBU.tiles -= 1;
@@ -1273,27 +1128,27 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
     //Util.Debug(">> display_tight_png");
     //Util.Debug(">> display_tight_png");
     var ctl, cmode, clength, getCLength, color, img;
     var ctl, cmode, clength, getCLength, color, img;
     //Util.Debug("   FBU.rects: " + FBU.rects);
     //Util.Debug("   FBU.rects: " + FBU.rects);
-    //Util.Debug("   starting rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
+    //Util.Debug("   starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
 
 
     FBU.bytes = 1; // compression-control byte
     FBU.bytes = 1; // compression-control byte
-    if (rQwait("TIGHT compression-control")) { return false; }
+    if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
 
 
     // Get 'compact length' header and data size
     // Get 'compact length' header and data size
-    getCLength = function (arr, offset) {
+    getCLength = function (arr) {
         var header = 1, data = 0;
         var header = 1, data = 0;
-        data += arr[offset + 0] & 0x7f;
-        if (arr[offset + 0] & 0x80) {
+        data += arr[0] & 0x7f;
+        if (arr[0] & 0x80) {
             header += 1;
             header += 1;
-            data += (arr[offset + 1] & 0x7f) << 7;
-            if (arr[offset + 1] & 0x80) {
+            data += (arr[1] & 0x7f) << 7;
+            if (arr[1] & 0x80) {
                 header += 1;
                 header += 1;
-                data += arr[offset + 2] << 14;
+                data += arr[2] << 14;
             }
             }
         }
         }
         return [header, data];
         return [header, data];
     };
     };
 
 
-    ctl = rQ[rQi];
+    ctl = ws.rQpeek8();
     switch (ctl >> 4) {
     switch (ctl >> 4) {
         case 0x08: cmode = "fill"; break;
         case 0x08: cmode = "fill"; break;
         case 0x09: cmode = "jpeg"; break;
         case 0x09: cmode = "jpeg"; break;
@@ -1307,38 +1162,38 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
         case "png":  FBU.bytes += 3;            break; // max clength
         case "png":  FBU.bytes += 3;            break; // max clength
     }
     }
 
 
-    if (rQwait("TIGHT " + cmode)) { return false; }
+    if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
 
 
-    //Util.Debug("   rQ.slice(0,20): " + rQ.slice(0,20) + " (" + rQlen() + ")");
+    //Util.Debug("   ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
     //Util.Debug("   cmode: " + cmode);
     //Util.Debug("   cmode: " + cmode);
 
 
     // Determine FBU.bytes
     // Determine FBU.bytes
     switch (cmode) {
     switch (cmode) {
     case "fill":
     case "fill":
-        rQi++; // shift off ctl
-        color = rQshiftBytes(fb_depth);
+        ws.rQshift8(); // shift off ctl
+        color = ws.rQshiftBytes(fb_depth);
         canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
         canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
         break;
         break;
     case "jpeg":
     case "jpeg":
     case "png":
     case "png":
-        clength = getCLength(rQ, rQi+1);
+        clength = getCLength(ws.rQslice(1, 4));
         FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
         FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
-        if (rQwait("TIGHT " + cmode)) { return false; }
+        if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
 
 
         // We have everything, render it
         // We have everything, render it
-        //Util.Debug("   png, rQlen(): " + rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
-        rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
+        //Util.Debug("   png, 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 = new Image();
         img.onload = scan_tight_imgQ;
         img.onload = scan_tight_imgQ;
         FBU.imgQ.push([img, FBU.x, FBU.y]);
         FBU.imgQ.push([img, FBU.x, FBU.y]);
         img.src = "data:image/" + cmode +
         img.src = "data:image/" + cmode +
-            extract_data_uri(rQshiftBytes(clength[1]));
+            extract_data_uri(ws.rQshiftBytes(clength[1]));
         img = null;
         img = null;
         break;
         break;
     }
     }
     FBU.bytes = 0;
     FBU.bytes = 0;
     FBU.rects -= 1;
     FBU.rects -= 1;
-    //Util.Debug("   ending rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
+    //Util.Debug("   ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
     //Util.Debug("<< display_tight_png");
     //Util.Debug("<< display_tight_png");
     return true;
     return true;
 };
 };
@@ -1373,7 +1228,7 @@ encHandlers.DesktopSize = function set_desktopsize() {
     canvas.resize(fb_width, fb_height);
     canvas.resize(fb_width, fb_height);
     timing.fbu_rt_start = (new Date()).getTime();
     timing.fbu_rt_start = (new Date()).getTime();
     // Send a new non-incremental request
     // Send a new non-incremental request
-    send_array(fbUpdateRequest(0));
+    ws.send(fbUpdateRequest(0));
 
 
     FBU.bytes = 0;
     FBU.bytes = 0;
     FBU.rects -= 1;
     FBU.rects -= 1;
@@ -1394,12 +1249,12 @@ encHandlers.Cursor = function set_cursor() {
     masklength = Math.floor((w + 7) / 8) * h;
     masklength = Math.floor((w + 7) / 8) * h;
 
 
     FBU.bytes = pixelslength + masklength;
     FBU.bytes = pixelslength + masklength;
-    if (rQwait("cursor encoding")) { return false; }
+    if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
 
 
     //Util.Debug("   set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
     //Util.Debug("   set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
 
 
-    canvas.changeCursor(rQshiftBytes(pixelslength),
-                            rQshiftBytes(masklength),
+    canvas.changeCursor(ws.rQshiftBytes(pixelslength),
+                            ws.rQshiftBytes(masklength),
                             x, y, w, h);
                             x, y, w, h);
 
 
     FBU.bytes = 0;
     FBU.bytes = 0;
@@ -1574,7 +1429,7 @@ that.sendCtrlAltDel = function() {
     arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
     arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
     arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
     arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
     arr = arr.concat(fbUpdateRequest(1));
     arr = arr.concat(fbUpdateRequest(1));
-    send_array(arr);
+    ws.send(arr);
 };
 };
 
 
 // Send a key press. If 'down' is not specified then send a down key
 // Send a key press. If 'down' is not specified then send a down key
@@ -1591,21 +1446,22 @@ that.sendKey = function(code, down) {
         arr = arr.concat(keyEvent(code, 0));
         arr = arr.concat(keyEvent(code, 0));
     }
     }
     arr = arr.concat(fbUpdateRequest(1));
     arr = arr.concat(fbUpdateRequest(1));
-    send_array(arr);
+    ws.send(arr);
 };
 };
 
 
 that.clipboardPasteFrom = function(text) {
 that.clipboardPasteFrom = function(text) {
     if (rfb_state !== "normal") { return; }
     if (rfb_state !== "normal") { return; }
     //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
     //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
-    send_array(clientCutText(text));
+    ws.send(clientCutText(text));
     //Util.Debug("<< clipboardPasteFrom");
     //Util.Debug("<< clipboardPasteFrom");
 };
 };
 
 
-that.testMode = function(override_send_array) {
+that.testMode = function(override_send) {
     // Overridable internal functions for testing
     // Overridable internal functions for testing
     test_mode = true;
     test_mode = true;
-    send_array = override_send_array;
-    that.recv_message = recv_message;  // Expose it
+    // TODO figure out what to do here
+    ws.send = override_send;
+    that.recv_message = ws.recv_message;  // Expose it
 
 
     checkEvents = function () { /* Stub Out */ };
     checkEvents = function () { /* Stub Out */ };
     that.connect = function(host, port, password) {
     that.connect = function(host, port, password) {

+ 1 - 11
include/vnc.js

@@ -33,21 +33,11 @@ function get_VNC_uri_prefix() {
     extra += start + "util.js" + end;
     extra += start + "util.js" + end;
     extra += start + "webutil.js" + end;
     extra += start + "webutil.js" + end;
     extra += start + "base64.js" + end;
     extra += start + "base64.js" + end;
+    extra += start + "websock.js" + end;
     extra += start + "des.js" + end;
     extra += start + "des.js" + end;
     extra += start + "canvas.js" + end;
     extra += start + "canvas.js" + end;
     extra += start + "rfb.js" + end;
     extra += start + "rfb.js" + end;
 
 
-    /* If no builtin websockets then load web_socket.js */
-    if (window.WebSocket) {
-        VNC_native_ws = true;
-    } else {
-        VNC_native_ws = false;
-        WEB_SOCKET_SWF_LOCATION = get_VNC_uri_prefix() +
-                    "web-socket-js/WebSocketMain.swf";
-        extra += start + "web-socket-js/swfobject.js" + end;
-        extra += start + "web-socket-js/FABridge.js" + end;
-        extra += start + "web-socket-js/web_socket.js" + end;
-    }
     document.write(extra);
     document.write(extra);
 }());
 }());
 
 

+ 313 - 0
include/websock.js

@@ -0,0 +1,313 @@
+/*
+ * Websock: high-performance binary WebSockets
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * Websock is similar to the standard WebSocket object but Websock
+ * enables communication with raw TCP sockets (i.e. the binary stream)
+ * via websockify. This is accomplished by base64 encoding the data
+ * stream between Websock and websockify.
+ *
+ * Websock has built-in receive queue buffering; the message event
+ * does not contain actual data but is simply a notification that
+ * there is new data available. Several rQ* methods are available to
+ * read binary data off of the receive queue.
+ */
+
+
+// Load Flash WebSocket emulator if needed
+
+if (window.WebSocket) {
+    Websock_native = true;
+} else {
+    /* no builtin WebSocket so load web_socket.js */
+    Websock_native = false;
+    (function () {
+        function get_INCLUDE_URI() {
+            return (typeof INCLUDE_URI !== "undefined") ?
+                INCLUDE_URI : "include/";
+        }
+
+        var start = "<script src='" + get_INCLUDE_URI(),
+            end = "'><\/script>", extra = "";
+
+        WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+                    "web-socket-js/WebSocketMain.swf";
+        extra += start + "web-socket-js/swfobject.js" + end;
+        extra += start + "web-socket-js/FABridge.js" + end;
+        extra += start + "web-socket-js/web_socket.js" + end;
+        document.write(extra);
+    }());
+}
+
+
+function Websock() {
+
+var api = {},         // Public API
+    websocket = null, // WebSocket object
+    rQ = [],          // Receive queue
+    rQi = 0,          // Receive queue index
+    rQmax = 10000,    // Max receive queue size before compacting
+    sQ = [],          // Send queue
+
+    eventHandlers = {
+        'message' : function() {},
+        'open'    : function() {},
+        'close'   : function() {},
+        'error'   : function() {}
+    };
+
+
+//
+// Queue public functions
+//
+
+function get_sQ() {
+    return sQ;
+}
+
+function get_rQ() {
+    return rQ;
+}
+function get_rQi() {
+    return rQi;
+}
+set_rQi = function(val) {
+    rQi = val;
+}
+
+function rQlen() {
+    return rQ.length - rQi;
+}
+
+function rQpeek8() {
+    return (rQ[rQi]      );
+}
+function rQshift8() {
+    return (rQ[rQi++]      );
+}
+function rQunshift8(num) {
+    if (rQi === 0) {
+        rQ.unshift(num);
+    } else {
+        rQi -= 1;
+        rQ[rQi] = num;
+    }
+
+}
+function rQshift16() {
+    return (rQ[rQi++] <<  8) +
+           (rQ[rQi++]      );
+}
+function rQshift32() {
+    return (rQ[rQi++] << 24) +
+           (rQ[rQi++] << 16) +
+           (rQ[rQi++] <<  8) +
+           (rQ[rQi++]      );
+}
+function rQshiftStr(len) {
+    var arr = rQ.slice(rQi, rQi + len);
+    rQi += len;
+    return arr.map(function (num) {
+            return String.fromCharCode(num); } ).join('');
+
+}
+function rQshiftBytes(len) {
+    rQi += len;
+    return rQ.slice(rQi-len, rQi);
+}
+
+function rQslice(start, end) {
+    if (end) {
+        return rQ.slice(rQi + start, rQi + end);
+    } else {
+        return rQ.slice(rQi + start);
+    }
+}
+
+// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
+// to be available in the receive queue. Return true if we need to
+// wait (and possibly print a debug message), otherwise false.
+function rQwait(msg, num, goback) {
+    var rQlen = rQ.length - rQi; // Skip rQlen() function call
+    if (rQlen < num) {
+        if (goback) {
+            if (rQi < goback) {
+                throw("rQwait cannot backup " + goback + " bytes");
+            }
+            rQi -= goback;
+        }
+        //Util.Debug("   waiting for " + (num-rQlen) +
+        //           " " + msg + " byte(s)");
+        return true;  // true means need more data
+    }
+    return false;
+}
+
+//
+// Private utility routines
+//
+
+function encode_message() {
+    /* base64 encode */
+    return Base64.encode(sQ);
+}
+
+function decode_message(data) {
+    //Util.Debug(">> decode_message: " + data);
+    /* base64 decode */
+    rQ = rQ.concat(Base64.decode(data, 0));
+    //Util.Debug(">> decode_message, rQ: " + rQ);
+}
+
+
+//
+// Public Send functions
+//
+
+function flush() {
+    if (websocket.bufferedAmount === 0) {
+        //Util.Debug("arr: " + arr);
+        //Util.Debug("sQ: " + sQ);
+        if (sQ) {
+            websocket.send(encode_message(sQ));
+            sQ = [];
+        }
+        return true;
+    } else {
+        Util.Debug("Delaying send");
+        return false;
+    }
+}
+
+// overridable for testing
+function send(arr) {
+    //Util.Debug(">> send_array: " + arr);
+    sQ = sQ.concat(arr);
+    flush();
+};
+
+function send_string(str) {
+    //Util.Debug(">> send_string: " + str);
+    api.send(str.split('').map(
+        function (chr) { return chr.charCodeAt(0); } ) );
+}
+
+//
+// Other public functions
+
+function recv_message(e) {
+    //Util.Debug(">> recv_message: " + e.data.length);
+
+    try {
+        decode_message(e.data);
+        if (rQlen() > 0) {
+            eventHandlers['message']();
+            // Compact the receive queue
+            if (rQ.length > rQmax) {
+                //Util.Debug("Compacting receive queue");
+                rQ = rQ.slice(rQi);
+                rQi = 0;
+            }
+        } 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') {
+            eventHandlers['error'](exc.name + ": " + exc.message);
+        } else {
+            eventHandlers['error'](exc);
+        }
+    }
+    //Util.Debug("<< recv_message");
+};
+
+
+// Set event handlers
+function on(evt, handler) { 
+    eventHandlers[evt] = handler;
+}
+
+function init() {
+    rQ         = [];
+    rQi        = 0;
+    sQ         = [];
+    websocket  = null;
+}
+
+function open(uri) {
+    init();
+
+    websocket = new WebSocket(uri);
+
+    websocket.onmessage = recv_message;
+    websocket.onopen = function(e) {
+        Util.Debug(">> WebSock.onopen");
+        eventHandlers['open']();
+        Util.Debug("<< WebSock.onopen");
+    }
+    websocket.onclose = function(e) {
+        Util.Debug(">> WebSock.onclose");
+        eventHandlers['close']();
+        Util.Debug("<< WebSock.onclose");
+    }
+    websocket.onerror = function(e) {
+        Util.Debug("<< WebSock.onerror: " + e);
+        eventHandlers['error'](e);
+        Util.Debug("<< WebSock.onerror: ");
+    }
+}
+
+function close() {
+    if (websocket) {
+        if ((websocket.readyState === WebSocket.OPEN) ||
+            (websocket.readyState === WebSocket.CONNECTING)) {
+            Util.Info("Closing WebSocket connection");
+            websocket.close();
+        }
+        websocket.onmessage = function (e) { return; };
+    }
+}
+
+function constructor() {
+    // Direct access to send and receive queues
+    api.get_sQ       = get_sQ;
+    api.get_rQ       = get_rQ;
+    api.get_rQi      = get_rQi;
+    api.set_rQi      = set_rQi;
+
+    // Routines to read from the receive queue
+    api.rQlen        = rQlen;
+    api.rQpeek8      = rQpeek8;
+    api.rQshift8     = rQshift8;
+    api.rQunshift8   = rQunshift8;
+    api.rQshift16    = rQshift16;
+    api.rQshift32    = rQshift32;
+    api.rQshiftStr   = rQshiftStr;
+    api.rQshiftBytes = rQshiftBytes;
+    api.rQslice      = rQslice;
+    api.rQwait       = rQwait;
+
+    api.flush        = flush;
+    api.send         = send;
+    api.send_string  = send_string;
+
+    api.recv_message = recv_message;
+    api.on           = on;
+    api.init         = init;
+    api.open         = open;
+    api.close        = close;
+
+    return api;
+}
+
+return constructor();
+
+}