소스 검색

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;
     }
 
+    // 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;
     IHDRsz = 40;
     ANDsz = w * h * 4;

+ 183 - 131
include/rfb.js

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

+ 0 - 48
include/util.js

@@ -34,68 +34,20 @@ if (!window.$) {
  * Make arrays quack
  */
 
-Array.prototype.shift8 = function () {
-    return this.shift();
-};
 Array.prototype.push8 = function (num) {
     this.push(num & 0xFF);
 };
 
-Array.prototype.shift16 = function () {
-    return (this.shift() << 8) +
-           (this.shift()     );
-};
 Array.prototype.push16 = function (num) {
     this.push((num >> 8) & 0xFF,
               (num     ) & 0xFF  );
 };
-Array.prototype.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) {
     this.push((num >> 24) & 0xFF,
               (num >> 16) & 0xFF,
               (num >>  8) & 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 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
         arr.push16le(0);      // Reserved

+ 1 - 1
tests/vnc_playback.html

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