Quellcode durchsuchen

New API. Refactor Canvas and RFB objects.

New API:

To use the RFB object, you now must instantiate it (this allows more
than one instance of it on the same page).

    rfb = new RFB(settings);

The 'settings' variable is a namespace that contains initial default
settings. These can also be set and read using 'rfb.set_FOO()' and
'rfb.get_FOO()' where FOO is the setting name. The current settings
are (and defaults) are:
    - target: the DOM Canvas element to use ('VNC_canvas').
    - encrypt: whether to encrypt the connection (false)
    - true_color: true_color or palette (true)
    - b64encode: base64 encode the WebSockets data (true)
    - local_cursor: use local cursor rendering (true if supported)
    - connectTimeout: milliseconds to wait for connect (2000)
    - updateState: callback when RFB state changes (none)
    - clipboardReceive: callback when clipboard data received (none)

The parameters to the updateState callback have also changed. The
function spec is now updateState(rfb, state, oldstate, msg):
    - rfb: the RFB object that this state change is for.
    - state: the new state
    - oldstate: the previous state
    - msg: a message associate with the state (not always set).

The clipboardReceive spec is clipboardReceive(rfb, text):
    - rfb: the RFB object that this text is from.
    - text: the clipboard text received.

Changes:

- The RFB and Canvas namespaces are now more proper objects. Private
  implementation is no longer exposed and the public API has been made
  explicit. Also, instantiation allows more than one VNC connection
  on the same page (to complete this, DefaultControls will also need
  this same refactoring).

- Added 'none' logging level.

- Removed automatic stylesheet selection workaround in util.js and
  move it to defaultcontrols so that it doesn't interfere with
  intergration.

- Also, some major JSLinting.

- Fix input, canvas, and cursor tests to work with new model.
Joel Martin vor 15 Jahren
Ursprung
Commit
8db09746b7
10 geänderte Dateien mit 1744 neuen und 1623 gelöschten Zeilen
  1. 7 6
      README.md
  2. 444 377
      include/canvas.js
  3. 43 35
      include/default_controls.js
  4. 1126 1098
      include/rfb.js
  5. 50 47
      include/util.js
  6. 32 26
      tests/canvas.html
  7. 3 3
      tests/cursor.html
  8. 4 3
      tests/input.html
  9. 5 5
      vnc.html
  10. 30 23
      vnc_auto.html

+ 7 - 6
README.md

@@ -195,7 +195,7 @@ The client is designed to be easily integrated with existing web
 structure and style.
 structure and style.
 
 
 At a minimum you must include the `vnc.js` and `default_controls.js`
 At a minimum you must include the `vnc.js` and `default_controls.js`
-scripts and call their load() functions. For example:
+scripts and call DefaultControls.load(). For example:
 
 
     <head>
     <head>
         <script src='include/vnc.js'></script>
         <script src='include/vnc.js'></script>
@@ -203,12 +203,13 @@ scripts and call their load() functions. For example:
     </head>
     </head>
     <body>
     <body>
         <div id='vnc'>Loading</div>
         <div id='vnc'>Loading</div>
+
+        <script>
+            window.onload = function () {
+                DefaultControls.load('vnc');
+            }
+        </script>
     </body>
     </body>
-    <script>
-        window.onload = function () {
-            DefaultControls.load('vnc');
-            RFB.load(); };
-    </script>
 
 
 See `vnc.html` and `vnc_auto.html` for examples. The file
 See `vnc.html` and `vnc_auto.html` for examples. The file
 `include/plain.css` has a list of stylable elements.
 `include/plain.css` has a list of stylable elements.

+ 444 - 377
include/canvas.js

@@ -7,183 +7,142 @@
  */
  */
 
 
 "use strict";
 "use strict";
-/*jslint white: false, bitwise: false */
-/*global window, $, Util, Base64 */
+/*jslint browser: true, white: false, bitwise: false */
+/*global window, Util, Base64 */
 
 
-// Globals defined here
-var Canvas;
+function Canvas(conf) {
 
 
-// Everything namespaced inside Canvas
-Canvas = {
+conf               = conf || {}; // Configuration
+var that           = {},         // Public API interface
 
 
-prefer_js    : false, // make private
-force_canvas : false, // make private
-cursor_uri   : true,  // make private
+    // Pre-declare functions used before definitions (jslint)jslint
+    setFillColor, fillRect,
 
 
-true_color   : true,
-colourMap    : [],
+    // Private Canvas namespace variables
+    c_forceCanvas = false,
 
 
-scale        : 1,
-c_wx         : 0,
-c_wy         : 0,
-ctx          : null,
+    c_width        = 0,
+    c_height       = 0,
 
 
-prevStyle    : "",
+    c_prevStyle    = "",
 
 
-focused      : true,
-keyPress     : null,
-mouseButton  : null,
-mouseMove    : null,
+    c_keyPress     = null,
+    c_mouseButton  = null,
+    c_mouseMove    = null;
 
 
-onMouseButton: function(e, down) {
-    var evt, pos, bmask;
-    if (! Canvas.focused) {
-        return true;
-    }
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale);
-    bmask = 1 << evt.button;
-    //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
-    if (Canvas.mouseButton) {
-        Canvas.mouseButton(pos.x, pos.y, down, bmask);
-    }
-    Util.stopEvent(e);
-    return false;
-},
 
 
-onMouseDown: function (e) {
-    Canvas.onMouseButton(e, 1);
-},
+// Capability settings, default can be overridden
+Util.conf_default(conf, that, 'prefer_js', null);
+Util.conf_default(conf, that, 'cursor_uri', null);
 
 
-onMouseUp: function (e) {
-    Canvas.onMouseButton(e, 0);
-},
+// Configuration settings
+Util.conf_default(conf, that, 'target', null);
+Util.conf_default(conf, that, 'true_color', true);
+Util.conf_default(conf, that, 'focused', true);
+Util.conf_default(conf, that, 'colourMap', []);
+Util.conf_default(conf, that, 'scale', 1);
 
 
-onMouseWheel: function (e) {
-    var evt, pos, bmask, wheelData;
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale);
-    wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
-    if (wheelData > 0) {
-        bmask = 1 << 3;
-    } else {
-        bmask = 1 << 4;
-    }
-    //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
-    if (Canvas.mouseButton) {
-        Canvas.mouseButton(pos.x, pos.y, 1, bmask);
-        Canvas.mouseButton(pos.x, pos.y, 0, bmask);
+// Override some specific getters/setters
+that.set_prefer_js = function(val) {
+    if (val && c_forceCanvas) {
+        Util.Warn("Preferring Javascript to Canvas ops is not supported");
+        return false;
     }
     }
-    Util.stopEvent(e);
-    return false;
-},
-
+    conf.prefer_js = val;
+    return true;
+};
 
 
-onMouseMove: function (e) {
-    var evt, pos;
-    evt = (e ? e : window.event);
-    pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale);
-    //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
-    if (Canvas.mouseMove) {
-        Canvas.mouseMove(pos.x, pos.y);
+that.get_colourMap = function(idx) {
+    if (typeof idx === 'undefined') {
+        return conf.colourMap;
+    } else {
+        return conf.colourMap[idx];
     }
     }
-},
+};
 
 
-onKeyDown: function (e) {
-    //Util.Debug("keydown: " + Canvas.getKeysym(e));
-    if (! Canvas.focused) {
-        return true;
-    }
-    if (Canvas.keyPress) {
-        Canvas.keyPress(Canvas.getKeysym(e), 1);
+that.set_colourMap = function(val, idx) {
+    if (typeof idx === 'undefined') {
+        conf.colourMap = val;
+    } else {
+        conf.colourMap[idx] = val;
     }
     }
-    Util.stopEvent(e);
-    return false;
-},
+};
+
+// Add some other getters/setters
+that.get_width = function() {
+    return c_width;
+};
+that.get_height = function() {
+    return c_height;
+};
 
 
-onKeyUp : function (e) {
-    //Util.Debug("keyup: " + Canvas.getKeysym(e));
-    if (! Canvas.focused) {
-        return true;
-    }
-    if (Canvas.keyPress) {
-        Canvas.keyPress(Canvas.getKeysym(e), 0);
-    }
-    Util.stopEvent(e);
-    return false;
-},
 
 
-onMouseDisable: function (e) {
-    var evt, pos;
-    if (! Canvas.focused) {
-        return true;
-    }
-    evt = (e ? e : window.event);
-    pos = Util.getPosition($(Canvas.id));
-    /* Stop propagation if inside canvas area */
-    if ((evt.clientX >= pos.x) &&
-        (evt.clientY >= pos.y) &&
-        (evt.clientX < (pos.x + Canvas.c_wx)) &&
-        (evt.clientY < (pos.y + Canvas.c_wy))) {
-        //Util.Debug("mouse event disabled");
-        Util.stopEvent(e);
-        return false;
-    }
-    //Util.Debug("mouse event not disabled");
-    return true;
-},
 
 
+//
+// Private functions
+//
 
 
-init: function (id) {
-    var c, imgTest, tval, i, curDat, curSave;
+// Create the public API interface
+function constructor() {
     Util.Debug(">> Canvas.init");
     Util.Debug(">> Canvas.init");
 
 
-    Canvas.id = id;
-    c = $(Canvas.id);
+    var c, ctx, imgTest, tval, i, curDat, curSave,
+        has_imageData = false;
+
+    if (! conf.target) { throw("target must be set"); }
+
+    if (typeof conf.target === 'string') {
+        conf.target = window.$(conf.target);
+    }
+
+    c = conf.target;
 
 
-    if (! c.getContext) { throw("No getContext method"); }
-    Canvas.ctx = c.getContext('2d'); 
+    if (! c.getContext) { throw("no getContext method"); }
 
 
-    Canvas.clear();
+    if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
+    ctx = conf.ctx;
+
+    that.clear();
 
 
     /*
     /*
      * Determine browser Canvas feature support
      * Determine browser Canvas feature support
      * and select fastest rendering methods
      * and select fastest rendering methods
      */
      */
     tval = 0;
     tval = 0;
-    Canvas.has_imageData = false;
     try {
     try {
-        imgTest = Canvas.ctx.getImageData(0, 0, 1,1);
+        imgTest = ctx.getImageData(0, 0, 1,1);
         imgTest.data[0] = 123;
         imgTest.data[0] = 123;
         imgTest.data[3] = 255;
         imgTest.data[3] = 255;
-        Canvas.ctx.putImageData(imgTest, 0, 0);
-        tval = Canvas.ctx.getImageData(0, 0, 1, 1).data[0];
+        ctx.putImageData(imgTest, 0, 0);
+        tval = ctx.getImageData(0, 0, 1, 1).data[0];
         if (tval === 123) {
         if (tval === 123) {
-            Canvas.has_imageData = true;
+            has_imageData = true;
         }
         }
-    } catch (exc) {}
+    } catch (exc1) {}
 
 
-    if (Canvas.has_imageData) {
+    if (has_imageData) {
         Util.Info("Canvas supports imageData");
         Util.Info("Canvas supports imageData");
-        Canvas.force_canvas = false;
-        if (Canvas.ctx.createImageData) {
+        c_forceCanvas = false;
+        if (ctx.createImageData) {
             // If it's there, it's faster
             // If it's there, it's faster
             Util.Info("Using Canvas createImageData");
             Util.Info("Using Canvas createImageData");
-            Canvas._imageData = Canvas._imageDataCreate;
-        } else if (Canvas.ctx.getImageData) {
+            that.imageData = that.imageDataCreate;
+        } else if (ctx.getImageData) {
             Util.Info("Using Canvas getImageData");
             Util.Info("Using Canvas getImageData");
-            Canvas._imageData = Canvas._imageDataGet;
+            that.imageData = that.imageDataGet;
         }
         }
         Util.Info("Prefering javascript operations");
         Util.Info("Prefering javascript operations");
-        Canvas.prefer_js = true;
-        Canvas._rgbxImage = Canvas._rgbxImageData;
-        Canvas._cmapImage = Canvas._cmapImageData;
+        if (conf.prefer_js === null) {
+            conf.prefer_js = true;
+        }
+        that.rgbxImage = that.rgbxImageData;
+        that.cmapImage = that.cmapImageData;
     } else {
     } else {
         Util.Warn("Canvas lacks imageData, using fillRect (slow)");
         Util.Warn("Canvas lacks imageData, using fillRect (slow)");
-        Canvas.force_canvas = true;
-        Canvas.prefer_js = false;
-        Canvas._rgbxImage = Canvas._rgbxImageFill;
-        Canvas._cmapImage = Canvas._cmapImageFill;
+        c_forceCanvas = true;
+        conf.prefer_js = false;
+        that.rgbxImage = that.rgbxImageFill;
+        that.cmapImage = that.cmapImageFill;
     }
     }
 
 
     /*
     /*
@@ -191,86 +150,267 @@ init: function (id) {
      * scheme
      * scheme
      */
      */
     curDat = [];
     curDat = [];
-    for (i=0; i < 8 * 8 * 4; i++) {
+    for (i=0; i < 8 * 8 * 4; i += 1) {
         curDat.push(255);
         curDat.push(255);
     }
     }
     try {
     try {
         curSave = c.style.cursor;
         curSave = c.style.cursor;
-        Canvas.changeCursor(curDat, curDat, 2, 2, 8, 8);
+        that.changeCursor(curDat, curDat, 2, 2, 8, 8);
         if (c.style.cursor) {
         if (c.style.cursor) {
+            if (conf.cursor_uri === null) {
+                conf.cursor_uri = true;
+            }
             Util.Info("Data URI scheme cursor supported");
             Util.Info("Data URI scheme cursor supported");
         } else {
         } else {
-            Canvas.cursor_uri = false;
+            if (conf.cursor_uri === null) {
+                conf.cursor_uri = false;
+            }
             Util.Warn("Data URI scheme cursor not supported");
             Util.Warn("Data URI scheme cursor not supported");
         }
         }
         c.style.cursor = curSave;
         c.style.cursor = curSave;
-    } catch (exc2) {
+    } catch (exc2) { 
         Util.Error("Data URI scheme cursor test exception: " + exc2);
         Util.Error("Data URI scheme cursor test exception: " + exc2);
         conf.cursor_uri = false;
         conf.cursor_uri = false;
     }
     }
 
 
-    Canvas.colourMap = [];
-    Canvas.prevStyle = "";
-    Canvas.focused = true;
+    conf.focused = true;
 
 
     Util.Debug("<< Canvas.init");
     Util.Debug("<< Canvas.init");
-    return true;
-},
+    return that ;
+}
+
+/* Translate DOM key event to keysym value */
+function getKeysym(e) {
+    var evt, keysym;
+    evt = (e ? e : window.event);
+
+    /* Remap modifier and special keys */
+    switch ( evt.keyCode ) {
+        case 8         : keysym = 0xFF08; break; // BACKSPACE
+        case 9         : keysym = 0xFF09; break; // TAB
+        case 13        : keysym = 0xFF0D; break; // ENTER
+        case 27        : keysym = 0xFF1B; break; // ESCAPE
+        case 45        : keysym = 0xFF63; break; // INSERT
+        case 46        : keysym = 0xFFFF; break; // DELETE
+        case 36        : keysym = 0xFF50; break; // HOME
+        case 35        : keysym = 0xFF57; break; // END
+        case 33        : keysym = 0xFF55; break; // PAGE_UP
+        case 34        : keysym = 0xFF56; break; // PAGE_DOWN
+        case 37        : keysym = 0xFF51; break; // LEFT
+        case 38        : keysym = 0xFF52; break; // UP
+        case 39        : keysym = 0xFF53; break; // RIGHT
+        case 40        : keysym = 0xFF54; break; // DOWN
+        case 112       : keysym = 0xFFBE; break; // F1
+        case 113       : keysym = 0xFFBF; break; // F2
+        case 114       : keysym = 0xFFC0; break; // F3
+        case 115       : keysym = 0xFFC1; break; // F4
+        case 116       : keysym = 0xFFC2; break; // F5
+        case 117       : keysym = 0xFFC3; break; // F6
+        case 118       : keysym = 0xFFC4; break; // F7
+        case 119       : keysym = 0xFFC5; break; // F8
+        case 120       : keysym = 0xFFC6; break; // F9
+        case 121       : keysym = 0xFFC7; break; // F10
+        case 122       : keysym = 0xFFC8; break; // F11
+        case 123       : keysym = 0xFFC9; break; // F12
+        case 16        : keysym = 0xFFE1; break; // SHIFT
+        case 17        : keysym = 0xFFE3; break; // CONTROL
+        //case 18        : keysym = 0xFFE7; break; // Left Meta (Mac Option)
+        case 18        : keysym = 0xFFE9; break; // Left ALT (Mac Command)
+        default        : keysym = evt.keyCode; break;
+    }
+
+    /* Remap symbols */
+    switch (keysym) {
+        case 186       : keysym = 59; break; // ;  (IE)
+        case 187       : keysym = 61; break; // =  (IE)
+        case 188       : keysym = 44; break; // ,  (Mozilla, IE)
+        case 109       :                     // -  (Mozilla)
+            if (Util.Engine.gecko) {
+                         keysym = 45; }
+                                      break;
+        case 189       : keysym = 45; break; // -  (IE)
+        case 190       : keysym = 46; break; // .  (Mozilla, IE)
+        case 191       : keysym = 47; break; // /  (Mozilla, IE)
+        case 192       : keysym = 96; break; // `  (Mozilla, IE)
+        case 219       : keysym = 91; break; // [  (Mozilla, IE)
+        case 220       : keysym = 92; break; // \  (Mozilla, IE)
+        case 221       : keysym = 93; break; // ]  (Mozilla, IE)
+        case 222       : keysym = 39; break; // '  (Mozilla, IE)
+    }
     
     
+    /* Remap shifted and unshifted keys */
+    if (!!evt.shiftKey) {
+        switch (keysym) {
+            case 48        : keysym = 41 ; break; // )  (shifted 0)
+            case 49        : keysym = 33 ; break; // !  (shifted 1)
+            case 50        : keysym = 64 ; break; // @  (shifted 2)
+            case 51        : keysym = 35 ; break; // #  (shifted 3)
+            case 52        : keysym = 36 ; break; // $  (shifted 4)
+            case 53        : keysym = 37 ; break; // %  (shifted 5)
+            case 54        : keysym = 94 ; break; // ^  (shifted 6)
+            case 55        : keysym = 38 ; break; // &  (shifted 7)
+            case 56        : keysym = 42 ; break; // *  (shifted 8)
+            case 57        : keysym = 40 ; break; // (  (shifted 9)
 
 
-start: function (keyPress, mouseButton, mouseMove) {
-    var c;
-    Util.Debug(">> Canvas.start");
+            case 59        : keysym = 58 ; break; // :  (shifted `)
+            case 61        : keysym = 43 ; break; // +  (shifted ;)
+            case 44        : keysym = 60 ; break; // <  (shifted ,)
+            case 45        : keysym = 95 ; break; // _  (shifted -)
+            case 46        : keysym = 62 ; break; // >  (shifted .)
+            case 47        : keysym = 63 ; break; // ?  (shifted /)
+            case 96        : keysym = 126; break; // ~  (shifted `)
+            case 91        : keysym = 123; break; // {  (shifted [)
+            case 92        : keysym = 124; break; // |  (shifted \)
+            case 93        : keysym = 125; break; // }  (shifted ])
+            case 39        : keysym = 34 ; break; // "  (shifted ')
+        }
+    } else if ((keysym >= 65) && (keysym <=90)) {
+        /* Remap unshifted A-Z */
+        keysym += 32;
+    } 
 
 
-    c = $(Canvas.id);
-    Canvas.keyPress = keyPress || null;
-    Canvas.mouseButton = mouseButton || null;
-    Canvas.mouseMove = mouseMove || null;
+    return keysym;
+}
 
 
-    Util.addEvent(document, 'keydown', Canvas.onKeyDown);
-    Util.addEvent(document, 'keyup', Canvas.onKeyUp);
-    Util.addEvent(c, 'mousedown', Canvas.onMouseDown);
-    Util.addEvent(c, 'mouseup', Canvas.onMouseUp);
-    Util.addEvent(c, 'mousemove', Canvas.onMouseMove);
-    Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
-            Canvas.onMouseWheel);
+function onMouseButton(e, down) {
+    var evt, pos, bmask;
+    if (! conf.focused) {
+        return true;
+    }
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    bmask = 1 << evt.button;
+    //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
+    if (c_mouseButton) {
+        c_mouseButton(pos.x, pos.y, down, bmask);
+    }
+    Util.stopEvent(e);
+    return false;
+}
 
 
-    /* Work around right and middle click browser behaviors */
-    Util.addEvent(document, 'click', Canvas.onMouseDisable);
-    Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
+function onMouseDown(e) {
+    onMouseButton(e, 1);
+}
 
 
-    Util.Debug("<< Canvas.start");
-},
+function onMouseUp(e) {
+    onMouseButton(e, 0);
+}
+
+function onMouseWheel(e) {
+    var evt, pos, bmask, wheelData;
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
+    if (wheelData > 0) {
+        bmask = 1 << 3;
+    } else {
+        bmask = 1 << 4;
+    }
+    //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
+    if (c_mouseButton) {
+        c_mouseButton(pos.x, pos.y, 1, bmask);
+        c_mouseButton(pos.x, pos.y, 0, bmask);
+    }
+    Util.stopEvent(e);
+    return false;
+}
+
+function onMouseMove(e) {
+    var evt, pos;
+    evt = (e ? e : window.event);
+    pos = Util.getEventPosition(e, conf.target, conf.scale);
+    //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
+    if (c_mouseMove) {
+        c_mouseMove(pos.x, pos.y);
+    }
+}
 
 
-clear: function () {
-    Canvas.resize(640, 20);
-    Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy);
-},
+function onKeyDown(e) {
+    //Util.Debug("keydown: " + getKeysym(e));
+    if (! conf.focused) {
+        return true;
+    }
+    if (c_keyPress) {
+        c_keyPress(getKeysym(e), 1);
+    }
+    Util.stopEvent(e);
+    return false;
+}
 
 
-resize: function (width, height, true_color) {
-    var c = $(Canvas.id);
+function onKeyUp(e) {
+    //Util.Debug("keyup: " + getKeysym(e));
+    if (! conf.focused) {
+        return true;
+    }
+    if (c_keyPress) {
+        c_keyPress(getKeysym(e), 0);
+    }
+    Util.stopEvent(e);
+    return false;
+}
 
 
-    if (typeof true_color !== "undefined") {
-        Canvas.true_color = true_color;
+function onMouseDisable(e) {
+    var evt, pos;
+    if (! conf.focused) {
+        return true;
+    }
+    evt = (e ? e : window.event);
+    pos = Util.getPosition(conf.target);
+    /* Stop propagation if inside canvas area */
+    if ((evt.clientX >= pos.x) &&
+        (evt.clientY >= pos.y) &&
+        (evt.clientX < (pos.x + c_width)) &&
+        (evt.clientY < (pos.y + c_height))) {
+        //Util.Debug("mouse event disabled");
+        Util.stopEvent(e);
+        return false;
     }
     }
+    //Util.Debug("mouse event not disabled");
+    return true;
+}
 
 
-    c.width = width;
-    c.height = height;
+//
+// Public API interface functions
+//
+
+that.getContext = function () {
+    return conf.ctx;
+};
+
+that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) {
+    var c;
+    Util.Debug(">> Canvas.start");
 
 
-    Canvas.c_wx = c.offsetWidth;
-    Canvas.c_wy = c.offsetHeight;
+    c = conf.target;
+    c_keyPress = keyPressFunc || null;
+    c_mouseButton = mouseButtonFunc || null;
+    c_mouseMove = mouseMoveFunc || null;
 
 
-    //Canvas.rescale(Canvas.scale);
-},
+    Util.addEvent(document, 'keydown', onKeyDown);
+    Util.addEvent(document, 'keyup', onKeyUp);
+    Util.addEvent(c, 'mousedown', onMouseDown);
+    Util.addEvent(c, 'mouseup', onMouseUp);
+    Util.addEvent(c, 'mousemove', onMouseMove);
+    Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+            onMouseWheel);
+
+    /* Work around right and middle click browser behaviors */
+    Util.addEvent(document, 'click', onMouseDisable);
+    Util.addEvent(document.body, 'contextmenu', onMouseDisable);
+
+    Util.Debug("<< Canvas.start");
+};
 
 
-rescale: function (factor) {
+that.rescale = function(factor) {
     var c, tp, x, y, 
     var c, tp, x, y, 
         properties = ['transform', 'WebkitTransform', 'MozTransform', null];
         properties = ['transform', 'WebkitTransform', 'MozTransform', null];
-    c = $(Canvas.id);
-    while (tp = properties.shift()) {
-        if (typeof c.style[tp] != 'undefined') {
+    c = conf.target;
+    tp = properties.shift();
+    while (tp) {
+        if (typeof c.style[tp] !== 'undefined') {
             break;
             break;
         }
         }
+        tp = properties.shift();
     }
     }
 
 
     if (tp === null) {
     if (tp === null) {
@@ -278,36 +418,83 @@ rescale: function (factor) {
         return;
         return;
     }
     }
 
 
-    if (Canvas.scale === factor) {
+    if (conf.scale === factor) {
         //Util.Debug("Canvas already scaled to '" + factor + "'");
         //Util.Debug("Canvas already scaled to '" + factor + "'");
         return;
         return;
     }
     }
 
 
-    Canvas.scale = factor;
+    conf.scale = factor;
     x = c.width - c.width * factor;
     x = c.width - c.width * factor;
     y = c.height - c.height * factor;
     y = c.height - c.height * factor;
-    c.style[tp] = "scale(" + Canvas.scale + ") translate(-" + x + "px, -" + y + "px)";
-},
-
-stop: function () {
-    var c = $(Canvas.id);
-    Util.removeEvent(document, 'keydown', Canvas.onKeyDown);
-    Util.removeEvent(document, 'keyup', Canvas.onKeyUp);
-    Util.removeEvent(c, 'mousedown', Canvas.onMouseDown);
-    Util.removeEvent(c, 'mouseup', Canvas.onMouseUp);
-    Util.removeEvent(c, 'mousemove', Canvas.onMouseMove);
+    c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
+};
+
+that.resize = function(width, height, true_color) {
+    var c = conf.target;
+
+    if (typeof true_color !== "undefined") {
+        conf.true_color = true_color;
+    }
+
+    c.width = width;
+    c.height = height;
+
+    c_width  = c.offsetWidth;
+    c_height = c.offsetHeight;
+
+    that.rescale(conf.scale);
+};
+
+that.clear = function() {
+    that.resize(640, 20);
+    conf.ctx.clearRect(0, 0, c_width, c_height);
+};
+
+that.stop = function() {
+    var c = conf.target;
+    Util.removeEvent(document, 'keydown', onKeyDown);
+    Util.removeEvent(document, 'keyup', onKeyUp);
+    Util.removeEvent(c, 'mousedown', onMouseDown);
+    Util.removeEvent(c, 'mouseup', onMouseUp);
+    Util.removeEvent(c, 'mousemove', onMouseMove);
     Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
     Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
-            Canvas.onMouseWheel);
+            onMouseWheel);
 
 
     /* Work around right and middle click browser behaviors */
     /* Work around right and middle click browser behaviors */
-    Util.removeEvent(document, 'click', Canvas.onMouseDisable);
-    Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
+    Util.removeEvent(document, 'click', onMouseDisable);
+    Util.removeEvent(document.body, 'contextmenu', onMouseDisable);
 
 
     // Turn off cursor rendering
     // Turn off cursor rendering
-    if (Canvas.cursor_uri) {
+    if (conf.cursor_uri) {
         c.style.cursor = "default";
         c.style.cursor = "default";
     }
     }
-},
+};
+
+setFillColor = function(color) {
+    var rgb, newStyle;
+    if (conf.true_color) {
+        rgb = color;
+    } else {
+        rgb = conf.colourMap[color[0]];
+    }
+    if (newStyle !== c_prevStyle) {
+        newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+        conf.ctx.fillStyle = newStyle;
+        c_prevStyle = newStyle;
+    }
+};
+that.setFillColor = setFillColor;
+
+fillRect = function(x, y, width, height, color) {
+    setFillColor(color);
+    conf.ctx.fillRect(x, y, width, height);
+};
+that.fillRect = fillRect;
+
+that.copyImage = function(old_x, old_y, new_x, new_y, width, height) {
+    conf.ctx.drawImage(conf.target, old_x, old_y, width, height,
+                                       new_x, new_y, width, height);
+};
 
 
 /*
 /*
  * Tile rendering functions optimized for rendering engines.
  * Tile rendering functions optimized for rendering engines.
@@ -316,16 +503,16 @@ stop: function () {
  *   faster than direct Canvas fillStyle, fillRect rendering. In
  *   faster than direct Canvas fillStyle, fillRect rendering. In
  *   gecko, Javascript array handling is much slower.
  *   gecko, Javascript array handling is much slower.
  */
  */
-getTile: function(x, y, width, height, color) {
+that.getTile = function(x, y, width, height, color) {
     var img, data, p, rgb, red, green, blue, j, i;
     var img, data, p, rgb, red, green, blue, j, i;
     img = {'x': x, 'y': y, 'width': width, 'height': height,
     img = {'x': x, 'y': y, 'width': width, 'height': height,
            'data': []};
            'data': []};
-    if (Canvas.prefer_js) {
+    if (conf.prefer_js) {
         data = img.data;
         data = img.data;
-        if (Canvas.true_color) {
+        if (conf.true_color) {
             rgb = color;
             rgb = color;
         } else {
         } else {
-            rgb = Canvas.colourMap[color[0]];
+            rgb = conf.colourMap[color[0]];
         }
         }
         red = rgb[0];
         red = rgb[0];
         green = rgb[1];
         green = rgb[1];
@@ -340,20 +527,20 @@ getTile: function(x, y, width, height, color) {
             }   
             }   
         } 
         } 
     } else {
     } else {
-        Canvas.fillRect(x, y, width, height, color);
+        fillRect(x, y, width, height, color);
     }
     }
     return img;
     return img;
-},
+};
 
 
-setSubTile: function(img, x, y, w, h, color) {
+that.setSubTile = function(img, x, y, w, h, color) {
     var data, p, rgb, red, green, blue, width, j, i;
     var data, p, rgb, red, green, blue, width, j, i;
-    if (Canvas.prefer_js) {
+    if (conf.prefer_js) {
         data = img.data;
         data = img.data;
         width = img.width;
         width = img.width;
-        if (Canvas.true_color) {
+        if (conf.true_color) {
             rgb = color;
             rgb = color;
         } else {
         } else {
-            rgb = Canvas.colourMap[color[0]];
+            rgb = conf.colourMap[color[0]];
         }
         }
         red = rgb[0];
         red = rgb[0];
         green = rgb[1];
         green = rgb[1];
@@ -368,31 +555,28 @@ setSubTile: function(img, x, y, w, h, color) {
             }   
             }   
         } 
         } 
     } else {
     } else {
-        Canvas.fillRect(img.x + x, img.y + y, w, h, color);
+        fillRect(img.x + x, img.y + y, w, h, color);
     }
     }
-},
+};
 
 
-putTile: function(img) {
-    if (Canvas.prefer_js) {
-        Canvas._rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
+that.putTile = function(img) {
+    if (conf.prefer_js) {
+        that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
     } else {
     } else {
         // No-op, under gecko already done by setSubTile
         // No-op, under gecko already done by setSubTile
     }
     }
-},
+};
 
 
-_imageDataGet: function(width, height) {
-    return Canvas.ctx.getImageData(0, 0, width, height);
-},
-_imageDataCreate: function(width, height) {
-    return Canvas.ctx.createImageData(width, height);
-},
-_imageDataRaw: function(width, height) {
-    return {'data': [], 'width': width, 'height': height};
-},
+that.imageDataGet = function(width, height) {
+    return conf.ctx.getImageData(0, 0, width, height);
+};
+that.imageDataCreate = function(width, height) {
+    return conf.ctx.createImageData(width, height);
+};
 
 
-_rgbxImageData: function(x, y, width, height, arr, offset) {
+that.rgbxImageData = function(x, y, width, height, arr, offset) {
     var img, i, j, data;
     var img, i, j, data;
-    img = Canvas._imageData(width, height);
+    img = that.imageData(width, height);
     data = img.data;
     data = img.data;
     for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
     for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
         data[i + 0] = arr[j + 0];
         data[i + 0] = arr[j + 0];
@@ -400,27 +584,27 @@ _rgbxImageData: function(x, y, width, height, arr, offset) {
         data[i + 2] = arr[j + 2];
         data[i + 2] = arr[j + 2];
         data[i + 3] = 255; // Set Alpha
         data[i + 3] = 255; // Set Alpha
     }
     }
-    Canvas.ctx.putImageData(img, x, y);
-},
+    conf.ctx.putImageData(img, x, y);
+};
 
 
 // really slow fallback if we don't have imageData
 // really slow fallback if we don't have imageData
-_rgbxImageFill: function(x, y, width, height, arr, offset) {
+that.rgbxImageFill = function(x, y, width, height, arr, offset) {
     var i, j, sx = 0, sy = 0;
     var i, j, sx = 0, sy = 0;
     for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
     for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
-        Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]);
+        fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]);
         sx += 1;
         sx += 1;
         if ((sx % width) === 0) {
         if ((sx % width) === 0) {
             sx = 0;
             sx = 0;
             sy += 1;
             sy += 1;
         }
         }
     }
     }
-},
+};
 
 
-_cmapImageData: function(x, y, width, height, arr, offset) {
+that.cmapImageData = function(x, y, width, height, arr, offset) {
     var img, i, j, data, rgb, cmap;
     var img, i, j, data, rgb, cmap;
-    img = Canvas._imageData(width, height);
+    img = that.imageData(width, height);
     data = img.data;
     data = img.data;
-    cmap = Canvas.colourMap;
+    cmap = conf.colourMap;
     for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
     for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
         rgb = cmap[arr[j]];
         rgb = cmap[arr[j]];
         data[i + 0] = rgb[0];
         data[i + 0] = rgb[0];
@@ -428,168 +612,47 @@ _cmapImageData: function(x, y, width, height, arr, offset) {
         data[i + 2] = rgb[2];
         data[i + 2] = rgb[2];
         data[i + 3] = 255; // Set Alpha
         data[i + 3] = 255; // Set Alpha
     }
     }
-    Canvas.ctx.putImageData(img, x, y);
-},
+    conf.ctx.putImageData(img, x, y);
+};
 
 
-_cmapImageFill: function(x, y, width, height, arr, offset) {
+that.cmapImageFill = function(x, y, width, height, arr, offset) {
     var i, j, sx = 0, sy = 0, cmap;
     var i, j, sx = 0, sy = 0, cmap;
-    cmap = Canvas.colourMap;
+    cmap = conf.colourMap;
     for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
     for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
-        Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
+        fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
         sx += 1;
         sx += 1;
         if ((sx % width) === 0) {
         if ((sx % width) === 0) {
             sx = 0;
             sx = 0;
             sy += 1;
             sy += 1;
         }
         }
     }
     }
-},
+};
 
 
 
 
-blitImage: function(x, y, width, height, arr, offset) {
-    if (Canvas.true_color) {
-        Canvas._rgbxImage(x, y, width, height, arr, offset);
+that.blitImage = function(x, y, width, height, arr, offset) {
+    if (conf.true_color) {
+        that.rgbxImage(x, y, width, height, arr, offset);
     } else {
     } else {
-        Canvas._cmapImage(x, y, width, height, arr, offset);
+        that.cmapImage(x, y, width, height, arr, offset);
     }
     }
-},
+};
 
 
-blitStringImage: function(str, x, y) {
+that.blitStringImage = function(str, x, y) {
     var img = new Image();
     var img = new Image();
-    img.onload = function () { Canvas.ctx.drawImage(img, x, y); };
+    img.onload = function () { conf.ctx.drawImage(img, x, y); };
     img.src = str;
     img.src = str;
-},
-
-setFillColor: function(color) {
-    var rgb, newStyle;
-    if (Canvas.true_color) {
-        rgb = color;
-    } else {
-        rgb = Canvas.colourMap[color[0]];
-    }
-    if (newStyle !== Canvas.prevStyle) {
-        newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
-        Canvas.ctx.fillStyle = newStyle;
-        Canvas.prevStyle = newStyle;
-    }
-},
-
-fillRect: function(x, y, width, height, color) {
-    Canvas.setFillColor(color);
-    Canvas.ctx.fillRect(x, y, width, height);
-},
-
-copyImage: function(old_x, old_y, new_x, new_y, width, height) {
-    Canvas.ctx.drawImage($(Canvas.id), old_x, old_y, width, height,
-                                       new_x, new_y, width, height);
-},
-
-/* Translate DOM key event to keysym value */
-getKeysym: function(e) {
-    var evt, keysym;
-    evt = (e ? e : window.event);
-
-    /* Remap modifier and special keys */
-    switch ( evt.keyCode ) {
-        case 8         : keysym = 0xFF08; break; // BACKSPACE
-        case 9         : keysym = 0xFF09; break; // TAB
-        case 13        : keysym = 0xFF0D; break; // ENTER
-        case 27        : keysym = 0xFF1B; break; // ESCAPE
-        case 45        : keysym = 0xFF63; break; // INSERT
-        case 46        : keysym = 0xFFFF; break; // DELETE
-        case 36        : keysym = 0xFF50; break; // HOME
-        case 35        : keysym = 0xFF57; break; // END
-        case 33        : keysym = 0xFF55; break; // PAGE_UP
-        case 34        : keysym = 0xFF56; break; // PAGE_DOWN
-        case 37        : keysym = 0xFF51; break; // LEFT
-        case 38        : keysym = 0xFF52; break; // UP
-        case 39        : keysym = 0xFF53; break; // RIGHT
-        case 40        : keysym = 0xFF54; break; // DOWN
-        case 112       : keysym = 0xFFBE; break; // F1
-        case 113       : keysym = 0xFFBF; break; // F2
-        case 114       : keysym = 0xFFC0; break; // F3
-        case 115       : keysym = 0xFFC1; break; // F4
-        case 116       : keysym = 0xFFC2; break; // F5
-        case 117       : keysym = 0xFFC3; break; // F6
-        case 118       : keysym = 0xFFC4; break; // F7
-        case 119       : keysym = 0xFFC5; break; // F8
-        case 120       : keysym = 0xFFC6; break; // F9
-        case 121       : keysym = 0xFFC7; break; // F10
-        case 122       : keysym = 0xFFC8; break; // F11
-        case 123       : keysym = 0xFFC9; break; // F12
-        case 16        : keysym = 0xFFE1; break; // SHIFT
-        case 17        : keysym = 0xFFE3; break; // CONTROL
-        //case 18        : keysym = 0xFFE7; break; // Left Meta (Mac Option)
-        case 18        : keysym = 0xFFE9; break; // Left ALT (Mac Command)
-        default        : keysym = evt.keyCode; break;
-    }
-
-    /* Remap symbols */
-    switch (keysym) {
-        case 186       : keysym = 59; break; // ;  (IE)
-        case 187       : keysym = 61; break; // =  (IE)
-        case 188       : keysym = 44; break; // ,  (Mozilla, IE)
-        case 109       :                     // -  (Mozilla)
-            if (Util.Engine.gecko) {
-                         keysym = 45; }
-                                      break;
-        case 189       : keysym = 45; break; // -  (IE)
-        case 190       : keysym = 46; break; // .  (Mozilla, IE)
-        case 191       : keysym = 47; break; // /  (Mozilla, IE)
-        case 192       : keysym = 96; break; // `  (Mozilla, IE)
-        case 219       : keysym = 91; break; // [  (Mozilla, IE)
-        case 220       : keysym = 92; break; // \  (Mozilla, IE)
-        case 221       : keysym = 93; break; // ]  (Mozilla, IE)
-        case 222       : keysym = 39; break; // '  (Mozilla, IE)
-    }
-    
-    /* Remap shifted and unshifted keys */
-    if (!!evt.shiftKey) {
-        switch (keysym) {
-            case 48        : keysym = 41 ; break; // )  (shifted 0)
-            case 49        : keysym = 33 ; break; // !  (shifted 1)
-            case 50        : keysym = 64 ; break; // @  (shifted 2)
-            case 51        : keysym = 35 ; break; // #  (shifted 3)
-            case 52        : keysym = 36 ; break; // $  (shifted 4)
-            case 53        : keysym = 37 ; break; // %  (shifted 5)
-            case 54        : keysym = 94 ; break; // ^  (shifted 6)
-            case 55        : keysym = 38 ; break; // &  (shifted 7)
-            case 56        : keysym = 42 ; break; // *  (shifted 8)
-            case 57        : keysym = 40 ; break; // (  (shifted 9)
-
-            case 59        : keysym = 58 ; break; // :  (shifted `)
-            case 61        : keysym = 43 ; break; // +  (shifted ;)
-            case 44        : keysym = 60 ; break; // <  (shifted ,)
-            case 45        : keysym = 95 ; break; // _  (shifted -)
-            case 46        : keysym = 62 ; break; // >  (shifted .)
-            case 47        : keysym = 63 ; break; // ?  (shifted /)
-            case 96        : keysym = 126; break; // ~  (shifted `)
-            case 91        : keysym = 123; break; // {  (shifted [)
-            case 92        : keysym = 124; break; // |  (shifted \)
-            case 93        : keysym = 125; break; // }  (shifted ])
-            case 39        : keysym = 34 ; break; // "  (shifted ')
-        }
-    } else if ((keysym >= 65) && (keysym <=90)) {
-        /* Remap unshifted A-Z */
-        keysym += 32;
-    } 
-
-    return keysym;
-},
-
+};
 
 
-isCursor: function() {
-    return Canvas.cursor_uri;
-},
-changeCursor: function(pixels, mask, hotx, hoty, w, h) {
+that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
     var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y;
     var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y;
     //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
     //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
     
     
-    if (!Canvas.cursor_uri) {
+    if (conf.cursor_uri === false) {
         Util.Warn("changeCursor called but no cursor data URI support");
         Util.Warn("changeCursor called but no cursor data URI support");
         return;
         return;
     }
     }
 
 
-    cmap = Canvas.colourMap;
+    cmap = conf.colourMap;
     IHDRsz = 40;
     IHDRsz = 40;
     ANDsz = w * h * 4;
     ANDsz = w * h * 4;
     XORsz = Math.ceil( (w * h) / 8.0 );
     XORsz = Math.ceil( (w * h) / 8.0 );
@@ -623,12 +686,12 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) {
     cur.push32le(0);
     cur.push32le(0);
 
 
     // XOR/color data
     // XOR/color data
-    for (y = h-1; y >= 0; y--) {
-        for (x = 0; x < w; x++) {
+    for (y = h-1; y >= 0; y -= 1) {
+        for (x = 0; x < w; x += 1) {
             idx = y * Math.ceil(w / 8) + Math.floor(x/8);
             idx = y * Math.ceil(w / 8) + Math.floor(x/8);
             alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
             alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
 
 
-            if (Canvas.true_color) {
+            if (conf.true_color) {
                 idx = ((w * y) + x) * 4;
                 idx = ((w * y) + x) * 4;
                 cur.push(pixels[idx + 2]); // blue
                 cur.push(pixels[idx + 2]); // blue
                 cur.push(pixels[idx + 1]); // green
                 cur.push(pixels[idx + 1]); // green
@@ -646,16 +709,20 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) {
     }
     }
 
 
     // AND/bitmask data (ignored, just needs to be right size)
     // AND/bitmask data (ignored, just needs to be right size)
-    for (y = 0; y < h; y++) {
-        for (x = 0; x < Math.ceil(w / 8); x++) {
+    for (y = 0; y < h; y += 1) {
+        for (x = 0; x < Math.ceil(w / 8); x += 1) {
             cur.push(0x00);
             cur.push(0x00);
         }
         }
     }
     }
 
 
     url = "data:image/x-icon;base64," + Base64.encode(cur);
     url = "data:image/x-icon;base64," + Base64.encode(cur);
-    $(Canvas.id).style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
+    conf.target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
     //Util.Debug("<< changeCursor, cur.length: " + cur.length);
     //Util.Debug("<< changeCursor, cur.length: " + cur.length);
-}
-
 };
 };
 
 
+
+
+return constructor();  // Return the public API interface
+
+}  // End of Canvas()
+

+ 43 - 35
include/default_controls.js

@@ -6,6 +6,7 @@
  * See README.md for usage and integration instructions.
  * See README.md for usage and integration instructions.
  */
  */
 "use strict";
 "use strict";
+/*jslint white: false */
 /*global $, Util, RFB, Canvas, VNC_uri_prefix, Element, Fx */
 /*global $, Util, RFB, Canvas, VNC_uri_prefix, Element, Fx */
 
 
 var DefaultControls = {
 var DefaultControls = {
@@ -16,10 +17,6 @@ settingsOpen : false,
 load: function(target) {
 load: function(target) {
     var html, i, DC = DefaultControls, sheet, sheets, llevels;
     var html, i, DC = DefaultControls, sheet, sheets, llevels;
 
 
-    /* Handle state updates */
-    RFB.setUpdateState(DC.updateState);
-    RFB.setClipboardReceive(DC.clipReceive);
-
     /* Populate the 'target' DOM element with default controls */
     /* Populate the 'target' DOM element with default controls */
     if (!target) { target = 'vnc'; }
     if (!target) { target = 'vnc'; }
 
 
@@ -61,7 +58,7 @@ load: function(target) {
     html += '              <option value="default">default</option>';
     html += '              <option value="default">default</option>';
     sheet = Util.selectStylesheet();
     sheet = Util.selectStylesheet();
     sheets = Util.getStylesheets();
     sheets = Util.getStylesheets();
-    for (i = 0; i < sheets.length; i++) {
+    for (i = 0; i < sheets.length; i += 1) {
         html += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>';
         html += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>';
     }
     }
     html += '              </select> Style</li>';
     html += '              </select> Style</li>';
@@ -69,7 +66,7 @@ load: function(target) {
     // Logging selection dropdown
     // Logging selection dropdown
     html += '            <li><select id="VNC_logging" name="vncLogging">';
     html += '            <li><select id="VNC_logging" name="vncLogging">';
     llevels = ['error', 'warn', 'info', 'debug'];
     llevels = ['error', 'warn', 'info', 'debug'];
-    for (i = 0; i < llevels.length; i++) {
+    for (i = 0; i < llevels.length; i += 1) {
         html += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>';
         html += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>';
     }
     }
     html += '              </select> Logging</li>';
     html += '              </select> Logging</li>';
@@ -107,6 +104,8 @@ load: function(target) {
     DC.initSetting('logging', 'warn');
     DC.initSetting('logging', 'warn');
     Util.init_logging(DC.getSetting('logging'));
     Util.init_logging(DC.getSetting('logging'));
     DC.initSetting('stylesheet', 'default');
     DC.initSetting('stylesheet', 'default');
+
+    Util.selectStylesheet(null); // call twice to get around webkit bug
     Util.selectStylesheet(DC.getSetting('stylesheet'));
     Util.selectStylesheet(DC.getSetting('stylesheet'));
 
 
     /* Populate the controls if defaults are provided in the URL */
     /* Populate the controls if defaults are provided in the URL */
@@ -118,12 +117,19 @@ load: function(target) {
     DC.initSetting('true_color', true);
     DC.initSetting('true_color', true);
     DC.initSetting('cursor', true);
     DC.initSetting('cursor', true);
 
 
+    DC.rfb = RFB({'target': 'VNC_canvas',
+                  'updateState': DC.updateState,
+                  'clipboardReceive': DC.clipReceive});
+    DC.rfb.init();
+
+    // Unfocus clipboard when over the VNC area
     $('VNC_screen').onmousemove = function () {
     $('VNC_screen').onmousemove = function () {
-            // Unfocus clipboard when over the VNC area
-            if (! Canvas.focused) {
+            var canvas = DC.rfb.get_canvas();
+            if ((! canvas) || (! canvas.get_focused())) {
                 $('VNC_clipboard_text').blur();
                 $('VNC_clipboard_text').blur();
             }
             }
         };
         };
+
 },
 },
 
 
 // Read form control compatible setting from cookie
 // Read form control compatible setting from cookie
@@ -154,7 +160,7 @@ updateSetting: function(name, value) {
     if (ctrl.type === 'checkbox') {
     if (ctrl.type === 'checkbox') {
         ctrl.checked = value;
         ctrl.checked = value;
     } else if (typeof ctrl.options !== 'undefined') {
     } else if (typeof ctrl.options !== 'undefined') {
-        for (i = 0; i < ctrl.options.length; i++) {
+        for (i = 0; i < ctrl.options.length; i += 1) {
             if (ctrl.options[i].value === value) {
             if (ctrl.options[i].value === value) {
                 ctrl.selectedIndex = i;
                 ctrl.selectedIndex = i;
                 break;
                 break;
@@ -176,7 +182,7 @@ saveSetting: function(name) {
         val = ctrl.value;
         val = ctrl.value;
     }
     }
     Util.createCookie(name, val);
     Util.createCookie(name, val);
-    Util.Debug("Setting saved '" + name + "=" + val + "'");
+    //Util.Debug("Setting saved '" + name + "=" + val + "'");
     return val;
     return val;
 },
 },
 
 
@@ -190,7 +196,7 @@ initSetting: function(name, defVal) {
         val = Util.readCookie(name, defVal);
         val = Util.readCookie(name, defVal);
     }
     }
     DefaultControls.updateSetting(name, val);
     DefaultControls.updateSetting(name, val);
-    Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
+    //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
     return val;
     return val;
 },
 },
 
 
@@ -208,7 +214,7 @@ clickSettingsMenu: function() {
         DC.updateSetting('encrypt');
         DC.updateSetting('encrypt');
         DC.updateSetting('base64');
         DC.updateSetting('base64');
         DC.updateSetting('true_color');
         DC.updateSetting('true_color');
-        if (Canvas.isCursor()) {
+        if (DC.rfb.get_canvas().get_cursor_uri()) {
             DC.updateSetting('cursor');
             DC.updateSetting('cursor');
         } else {
         } else {
             DC.updateSetting('cursor', false);
             DC.updateSetting('cursor', false);
@@ -235,10 +241,11 @@ closeSettingsMenu: function() {
 
 
 // Disable/enable controls depending on connection state
 // Disable/enable controls depending on connection state
 settingsDisabled: function(disabled) {
 settingsDisabled: function(disabled) {
+    var DC = DefaultControls;
     $('VNC_encrypt').disabled = disabled;
     $('VNC_encrypt').disabled = disabled;
     $('VNC_base64').disabled = disabled;
     $('VNC_base64').disabled = disabled;
     $('VNC_true_color').disabled = disabled;
     $('VNC_true_color').disabled = disabled;
-    if (Canvas.isCursor()) {
+    if (DC.rfb && DC.rfb.get_canvas().get_cursor_uri()) {
         $('VNC_cursor').disabled = disabled;
         $('VNC_cursor').disabled = disabled;
     } else {
     } else {
         DefaultControls.updateSetting('cursor', false);
         DefaultControls.updateSetting('cursor', false);
@@ -248,12 +255,12 @@ settingsDisabled: function(disabled) {
 
 
 // Save/apply settings when 'Apply' button is pressed
 // Save/apply settings when 'Apply' button is pressed
 settingsApply: function() {
 settingsApply: function() {
-    Util.Debug(">> settingsApply");
+    //Util.Debug(">> settingsApply");
     var DC = DefaultControls;
     var DC = DefaultControls;
     DC.saveSetting('encrypt');
     DC.saveSetting('encrypt');
     DC.saveSetting('base64');
     DC.saveSetting('base64');
     DC.saveSetting('true_color');
     DC.saveSetting('true_color');
-    if (Canvas.isCursor()) {
+    if (DC.rfb.get_canvas().get_cursor_uri()) {
         DC.saveSetting('cursor');
         DC.saveSetting('cursor');
     }
     }
     DC.saveSetting('stylesheet');
     DC.saveSetting('stylesheet');
@@ -263,21 +270,21 @@ settingsApply: function() {
     Util.selectStylesheet(DC.getSetting('stylesheet'));
     Util.selectStylesheet(DC.getSetting('stylesheet'));
     Util.init_logging(DC.getSetting('logging'));
     Util.init_logging(DC.getSetting('logging'));
 
 
-    Util.Debug("<< settingsApply");
+    //Util.Debug("<< settingsApply");
 },
 },
 
 
 
 
 
 
 setPassword: function() {
 setPassword: function() {
-    RFB.sendPassword($('VNC_password').value);
+    DefaultControls.rfb.sendPassword($('VNC_password').value);
     return false;
     return false;
 },
 },
 
 
 sendCtrlAltDel: function() {
 sendCtrlAltDel: function() {
-    RFB.sendCtrlAltDel();
+    DefaultControls.rfb.sendCtrlAltDel();
 },
 },
 
 
-updateState: function(state, msg) {
+updateState: function(rfb, state, oldstate, msg) {
     var s, sb, c, cad, klass;
     var s, sb, c, cad, klass;
     s = $('VNC_status');
     s = $('VNC_status');
     sb = $('VNC_status_bar');
     sb = $('VNC_status_bar');
@@ -334,6 +341,13 @@ updateState: function(state, msg) {
 
 
 },
 },
 
 
+clipReceive: function(rfb, text) {
+    Util.Debug(">> DefaultControls.clipReceive: " + text.substr(0,40) + "...");
+    $('VNC_clipboard_text').value = text;
+    Util.Debug("<< DefaultControls.clipReceive");
+},
+
+
 connect: function() {
 connect: function() {
     var host, port, password, DC = DefaultControls;
     var host, port, password, DC = DefaultControls;
 
 
@@ -346,43 +360,37 @@ connect: function() {
         throw("Must set host and port");
         throw("Must set host and port");
     }
     }
 
 
-    RFB.setEncrypt(DC.getSetting('encrypt'));
-    RFB.setBase64(DC.getSetting('base64'));
-    RFB.setTrueColor(DC.getSetting('true_color'));
-    RFB.setCursor(DC.getSetting('cursor'));
+    DC.rfb.set_encrypt(DC.getSetting('encrypt'));
+    DC.rfb.set_b64encode(DC.getSetting('base64'));
+    DC.rfb.set_true_color(DC.getSetting('true_color'));
+    DC.rfb.set_local_cursor(DC.getSetting('cursor'));
 
 
-    RFB.connect(host, port, password);
+    DC.rfb.connect(host, port, password);
 },
 },
 
 
 disconnect: function() {
 disconnect: function() {
     DefaultControls.closeSettingsMenu();
     DefaultControls.closeSettingsMenu();
 
 
-    RFB.disconnect();
+    DefaultControls.rfb.disconnect();
 },
 },
 
 
 canvasBlur: function() {
 canvasBlur: function() {
-    Canvas.focused = false;
+    DefaultControls.rfb.get_canvas().set_focused(false);
 },
 },
 
 
 canvasFocus: function() {
 canvasFocus: function() {
-    Canvas.focused = true;
+    DefaultControls.rfb.get_canvas().set_focused(true);
 },
 },
 
 
 clipClear: function() {
 clipClear: function() {
     $('VNC_clipboard_text').value = "";
     $('VNC_clipboard_text').value = "";
-    RFB.clipboardPasteFrom("");
-},
-
-clipReceive: function(text) {
-    Util.Debug(">> DefaultControls.clipReceive: " + text.substr(0,40) + "...");
-    $('VNC_clipboard_text').value = text;
-    Util.Debug("<< DefaultControls.clipReceive");
+    DefaultControls.rfb.clipboardPasteFrom("");
 },
 },
 
 
 clipSend: function() {
 clipSend: function() {
     var text = $('VNC_clipboard_text').value;
     var text = $('VNC_clipboard_text').value;
     Util.Debug(">> DefaultControls.clipSend: " + text.substr(0,40) + "...");
     Util.Debug(">> DefaultControls.clipSend: " + text.substr(0,40) + "...");
-    RFB.clipboardPasteFrom(text);
+    DefaultControls.rfb.clipboardPasteFrom(text);
     Util.Debug("<< DefaultControls.clipSend");
     Util.Debug("<< DefaultControls.clipSend");
 }
 }
 
 

+ 1126 - 1098
include/rfb.js

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

+ 50 - 47
include/util.js

@@ -14,41 +14,6 @@
 var Util = {}, $;
 var Util = {}, $;
 
 
 
 
-/*
- * Logging/debug routines
- */
-
-Util.init_logging = function (level) {
-    if (typeof window.console === "undefined") {
-        if (typeof window.opera !== "undefined") {
-            window.console = {
-                'log'  : window.opera.postError,
-                'warn' : window.opera.postError,
-                'error': window.opera.postError };
-        } else {
-            window.console = {
-                'log'  : function(m) {},
-                'warn' : function(m) {},
-                'error': function(m) {}};
-        }
-    }
-
-    Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
-    switch (level) {
-        case 'debug': Util.Debug = function (msg) { console.log(msg); };
-        case 'info':  Util.Info  = function (msg) { console.log(msg); };
-        case 'warn':  Util.Warn  = function (msg) { console.warn(msg); };
-        case 'error': Util.Error = function (msg) { console.error(msg); };
-            break;
-        default:
-            throw("invalid logging type '" + level + "'");
-    }
-};
-// Initialize logging level
-Util.init_logging( (document.location.href.match(
-                    /logging=([A-Za-z0-9\._\-]*)/) ||
-                    ['', 'warn'])[1] );
-
 /*
 /*
  * Simple DOM selector by ID
  * Simple DOM selector by ID
  */
  */
@@ -138,6 +103,42 @@ Array.prototype.shiftBytes = function (len) {
  * ------------------------------------------------------
  * ------------------------------------------------------
  */
  */
 
 
+/*
+ * Logging/debug routines
+ */
+
+Util.init_logging = function (level) {
+    if (typeof window.console === "undefined") {
+        if (typeof window.opera !== "undefined") {
+            window.console = {
+                'log'  : window.opera.postError,
+                'warn' : window.opera.postError,
+                'error': window.opera.postError };
+        } else {
+            window.console = {
+                'log'  : function(m) {},
+                'warn' : function(m) {},
+                'error': function(m) {}};
+        }
+    }
+
+    Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
+    switch (level) {
+        case 'debug': Util.Debug = function (msg) { console.log(msg); };
+        case 'info':  Util.Info  = function (msg) { console.log(msg); };
+        case 'warn':  Util.Warn  = function (msg) { console.warn(msg); };
+        case 'error': Util.Error = function (msg) { console.error(msg); };
+        case 'none':
+            break;
+        default:
+            throw("invalid logging type '" + level + "'");
+    }
+};
+// Initialize logging level
+Util.init_logging( (document.location.href.match(
+                    /logging=([A-Za-z0-9\._\-]*)/) ||
+                    ['', 'warn'])[1] );
+
 Util.dirObj = function (obj, depth, parent) {
 Util.dirObj = function (obj, depth, parent) {
     var i, msg = "", val = "";
     var i, msg = "", val = "";
     if (! depth) { depth=2; }
     if (! depth) { depth=2; }
@@ -168,7 +169,7 @@ Util.getQueryVar = function(name, defVal) {
 };
 };
 
 
 // Set defaults for Crockford style function namespaces
 // Set defaults for Crockford style function namespaces
-Util.conf_default = function(cfg, api, v, val) {
+Util.conf_default = function(cfg, api, v, val, force_bool) {
     if (typeof cfg[v] === 'undefined') {
     if (typeof cfg[v] === 'undefined') {
         cfg[v] = val;
         cfg[v] = val;
     }
     }
@@ -181,6 +182,13 @@ Util.conf_default = function(cfg, api, v, val) {
     // Default setter
     // Default setter
     if (typeof api['set_' + v] === 'undefined') {
     if (typeof api['set_' + v] === 'undefined') {
         api['set_' + v] = function (val) {
         api['set_' + v] = function (val) {
+                if (force_bool) {
+                    if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
+                        val = false;
+                    } else {
+                        val = true;
+                    }
+                }
                 cfg[v] = val;
                 cfg[v] = val;
             };
             };
     }
     }
@@ -311,10 +319,9 @@ Util.createCookie = function(name,value,days) {
 };
 };
 
 
 Util.readCookie = function(name, defaultValue) {
 Util.readCookie = function(name, defaultValue) {
-    var nameEQ = name + "=";
-    var ca = document.cookie.split(';');
-    for(var i=0;i < ca.length;i++) {
-        var c = ca[i];
+    var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
+    for(i=0; i < ca.length; i += 1) {
+        c = ca[i];
         while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
         while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
         if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
         if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
     }
     }
@@ -330,7 +337,7 @@ Util.eraseCookie = function(name) {
  */
  */
 Util.getStylesheets = function() { var i, links, sheets = [];
 Util.getStylesheets = function() { var i, links, sheets = [];
     links = document.getElementsByTagName("link");
     links = document.getElementsByTagName("link");
-    for (i = 0; i < links.length; i++) {
+    for (i = 0; i < links.length; i += 1) {
         if (links[i].title &&
         if (links[i].title &&
             links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
             links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
             sheets.push(links[i]);
             sheets.push(links[i]);
@@ -346,19 +353,15 @@ Util.selectStylesheet = function(sheet) {
     if (typeof sheet === 'undefined') {
     if (typeof sheet === 'undefined') {
         sheet = 'default';
         sheet = 'default';
     }
     }
-    for (i=0; i < sheets.length; i++) {
+    for (i=0; i < sheets.length; i += 1) {
         link = sheets[i];
         link = sheets[i];
         if (link.title === sheet) {    
         if (link.title === sheet) {    
             Util.Debug("Using stylesheet " + sheet);
             Util.Debug("Using stylesheet " + sheet);
             link.disabled = false;
             link.disabled = false;
         } else {
         } else {
-            Util.Debug("Skipping stylesheet " + link.title);
+            //Util.Debug("Skipping stylesheet " + link.title);
             link.disabled = true;
             link.disabled = true;
         }
         }
     }
     }
     return sheet;
     return sheet;
 };
 };
-
-// call once to disable alternates and get around webkit bug
-Util.selectStylesheet(null);
-

+ 32 - 26
tests/canvas.html

@@ -46,14 +46,16 @@
         }
         }
 
 
         function test_functions () {
         function test_functions () {
-            var img, x, y;
-            Canvas.fillRect(0, 0, Canvas.c_wx, Canvas.c_wy, [240,240,240]);
+            var img, x, y, w, h, ctx = canvas.getContext();
+            w = canvas.get_width();
+            h = canvas.get_height();
+            canvas.fillRect(0, 0, w, h, [240,240,240]);
 
 
-            Canvas.blitStringImage("data:image/png;base64," + face64, 150, 10);
+            canvas.blitStringImage("data:image/png;base64," + face64, 150, 10);
 
 
             var himg = new Image();
             var himg = new Image();
             himg.onload = function () {
             himg.onload = function () {
-                Canvas.ctx.drawImage(himg, 200, 40); };
+                ctx.drawImage(himg, 200, 40); };
             himg.src = "face.png";
             himg.src = "face.png";
 
 
             /* Test array image data */
             /* Test array image data */
@@ -66,15 +68,14 @@
                     data[(y*50 + x)*4 + 3] = 255;
                     data[(y*50 + x)*4 + 3] = 255;
                 }
                 }
             }
             }
-            Canvas.blitImage(30, 10, 50, 50, data, 0);
+            canvas.blitImage(30, 10, 50, 50, data, 0);
 
 
-            //Canvas.prefer_js = false;
-            img = Canvas.getTile(5,5,16,16,[0,128,128]);
-            Canvas.putTile(img);
+            img = canvas.getTile(5,5,16,16,[0,128,128]);
+            canvas.putTile(img);
 
 
-            img = Canvas.getTile(90,15,16,16,[0,0,0]);
-            Canvas.setSubTile(img, 0,0,16,16,[128,128,0]);
-            Canvas.putTile(img);
+            img = canvas.getTile(90,15,16,16,[0,0,0]);
+            canvas.setSubTile(img, 0,0,16,16,[128,128,0]);
+            canvas.putTile(img);
         }
         }
 
 
         function begin () {
         function begin () {
@@ -85,30 +86,35 @@
         }
         }
 
 
         function start_delayed () {
         function start_delayed () {
+            var ret;
+
+            ret = canvas.set_prefer_js(true);
+            if (ret) {
+                message("Running test: prefer Javascript ops");
+                var time1 = run_test();
+                message("prefer Javascript ops: " + time1 + "ms total, " +
+                        (time1 / iterations) + "ms per frame");
+            } else {
+                message("Could not run: prefer Javascript ops");
+            }
 
 
-            message("Running test: prefer Javascript");
-            Canvas.prefer_js = true;
-            var time1 = run_test();
-            message("prefer Javascript: " + time1 + "ms total, " +
-                    (time1 / iterations) + "ms per frame");
-
+            canvas.set_prefer_js(false);
             message("Running test: prefer Canvas ops");
             message("Running test: prefer Canvas ops");
-            Canvas.prefer_js = false;
             var time2 = run_test();
             var time2 = run_test();
             message("prefer Canvas ops: " + time2 + "ms total, " +
             message("prefer Canvas ops: " + time2 + "ms total, " +
                     (time2 / iterations) + "ms per frame");
                     (time2 / iterations) + "ms per frame");
 
 
-            Canvas.resize(start_width, start_height, true);
+            canvas.resize(start_width, start_height, true);
             test_functions();
             test_functions();
             $('startButton').disabled = false;
             $('startButton').disabled = false;
-            $('startButton').value = "Start";
+            $('startButton').value = "Do Performance Test";
         }
         }
 
 
         function run_test () {
         function run_test () {
             var width, height;
             var width, height;
             width = $('width').value;
             width = $('width').value;
             height = $('height').value;
             height = $('height').value;
-            Canvas.resize(width, height);
+            canvas.resize(width, height);
             var color, start_time = (new Date()).getTime(), w, h;
             var color, start_time = (new Date()).getTime(), w, h;
             for (var i=0; i < iterations; i++) {
             for (var i=0; i < iterations; i++) {
                 color = [128, 128, (255 / iterations) * i, 0];
                 color = [128, 128, (255 / iterations) * i, 0];
@@ -116,9 +122,9 @@
                     for (var y=0; y < height; y = y + 16) {
                     for (var y=0; y < height; y = y + 16) {
                         w = Math.min(16, width - x);
                         w = Math.min(16, width - x);
                         h = Math.min(16, height - y);
                         h = Math.min(16, height - y);
-                        var tile = Canvas.getTile(x, y, w, h, color);
-                        Canvas.setSubTile(tile, 0, 0, w, h, color);
-                        Canvas.putTile(tile);
+                        var tile = canvas.getTile(x, y, w, h, color);
+                        canvas.setSubTile(tile, 0, 0, w, h, color);
+                        canvas.putTile(tile);
                     }
                     }
                 }
                 }
             }
             }
@@ -129,8 +135,8 @@
         window.onload = function() {
         window.onload = function() {
             message("in onload");
             message("in onload");
             $('iterations').value = 10;
             $('iterations').value = 10;
-            Canvas.init('canvas');
-            Canvas.resize(start_width, start_height, true);
+            canvas = Canvas({'target' : 'canvas'});
+            canvas.resize(start_width, start_height, true);
             message("Canvas initialized");
             message("Canvas initialized");
             test_functions();
             test_functions();
         }
         }

+ 3 - 3
tests/cursor.html

@@ -105,10 +105,10 @@
  
  
     window.onload = function() {
     window.onload = function() {
         debug("onload");
         debug("onload");
-        var cross, cursor, cursor64;
+        var canvas, cross, cursor, cursor64;
 
 
-        Canvas.init("testcanvas");
-        debug("Canvas.init() indicates Data URI cursor support is: " + Canvas.isCursor());
+        canvas = new Canvas({'target' : "testcanvas"});
+        debug("canvas indicates Data URI cursor support is: " + canvas.get_cursor_uri());
 
 
         $('button1').style.cursor="url(face.png), default";
         $('button1').style.cursor="url(face.png), default";
 
 

+ 4 - 3
tests/input.html

@@ -19,6 +19,7 @@
         src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
         src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
     -->
     -->
     <script src="include/util.js"></script>
     <script src="include/util.js"></script>
+    <script src="include/base64.js"></script>
     <script src="include/canvas.js"></script>
     <script src="include/canvas.js"></script>
     <script>
     <script>
         var msg_cnt = 0;
         var msg_cnt = 0;
@@ -51,9 +52,9 @@
         }
         }
 
 
         window.onload = function() {
         window.onload = function() {
-            Canvas.init('canvas');
-            Canvas.resize(width, height);
-            Canvas.start(keyPress, mouseButton, mouseMove);
+            var canvas = Canvas({'target' : 'canvas'});
+            canvas.resize(width, height, true);
+            canvas.start(keyPress, mouseButton, mouseMove);
             message("Canvas initialized");
             message("Canvas initialized");
         }
         }
     </script>
     </script>

+ 5 - 5
vnc.html

@@ -16,12 +16,12 @@ noVNC example: simple example using default controls
 
 
     <body>
     <body>
         <div id='vnc'>Loading</div>
         <div id='vnc'>Loading</div>
-    </body>
 
 
-    <script>
+        <script>
         window.onload = function () {
         window.onload = function () {
             DefaultControls.load('vnc');
             DefaultControls.load('vnc');
-            RFB.load();
-        }
-    </script>
+        };
+        </script>
+
+    </body>
 </html>
 </html>

+ 30 - 23
vnc_auto.html

@@ -7,8 +7,7 @@ Connect parameters are provided in query string:
 <html>
 <html>
     <head>
     <head>
         <title>VNC Client</title>
         <title>VNC Client</title>
-        <link rel="stylesheet" href="include/plain.css" TITLE="plain">
-        <link rel="Alternate StyleSheet" href="include/black.css" TITLE="Black">
+        <link rel="stylesheet" href="include/plain.css" title="plain">
         <!--
         <!--
         <script type='text/javascript' 
         <script type='text/javascript' 
             src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
             src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
@@ -19,31 +18,36 @@ Connect parameters are provided in query string:
     <body style="margin: 0px;">
     <body style="margin: 0px;">
         <div id="VNC_screen">
         <div id="VNC_screen">
             <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;">
             <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;">
-                <table border=0 width=100%><tr>
+                <table border=0 width="100%"><tr>
                     <td><div id="VNC_status">Loading</div></td>
                     <td><div id="VNC_status">Loading</div></td>
-                    <td width=1%><div id="VNC_buttons">
+                    <td width="1%"><div id="VNC_buttons">
                         <input type=button value="Send CtrlAltDel"
                         <input type=button value="Send CtrlAltDel"
-                            id="sendCtrlAltDelButton"
-                            onclick="sendCtrlAltDel();"></div></td>
+                            id="sendCtrlAltDelButton">
+                            </div></td>
                 </tr></table>
                 </tr></table>
             </div>
             </div>
             <canvas id="VNC_canvas" width="640px" height="20px">
             <canvas id="VNC_canvas" width="640px" height="20px">
                 Canvas not supported.
                 Canvas not supported.
             </canvas>
             </canvas>
         </div>
         </div>
-    </body>
 
 
-    <script>
+        <script>
+        /*jslint white: false */
+        /*global window, $, Util, RFB, */
+        "use strict";
+
+        var rfb;
+
         function setPassword() {
         function setPassword() {
-            RFB.sendPassword($('password_input').value);
+            rfb.sendPassword($('password_input').value);
             return false;
             return false;
         }
         }
         function sendCtrlAltDel() {
         function sendCtrlAltDel() {
-            RFB.sendCtrlAltDel();
+            rfb.sendCtrlAltDel();
             return false;
             return false;
         }
         }
-        function updateState(state, msg) {
-            var s, sb, klass, html;
+        function updateState(rfb, state, oldstate, msg) {
+            var s, sb, cad, klass;
             s = $('VNC_status');
             s = $('VNC_status');
             sb = $('VNC_status_bar');
             sb = $('VNC_status_bar');
             cad = $('sendCtrlAltDelButton');
             cad = $('sendCtrlAltDelButton');
@@ -64,8 +68,9 @@ Connect parameters are provided in query string:
                     msg += '  style="margin-bottom: 0px">';
                     msg += '  style="margin-bottom: 0px">';
                     msg += 'Password Required: ';
                     msg += 'Password Required: ';
                     msg += '<input type=password size=10 id="password_input" class="VNC_status">';
                     msg += '<input type=password size=10 id="password_input" class="VNC_status">';
-                    msg += '</form>';
-                    // Fall through
+                    msg += '<\/form>';
+                    klass = "VNC_status_warn";
+                    break;
                 default:
                 default:
                     klass = "VNC_status_warn";
                     klass = "VNC_status_warn";
             }
             }
@@ -82,6 +87,8 @@ Connect parameters are provided in query string:
         window.onload = function () {
         window.onload = function () {
             var host, port, password;
             var host, port, password;
 
 
+            $('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
+
             host = Util.getQueryVar('host', null);
             host = Util.getQueryVar('host', null);
             port = Util.getQueryVar('port', null);
             port = Util.getQueryVar('port', null);
             password = Util.getQueryVar('password', '');
             password = Util.getQueryVar('password', '');
@@ -91,15 +98,15 @@ Connect parameters are provided in query string:
                 return;
                 return;
             }
             }
 
 
-            RFB.setEncrypt(Util.getQueryVar('encrypt', true));
-            RFB.setBase64(Util.getQueryVar('base64', true));
-            RFB.setTrueColor(Util.getQueryVar('true_color', true));
-            RFB.setCursor(Util.getQueryVar('cursor', true));
-            RFB.setUpdateState(updateState);
+            rfb = new RFB({'encrypt':      Util.getQueryVar('encrypt', true),
+                       'b64encode':    Util.getQueryVar('base64', true),
+                       'true_color':   Util.getQueryVar('true_color', true),
+                       'local_cursor': Util.getQueryVar('cursor', true),
+                       'updateState':  updateState});
+            rfb.connect(host, port, password);
+        };
+        </script>
 
 
-            RFB.load();
-            RFB.connect(host, port, password);
-        }
-    </script>
+    </body>
 </html>
 </html>