瀏覽代碼

Indexed receive queue. Up to 2X speedup in Chrome.

Generally, most servers send hextile updates as single updates
containing many rects. Some servers send hextile updates as many small
framebuffer updates with a few rects each (such as QEMU). This latter
cases revealed that shifting off the beginning of the receive queue
(which happens after each hextile FBU) performs poorly.

This change switches to using an indexed receive queue (instead of
actually shifting off the array). When the receive queue has grown to
a certain size, then it is compacted all at once.

The code is not as clean, but this change results in more than 2X
speedup under Chrome for the pessimal case and 10-20% in firefox.
Joel Martin 15 年之前
父節點
當前提交
67b4e9879a
共有 5 個文件被更改,包括 207 次插入180 次删除
  1. 12 0
      include/canvas.js
  2. 183 131
      include/rfb.js
  3. 0 48
      include/util.js
  4. 11 0
      tests/cursor.html
  5. 1 1
      tests/vnc_playback.html

+ 12 - 0
include/canvas.js

@@ -654,6 +654,18 @@ that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
         return;
         return;
     }
     }
 
 
+    // Push multi-byte little-endian values
+    cur.push16le = function (num) {
+        this.push((num     ) & 0xFF,
+                  (num >> 8) & 0xFF  );
+    };
+    cur.push32le = function (num) {
+        this.push((num      ) & 0xFF,
+                  (num >>  8) & 0xFF,
+                  (num >> 16) & 0xFF,
+                  (num >> 24) & 0xFF  );
+    };
+
     cmap = conf.colourMap;
     cmap = conf.colourMap;
     IHDRsz = 40;
     IHDRsz = 40;
     ANDsz = w * h * 4;
     ANDsz = w * h * 4;

+ 183 - 131
include/rfb.js

@@ -69,6 +69,7 @@ var that           = {},         // Public API interface
 
 
     // Receive and send queues
     // Receive and send queues
     RQ             = [],  // Receive Queue
     RQ             = [],  // Receive Queue
+    RQi            = 0,   // Receive Queue Index
     SQ             = "",  // Send Queue
     SQ             = "",  // Send Queue
 
 
     // Frame buffer update state
     // Frame buffer update state
@@ -100,6 +101,7 @@ var that           = {},         // Public API interface
     scan_imgs_rate = 100,
     scan_imgs_rate = 100,
     last_req_time  = 0,
     last_req_time  = 0,
     rre_chunk_sz   = 100,
     rre_chunk_sz   = 100,
+    maxRQlen       = 100000,
 
 
     timing         = {
     timing         = {
         last_fbu       : 0,
         last_fbu       : 0,
@@ -155,7 +157,7 @@ Util.conf_default(conf, that, 'updateState', function () {
         Util.Debug(">> externalUpdateState stub"); });
         Util.Debug(">> externalUpdateState stub"); });
 // clipboard contents received callback
 // clipboard contents received callback
 Util.conf_default(conf, that, 'clipboardReceive', function () {
 Util.conf_default(conf, that, 'clipboardReceive', function () {
-    Util.Debug(">> clipboardReceive stub"); });
+        Util.Debug(">> clipboardReceive stub"); });
 
 
 
 
 // Override/add some specific getters/setters
 // Override/add some specific getters/setters
@@ -182,6 +184,35 @@ that.get_canvas = function() {
 // Private functions
 // Private functions
 //
 //
 
 
+//
+// Receive Queue functions
+//
+RQlen = function() {
+    return RQ.length - RQi;
+}
+
+RQshift16 = function() {
+    return (RQ[RQi++] <<  8) +
+           (RQ[RQi++]      );
+}
+RQshift32 = function() {
+    return (RQ[RQi++] << 24) +
+           (RQ[RQi++] << 16) +
+           (RQ[RQi++] <<  8) +
+           (RQ[RQi++]      );
+}
+RQshiftStr = function(len) {
+    var arr = RQ.slice(RQi, RQi + len);
+    RQi += len;
+    return arr.map(function (num) {
+            return String.fromCharCode(num); } ).join('');
+
+}
+RQshiftBytes = function(len) {
+    RQi += len;
+    return RQ.slice(RQi-len, RQi);
+}
+
 //
 //
 // Setup routines
 // Setup routines
 //
 //
@@ -189,7 +220,7 @@ that.get_canvas = function() {
 // Create the public API interface
 // Create the public API interface
 function constructor() {
 function constructor() {
     var i;
     var i;
-    //Util.Debug(">> init");
+    Util.Debug(">> RFB.constructor");
 
 
     // Create lookup tables based encoding number
     // Create lookup tables based encoding number
     for (i=0; i < encodings.length; i+=1) {
     for (i=0; i < encodings.length; i+=1) {
@@ -205,12 +236,12 @@ function constructor() {
         updateState('fatal', "No working Canvas");
         updateState('fatal', "No working Canvas");
     }
     }
 
 
-    //Util.Debug("<< init");
+    Util.Debug("<< RFB.constructor");
     return that;  // Return the public API interface
     return that;  // Return the public API interface
 }
 }
 
 
 function init_ws() {
 function init_ws() {
-    //Util.Debug(">> init_ws");
+    Util.Debug(">> RFB.init_ws");
 
 
     var uri = "", vars = [];
     var uri = "", vars = [];
     if (conf.encrypt) {
     if (conf.encrypt) {
@@ -261,7 +292,7 @@ function init_ws() {
             }
             }
         }, conf.connectTimeout);
         }, conf.connectTimeout);
 
 
-    //Util.Debug("<< init_ws");
+    Util.Debug("<< RFB.init_ws");
 }
 }
 
 
 init_vars = function() {
 init_vars = function() {
@@ -269,6 +300,7 @@ init_vars = function() {
     cuttext          = 'none';
     cuttext          = 'none';
     cuttext_length   = 0;
     cuttext_length   = 0;
     RQ               = [];
     RQ               = [];
+    RQi              = 0;
     SQ               = "";
     SQ               = "";
     FBU.rects        = 0;
     FBU.rects        = 0;
     FBU.subrects     = 0;  // RRE and HEXTILE
     FBU.subrects     = 0;  // RRE and HEXTILE
@@ -456,8 +488,8 @@ function decode_message(data) {
 }
 }
 
 
 function handle_message() {
 function handle_message() {
-    //Util.Debug("RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")");
-    if (RQ.length === 0) {
+    //Util.Debug("RQ.slice(RQi,RQi+20): " + RQ.slice(RQi,RQi+20) + " (" + RQlen() + ")");
+    if (RQlen() === 0) {
         Util.Warn("handle_message called on empty receive queue");
         Util.Warn("handle_message called on empty receive queue");
         return;
         return;
     }
     }
@@ -470,7 +502,7 @@ function handle_message() {
         that.disconnect();
         that.disconnect();
         break;
         break;
     case 'normal':
     case 'normal':
-        if (normal_msg() && RQ.length > 0) {
+        if (normal_msg() && 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) {
@@ -483,6 +515,12 @@ 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 > maxRQlen) {
+            //Util.Debug("Compacting receive queue");
+            RQ = RQ.slice(RQi);
+            RQi = 0;
+        }
         break;
         break;
     default:
     default:
         init_msg();
         init_msg();
@@ -495,7 +533,7 @@ recv_message = function(e) {
 
 
     try {
     try {
         decode_message(e.data);
         decode_message(e.data);
-        if (RQ.length > 0) {
+        if (RQlen() > 0) {
             handle_message();
             handle_message();
         } else {
         } else {
             Util.Debug("Ignoring empty message");
             Util.Debug("Ignoring empty message");
@@ -669,16 +707,16 @@ 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 (" + RQ.length + ") " + RQ);
+    //Util.Debug("RQ (" + RQlen() + ") " + RQ);
     switch (rfb_state) {
     switch (rfb_state) {
 
 
     case 'ProtocolVersion' :
     case 'ProtocolVersion' :
-        if (RQ.length < 12) {
+        if (RQlen() < 12) {
             updateState('failed',
             updateState('failed',
                     "Disconnected: incomplete protocol version");
                     "Disconnected: incomplete protocol version");
             return;
             return;
         }
         }
-        sversion = RQ.shiftStr(12).substr(4,7);
+        sversion = 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;
@@ -718,16 +756,16 @@ init_msg = function() {
 
 
     case 'Security' :
     case 'Security' :
         if (rfb_version >= 3.7) {
         if (rfb_version >= 3.7) {
-            num_types = RQ.shift8();
+            num_types = RQ[RQi++];
             if (num_types === 0) {
             if (num_types === 0) {
-                strlen = RQ.shift32();
-                reason = RQ.shiftStr(strlen);
+                strlen = RQshift32();
+                reason = RQshiftStr(strlen);
                 updateState('failed',
                 updateState('failed',
                         "Disconnected: security failure: " + reason);
                         "Disconnected: security failure: " + reason);
                 return;
                 return;
             }
             }
             rfb_auth_scheme = 0;
             rfb_auth_scheme = 0;
-            types = RQ.shiftBytes(num_types);
+            types = 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)) {
@@ -742,11 +780,11 @@ init_msg = function() {
             
             
             send_array([rfb_auth_scheme]);
             send_array([rfb_auth_scheme]);
         } else {
         } else {
-            if (RQ.length < 4) {
+            if (RQlen() < 4) {
                 updateState('failed', "Invalid security frame");
                 updateState('failed', "Invalid security frame");
                 return;
                 return;
             }
             }
-            rfb_auth_scheme = RQ.shift32();
+            rfb_auth_scheme = RQshift32();
         }
         }
         updateState('Authentication',
         updateState('Authentication',
                 "Authenticating using scheme: " + rfb_auth_scheme);
                 "Authenticating using scheme: " + rfb_auth_scheme);
@@ -757,12 +795,12 @@ 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 (RQ.length < 4) {
+                if (RQlen() < 4) {
                     //Util.Debug("   waiting for auth reason bytes");
                     //Util.Debug("   waiting for auth reason bytes");
                     return;
                     return;
                 }
                 }
-                strlen = RQ.shift32();
-                reason = RQ.shiftStr(strlen);
+                strlen = RQshift32();
+                reason = RQshiftStr(strlen);
                 updateState('failed',
                 updateState('failed',
                         "Disconnected: auth failure: " + reason);
                         "Disconnected: auth failure: " + reason);
                 return;
                 return;
@@ -774,11 +812,11 @@ init_msg = function() {
                     updateState('password', "Password Required");
                     updateState('password', "Password Required");
                     return;
                     return;
                 }
                 }
-                if (RQ.length < 16) {
+                if (RQlen() < 16) {
                     //Util.Debug("   waiting for auth challenge bytes");
                     //Util.Debug("   waiting for auth challenge bytes");
                     return;
                     return;
                 }
                 }
-                challenge = RQ.shiftBytes(16);
+                challenge = RQshiftBytes(16);
                 //Util.Debug("Password: " + rfb_password);
                 //Util.Debug("Password: " + rfb_password);
                 //Util.Debug("Challenge: " + challenge +
                 //Util.Debug("Challenge: " + challenge +
                 //           " (" + challenge.length + ")");
                 //           " (" + challenge.length + ")");
@@ -799,18 +837,18 @@ init_msg = function() {
         break;
         break;
 
 
     case 'SecurityResult' :
     case 'SecurityResult' :
-        if (RQ.length < 4) {
+        if (RQlen() < 4) {
             updateState('failed', "Invalid VNC auth response");
             updateState('failed', "Invalid VNC auth response");
             return;
             return;
         }
         }
-        switch (RQ.shift32()) {
+        switch (RQshift32()) {
             case 0:  // OK
             case 0:  // OK
                 updateState('ServerInitialisation', "Authentication OK");
                 updateState('ServerInitialisation', "Authentication OK");
                 break;
                 break;
             case 1:  // failed
             case 1:  // failed
                 if (rfb_version >= 3.8) {
                 if (rfb_version >= 3.8) {
-                    reason_len = RQ.shift32();
-                    reason = RQ.shiftStr(reason_len);
+                    reason_len = RQshift32();
+                    reason = RQshiftStr(reason_len);
                     updateState('failed', reason);
                     updateState('failed', reason);
                 } else {
                 } else {
                     updateState('failed', "Authentication failed");
                     updateState('failed', "Authentication failed");
@@ -825,20 +863,20 @@ init_msg = function() {
         break;
         break;
 
 
     case 'ServerInitialisation' :
     case 'ServerInitialisation' :
-        if (RQ.length < 24) {
+        if (RQlen() < 24) {
             updateState('failed', "Invalid server initialisation");
             updateState('failed', "Invalid server initialisation");
             return;
             return;
         }
         }
 
 
         /* Screen size */
         /* Screen size */
-        fb_width  = RQ.shift16();
-        fb_height = RQ.shift16();
+        fb_width  = RQshift16();
+        fb_height = RQshift16();
 
 
         /* PIXEL_FORMAT */
         /* PIXEL_FORMAT */
-        bpp            = RQ.shift8();
-        depth          = RQ.shift8();
-        big_endian     = RQ.shift8();
-        true_color     = RQ.shift8();
+        bpp            = RQ[RQi++];
+        depth          = RQ[RQi++];
+        big_endian     = RQ[RQi++];
+        true_color     = RQ[RQi++];
 
 
         Util.Info("Screen: " + fb_width + "x" + fb_height + 
         Util.Info("Screen: " + fb_width + "x" + fb_height + 
                   ", bpp: " + bpp + ", depth: " + depth +
                   ", bpp: " + bpp + ", depth: " + depth +
@@ -846,9 +884,9 @@ init_msg = function() {
                   ", true_color: " + true_color);
                   ", true_color: " + true_color);
 
 
         /* Connection name/title */
         /* Connection name/title */
-        RQ.shiftStr(12);
-        name_length   = RQ.shift32();
-        fb_name = RQ.shiftStr(name_length);
+        RQshiftStr(12);
+        name_length   = RQshift32();
+        fb_name = 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);
@@ -891,14 +929,12 @@ normal_msg = function() {
     var ret = true, msg_type,
     var ret = true, msg_type,
         c, first_colour, num_colours, red, green, blue;
         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) {
     if (FBU.rects > 0) {
         msg_type = 0;
         msg_type = 0;
     } else if (cuttext !== 'none') {
     } else if (cuttext !== 'none') {
         msg_type = 3;
         msg_type = 3;
     } else {
     } else {
-        msg_type = RQ.shift8();
+        msg_type = RQ[RQi++];
     }
     }
     switch (msg_type) {
     switch (msg_type) {
     case 0:  // FramebufferUpdate
     case 0:  // FramebufferUpdate
@@ -906,16 +942,16 @@ normal_msg = function() {
         break;
         break;
     case 1:  // SetColourMapEntries
     case 1:  // SetColourMapEntries
         Util.Debug("SetColourMapEntries");
         Util.Debug("SetColourMapEntries");
-        RQ.shift8();  // Padding
-        first_colour = RQ.shift16(); // First colour
-        num_colours = RQ.shift16();
+        RQ[RQi++];  // Padding
+        first_colour = RQshift16(); // First colour
+        num_colours = RQshift16();
         for (c=0; c < num_colours; c+=1) { 
         for (c=0; c < num_colours; c+=1) { 
-            red = RQ.shift16();
+            red = 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(RQ.shift16() / 256, 10);
-            blue = parseInt(RQ.shift16() / 256, 10);
+            green = parseInt(RQshift16() / 256, 10);
+            blue = parseInt(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");
@@ -931,19 +967,19 @@ normal_msg = function() {
             cuttext = 'header';
             cuttext = 'header';
         }
         }
         if (cuttext === 'header') {
         if (cuttext === 'header') {
-            if (RQ.length < 7) {
+            if (RQlen() < 7) {
                 //Util.Debug("waiting for ServerCutText header");
                 //Util.Debug("waiting for ServerCutText header");
                 return false;
                 return false;
             }
             }
-            RQ.shiftBytes(3);  // Padding
-            cuttext_length = RQ.shift32();
+            RQshiftBytes(3);  // Padding
+            cuttext_length = RQshift32();
         }
         }
         cuttext = 'bytes';
         cuttext = 'bytes';
-        if (RQ.length < cuttext_length) {
+        if (RQlen() < cuttext_length) {
             //Util.Debug("waiting for ServerCutText bytes");
             //Util.Debug("waiting for ServerCutText bytes");
             return false;
             return false;
         }
         }
-        conf.clipboardReceive(that, RQ.shiftStr(cuttext_length));
+        conf.clipboardReceive(that, RQshiftStr(cuttext_length));
         cuttext = 'none';
         cuttext = 'none';
         break;
         break;
     default:
     default:
@@ -961,13 +997,17 @@ framebufferUpdate = function() {
 
 
     if (FBU.rects === 0) {
     if (FBU.rects === 0) {
         //Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20));
         //Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20));
-        if (RQ.length < 3) {
-            RQ.unshift(0);  // FBU msg_type
-            Util.Debug("   waiting for FBU header bytes");
+        if (RQlen() < 3) {
+            if (RQi === 0) {
+                RQ.unshift(0);  // FBU msg_type
+            } else {
+                RQi -= 1;
+            }
+            //Util.Debug("   waiting for FBU header bytes");
             return false;
             return false;
         }
         }
-        RQ.shift8();
-        FBU.rects = RQ.shift16();
+        RQ[RQi++];
+        FBU.rects = 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;
@@ -982,17 +1022,18 @@ framebufferUpdate = function() {
         if (rfb_state !== "normal") {
         if (rfb_state !== "normal") {
             return false;
             return false;
         }
         }
-        if (RQ.length < FBU.bytes) {
+        if (RQlen() < FBU.bytes) {
+            //Util.Debug("   waiting for " + (FBU.bytes - RQlen()) + " FBU bytes");
             return false;
             return false;
         }
         }
         if (FBU.bytes === 0) {
         if (FBU.bytes === 0) {
-            if (RQ.length < 12) {
+            if (RQlen() < 12) {
                 //Util.Debug("   waiting for rect header bytes");
                 //Util.Debug("   waiting for rect header bytes");
                 return false;
                 return false;
             }
             }
             /* New FramebufferUpdate */
             /* New FramebufferUpdate */
 
 
-            hdr = RQ.shiftBytes(12);
+            hdr = 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];
@@ -1009,7 +1050,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 += ", RQ.length: " + RQ.length;
+                msg += ", RQlen(): " + RQlen();
                 Util.Debug(msg);
                 Util.Debug(msg);
                 */
                 */
             } else {
             } else {
@@ -1021,7 +1062,7 @@ framebufferUpdate = function() {
         }
         }
 
 
         timing.last_fbu = (new Date()).getTime();
         timing.last_fbu = (new Date()).getTime();
-        last_bytes = RQ.length;
+        last_bytes = RQlen();
         last_rects = FBU.rects;
         last_rects = FBU.rects;
 
 
         // false ret means need more data
         // false ret means need more data
@@ -1029,7 +1070,7 @@ framebufferUpdate = function() {
 
 
         now = (new Date()).getTime();
         now = (new Date()).getTime();
         timing.cur_fbu += (now - timing.last_fbu);
         timing.cur_fbu += (now - timing.last_fbu);
-        timing.h_bytes += last_bytes-RQ.length;
+        timing.h_bytes += last_bytes-RQlen();
 
 
         if (FBU.rects < last_rects) {
         if (FBU.rects < last_rects) {
             // Some work was done
             // Some work was done
@@ -1063,6 +1104,9 @@ framebufferUpdate = function() {
                 timing.fbu_rt_start = 0;
                 timing.fbu_rt_start = 0;
             }
             }
         }
         }
+        if (! ret) {
+            break; // false ret means need more data
+        }
     }
     }
     return ret;
     return ret;
 };
 };
@@ -1080,16 +1124,16 @@ encHandlers.RAW = function display_raw() {
         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 (RQ.length < FBU.bytes) {
+    if (RQlen() < FBU.bytes) {
         //Util.Debug("   waiting for " +
         //Util.Debug("   waiting for " +
-        //           (FBU.bytes - RQ.length) + " RAW bytes");
+        //           (FBU.bytes - RQlen()) + " RAW bytes");
         return false;
         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(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);
+                          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);
     FBU.lines -= cur_height;
     FBU.lines -= cur_height;
 
 
     if (FBU.lines > 0) {
     if (FBU.lines > 0) {
@@ -1106,13 +1150,13 @@ encHandlers.COPYRECT = function display_copy_rect() {
 
 
     var old_x, old_y;
     var old_x, old_y;
 
 
-    if (RQ.length < 4) {
+    if (RQlen() < 4) {
         //Util.Debug("   waiting for " +
         //Util.Debug("   waiting for " +
-        //           (FBU.bytes - RQ.length) + " COPYRECT bytes");
+        //           (FBU.bytes - RQlen()) + " COPYRECT bytes");
         return false;
         return false;
     }
     }
-    old_x = RQ.shift16();
-    old_y = RQ.shift16();
+    old_x = RQshift16();
+    old_y = 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;
@@ -1120,25 +1164,25 @@ encHandlers.COPYRECT = function display_copy_rect() {
 };
 };
 
 
 encHandlers.RRE = function display_rre() {
 encHandlers.RRE = function display_rre() {
-    //Util.Debug(">> display_rre (" + RQ.length + " bytes)");
+    //Util.Debug(">> display_rre (" + 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 (RQ.length < 4 + fb_Bpp) {
+        if (RQlen() < 4 + fb_Bpp) {
             //Util.Debug("   waiting for " +
             //Util.Debug("   waiting for " +
-            //           (4 + fb_Bpp - RQ.length) + " RRE bytes");
+            //           (4 + fb_Bpp - RQlen()) + " RRE bytes");
             return false;
             return false;
         }
         }
-        FBU.subrects = RQ.shift32();
-        color = RQ.shiftBytes(fb_Bpp); // Background
+        FBU.subrects = RQshift32();
+        color = 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) && (RQ.length >= (fb_Bpp + 8))) {
-        color = RQ.shiftBytes(fb_Bpp);
-        x = RQ.shift16();
-        y = RQ.shift16();
-        width = RQ.shift16();
-        height = RQ.shift16();
+    while ((FBU.subrects > 0) && (RQlen() >= (fb_Bpp + 8))) {
+        color = RQshiftBytes(fb_Bpp);
+        x = RQshift16();
+        y = RQshift16();
+        width = RQshift16();
+        height = 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;
     }
     }
@@ -1158,7 +1202,7 @@ 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, idx, 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;
 
 
     if (FBU.tiles === 0) {
     if (FBU.tiles === 0) {
@@ -1168,14 +1212,15 @@ encHandlers.HEXTILE = function display_hextile() {
         FBU.tiles = FBU.total_tiles;
         FBU.tiles = FBU.total_tiles;
     }
     }
 
 
-    /* FBU.bytes comes in as 1, RQ.length at least 1 */
+    /* FBU.bytes comes in as 1, RQlen() at least 1 */
     while (FBU.tiles > 0) {
     while (FBU.tiles > 0) {
         FBU.bytes = 1;
         FBU.bytes = 1;
-        if (RQ.length < FBU.bytes) {
+        if (RQlen() < FBU.bytes) {
             //Util.Debug("   waiting for HEXTILE subencoding byte");
             //Util.Debug("   waiting for HEXTILE subencoding byte");
             return false;
             return false;
         }
         }
-        subencoding = RQ[0];  // Peek
+        //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);
+        subencoding = RQ[RQi];  // Peek
         if (subencoding > 30) { // Raw
         if (subencoding > 30) { // Raw
             updateState('failed',
             updateState('failed',
                     "Disconnected: illegal hextile subencoding " + subencoding);
                     "Disconnected: illegal hextile subencoding " + subencoding);
@@ -1204,12 +1249,12 @@ 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 (RQ.length < FBU.bytes) {
+                if (RQlen() < FBU.bytes) {
                     /* Wait for subrects byte */
                     /* Wait for subrects byte */
                     //Util.Debug("   waiting for hextile subrects header byte");
                     //Util.Debug("   waiting for hextile subrects header byte");
                     return false;
                     return false;
                 }
                 }
-                subrects = RQ[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);
                 } else {
                 } else {
@@ -1218,23 +1263,26 @@ encHandlers.HEXTILE = function display_hextile() {
             }
             }
         }
         }
 
 
-        //Util.Debug("   tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
-        //      ", subencoding:" + subencoding +
-        //      "(last: " + FBU.lastsubencoding + "), subrects:" +
-        //      subrects + ", tile:" + tile_x + "," + tile_y +
-        //      " [" + x + "," + y + "]@" + w + "x" + h +
-        //      ", d.length:" + RQ.length + ", bytes:" + FBU.bytes +
-        //      " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) +
-        //      " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10));
-        if (RQ.length < FBU.bytes) {
+        /*
+        Util.Debug("   tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
+              " (" + tile_x + "," + tile_y + ")" +
+              " [" + x + "," + y + "]@" + w + "x" + h +
+              ", subenc:" + subencoding +
+              "(last: " + FBU.lastsubencoding + "), subrects:" +
+              subrects +
+              ", RQlen():" + RQlen() + ", FBU.bytes:" + FBU.bytes +
+              " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) +
+              " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10));
+        */
+        if (RQlen() < FBU.bytes) {
             //Util.Debug("   waiting for " +
             //Util.Debug("   waiting for " +
-            //           (FBU.bytes - RQ.length) + " hextile bytes");
+            //           (FBU.bytes - RQlen()) + " hextile bytes");
             return false;
             return false;
         }
         }
 
 
         /* We know the encoding and have a whole tile */
         /* We know the encoding and have a whole tile */
-        FBU.subencoding = RQ[0];
-        idx = 1;
+        FBU.subencoding = RQ[RQi];
+        RQi += 1;
         if (FBU.subencoding === 0) {
         if (FBU.subencoding === 0) {
             if (FBU.lastsubencoding & 0x01) {
             if (FBU.lastsubencoding & 0x01) {
                 /* Weird: ignore blanks after RAW */
                 /* Weird: ignore blanks after RAW */
@@ -1243,35 +1291,36 @@ encHandlers.HEXTILE = function display_hextile() {
                 canvas.fillRect(x, y, w, h, FBU.background);
                 canvas.fillRect(x, y, w, h, FBU.background);
             }
             }
         } else if (FBU.subencoding & 0x01) { // Raw
         } else if (FBU.subencoding & 0x01) { // Raw
-            canvas.blitImage(x, y, w, h, RQ, idx);
+            canvas.blitImage(x, y, w, h, RQ, RQi);
+            RQi += FBU.bytes - 1;
         } else {
         } else {
             if (FBU.subencoding & 0x02) { // Background
             if (FBU.subencoding & 0x02) { // Background
-                FBU.background = RQ.slice(idx, idx + fb_Bpp);
-                idx += fb_Bpp;
+                FBU.background = RQ.slice(RQi, RQi + fb_Bpp);
+                RQi += fb_Bpp;
             }
             }
             if (FBU.subencoding & 0x04) { // Foreground
             if (FBU.subencoding & 0x04) { // Foreground
-                FBU.foreground = RQ.slice(idx, idx + fb_Bpp);
-                idx += fb_Bpp;
+                FBU.foreground = RQ.slice(RQi, RQi + fb_Bpp);
+                RQi += 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
             if (FBU.subencoding & 0x08) { // AnySubrects
-                subrects = RQ[idx];
-                idx += 1;
+                subrects = RQ[RQi];
+                RQi += 1;
                 for (s = 0; s < subrects; s += 1) {
                 for (s = 0; s < subrects; s += 1) {
                     if (FBU.subencoding & 0x10) { // SubrectsColoured
                     if (FBU.subencoding & 0x10) { // SubrectsColoured
-                        color = RQ.slice(idx, idx + fb_Bpp);
-                        idx += fb_Bpp;
+                        color = RQ.slice(RQi, RQi + fb_Bpp);
+                        RQi += fb_Bpp;
                     } else {
                     } else {
                         color = FBU.foreground;
                         color = FBU.foreground;
                     }
                     }
-                    xy = RQ[idx];
-                    idx += 1;
+                    xy = RQ[RQi];
+                    RQi += 1;
                     sx = (xy >> 4);
                     sx = (xy >> 4);
                     sy = (xy & 0x0f);
                     sy = (xy & 0x0f);
 
 
-                    wh = RQ[idx];
-                    idx += 1;
+                    wh = RQ[RQi];
+                    RQi += 1;
                     sw = (wh >> 4)   + 1;
                     sw = (wh >> 4)   + 1;
                     sh = (wh & 0x0f) + 1;
                     sh = (wh & 0x0f) + 1;
 
 
@@ -1280,7 +1329,7 @@ encHandlers.HEXTILE = function display_hextile() {
             }
             }
             canvas.putTile(tile);
             canvas.putTile(tile);
         }
         }
-        RQ.shiftBytes(FBU.bytes);
+        //RQshiftBytes(FBU.bytes);
         FBU.lastsubencoding = FBU.subencoding;
         FBU.lastsubencoding = FBU.subencoding;
         FBU.bytes = 0;
         FBU.bytes = 0;
         FBU.tiles -= 1;
         FBU.tiles -= 1;
@@ -1299,12 +1348,12 @@ 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("   RQ.length: " + RQ.length);
+    //Util.Debug("   RQlen(): " + RQlen());
     //Util.Debug("   RQ.slice(0,20): " + RQ.slice(0,20));
     //Util.Debug("   RQ.slice(0,20): " + RQ.slice(0,20));
 
 
 
 
     FBU.bytes = 1; // compression-control byte
     FBU.bytes = 1; // compression-control byte
-    if (RQ.length < FBU.bytes) {
+    if (RQlen() < FBU.bytes) {
         Util.Debug("   waiting for TIGHT compression-control byte");
         Util.Debug("   waiting for TIGHT compression-control byte");
         return false;
         return false;
     }
     }
@@ -1324,7 +1373,7 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
         return [header, data];
         return [header, data];
     };
     };
 
 
-    ctl = RQ[0];
+    ctl = RQ[RQi];
     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;
@@ -1338,44 +1387,44 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
         case "png":  FBU.bytes += 3;            break; // max clength
         case "png":  FBU.bytes += 3;            break; // max clength
     }
     }
 
 
-    if (RQ.length < FBU.bytes) {
+    if (RQlen() < FBU.bytes) {
         Util.Debug("   waiting for TIGHT " + cmode + " bytes");
         Util.Debug("   waiting for TIGHT " + cmode + " bytes");
         return false;
         return false;
     }
     }
 
 
-    //Util.Debug("   RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")");
+    //Util.Debug("   RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQlen() + ")");
     //Util.Debug("   cmode: " + cmode);
     //Util.Debug("   cmode: " + cmode);
 
 
     // Determine FBU.bytes
     // Determine FBU.bytes
     switch (cmode) {
     switch (cmode) {
     case "fill":
     case "fill":
-        RQ.shift8(); // shift off ctl
-        color = RQ.shiftBytes(fb_depth);
+        RQ[RQi++]; // shift off ctl
+        color = 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, 1);
         clength = getCLength(RQ, 1);
         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 (RQ.length < FBU.bytes) {
+        if (RQlen() < FBU.bytes) {
             Util.Debug("   waiting for TIGHT " + cmode + " bytes");
             Util.Debug("   waiting for TIGHT " + cmode + " bytes");
             return false;
             return false;
         }
         }
 
 
         // We have everything, render it
         // 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
+        //Util.Debug("   png, RQlen(): " + RQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
+        RQshiftBytes(1 + clength[0]); // shift off ctl + compact length
         img = new Image();
         img = new Image();
         img.onload = scan_tight_imgs;
         img.onload = scan_tight_imgs;
         FBU.imgs.push([img, FBU.x, FBU.y]);
         FBU.imgs.push([img, FBU.x, FBU.y]);
         img.src = "data:image/" + cmode +
         img.src = "data:image/" + cmode +
-            extract_data_uri(RQ.shiftBytes(clength[1]));
+            extract_data_uri(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.length: " + RQ.length);
+    //Util.Debug("   ending RQlen(): " + RQlen());
     //Util.Debug("   ending RQ.slice(0,20): " + RQ.slice(0,20));
     //Util.Debug("   ending RQ.slice(0,20): " + RQ.slice(0,20));
     //Util.Debug("<< display_tight_png");
     //Util.Debug("<< display_tight_png");
     return true;
     return true;
@@ -1431,7 +1480,7 @@ encHandlers.Cursor = function set_cursor() {
     pixelslength = w * h * fb_Bpp;
     pixelslength = w * h * fb_Bpp;
     masklength = Math.floor((w + 7) / 8) * h;
     masklength = Math.floor((w + 7) / 8) * h;
 
 
-    if (RQ.length < (pixelslength + masklength)) {
+    if (RQlen() < (pixelslength + masklength)) {
         //Util.Debug("waiting for cursor encoding bytes");
         //Util.Debug("waiting for cursor encoding bytes");
         FBU.bytes = pixelslength + masklength;
         FBU.bytes = pixelslength + masklength;
         return false;
         return false;
@@ -1439,8 +1488,8 @@ encHandlers.Cursor = function set_cursor() {
 
 
     //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(RQ.shiftBytes(pixelslength),
-                            RQ.shiftBytes(masklength),
+    canvas.changeCursor(RQshiftBytes(pixelslength),
+                            RQshiftBytes(masklength),
                             x, y, w, h);
                             x, y, w, h);
 
 
     FBU.bytes = 0;
     FBU.bytes = 0;
@@ -1556,13 +1605,16 @@ pointerEvent = function(x, y) {
 
 
 clientCutText = function(text) {
 clientCutText = function(text) {
     //Util.Debug(">> clientCutText");
     //Util.Debug(">> clientCutText");
-    var arr;
+    var arr, i, n;
     arr = [6];     // msg-type
     arr = [6];     // msg-type
     arr.push8(0);  // padding
     arr.push8(0);  // padding
     arr.push8(0);  // padding
     arr.push8(0);  // padding
     arr.push8(0);  // padding
     arr.push8(0);  // padding
     arr.push32(text.length);
     arr.push32(text.length);
-    arr.pushStr(text);
+    n = text.length;
+    for (i=0; i < n; i+=1) {
+        arr.push(text.charCodeAt(i));
+    }
     //Util.Debug("<< clientCutText:" + arr);
     //Util.Debug("<< clientCutText:" + arr);
     return arr;
     return arr;
 };
 };

+ 0 - 48
include/util.js

@@ -34,68 +34,20 @@ if (!window.$) {
  * Make arrays quack
  * Make arrays quack
  */
  */
 
 
-Array.prototype.shift8 = function () {
-    return this.shift();
-};
 Array.prototype.push8 = function (num) {
 Array.prototype.push8 = function (num) {
     this.push(num & 0xFF);
     this.push(num & 0xFF);
 };
 };
 
 
-Array.prototype.shift16 = function () {
-    return (this.shift() << 8) +
-           (this.shift()     );
-};
 Array.prototype.push16 = function (num) {
 Array.prototype.push16 = function (num) {
     this.push((num >> 8) & 0xFF,
     this.push((num >> 8) & 0xFF,
               (num     ) & 0xFF  );
               (num     ) & 0xFF  );
 };
 };
-Array.prototype.push16le = function (num) {
-    this.push((num     ) & 0xFF,
-              (num >> 8) & 0xFF  );
-};
-
-
-Array.prototype.shift32 = function () {
-    return (this.shift() << 24) +
-           (this.shift() << 16) +
-           (this.shift() <<  8) +
-           (this.shift()      );
-};
-Array.prototype.get32 = function (off) {
-    return (this[off    ] << 24) +
-           (this[off + 1] << 16) +
-           (this[off + 2] <<  8) +
-           (this[off + 3]      );
-};
 Array.prototype.push32 = function (num) {
 Array.prototype.push32 = function (num) {
     this.push((num >> 24) & 0xFF,
     this.push((num >> 24) & 0xFF,
               (num >> 16) & 0xFF,
               (num >> 16) & 0xFF,
               (num >>  8) & 0xFF,
               (num >>  8) & 0xFF,
               (num      ) & 0xFF  );
               (num      ) & 0xFF  );
 };
 };
-Array.prototype.push32le = function (num) {
-    this.push((num      ) & 0xFF,
-              (num >>  8) & 0xFF,
-              (num >> 16) & 0xFF,
-              (num >> 24) & 0xFF  );
-};
-
-
-Array.prototype.shiftStr = function (len) {
-    var arr = this.splice(0, len);
-    return arr.map(function (num) {
-            return String.fromCharCode(num); } ).join('');
-};
-Array.prototype.pushStr = function (str) {
-    var i, n = str.length;
-    for (i=0; i < n; i+=1) {
-        this.push(str.charCodeAt(i));
-    }
-};
-
-Array.prototype.shiftBytes = function (len) {
-    return this.splice(0, len);
-};
 
 
 /* 
 /* 
  * ------------------------------------------------------
  * ------------------------------------------------------

+ 11 - 0
tests/cursor.html

@@ -45,6 +45,17 @@
         var ANDsz = w * h * 4;
         var ANDsz = w * h * 4;
         var XORsz = Math.ceil( (w * h) / 8.0 );
         var XORsz = Math.ceil( (w * h) / 8.0 );
 
 
+        // Push multi-byte little-endian values
+        arr.push16le = function (num) {
+            this.push((num     ) & 0xFF,
+                      (num >> 8) & 0xFF  );
+        };
+        arr.push32le = function (num) {
+            this.push((num      ) & 0xFF,
+                      (num >>  8) & 0xFF,
+                      (num >> 16) & 0xFF,
+                      (num >> 24) & 0xFF  );
+        };
 
 
         // Main header
         // Main header
         arr.push16le(0);      // Reserved
         arr.push16le(0);      // Reserved

+ 1 - 1
tests/vnc_playback.html

@@ -1,6 +1,6 @@
 <html>
 <html>
     <head>
     <head>
-        <title>VNC Test</title>
+        <title>VNC Playback</title>
         <link rel="stylesheet" href="include/plain.css">
         <link rel="stylesheet" href="include/plain.css">
     </head>
     </head>
     <body>
     <body>