Browse Source

API changes. Client cursor and settings menu.

The following API changes may affect integrators:

    - Settings have been moved out of the RFB.connect() call. Each
      setting now has it's own setter function: setEncrypt, setBase64,
      setTrueColor, setCursor.

    - Encrypt and cursor settings now default to on.

    - CSS changes:
        - VNC_status_bar for input buttons switched to a element class.

        - VNC_buttons split into VNC_buttons_right and
          VNC_buttons_left

        - New id styles for VNC_settings_menu and VNC_setting

Note: the encrypt, true_color and cursor, logging setting can all be
  set on load using query string variables (in addition to host, port
  and password).

Client cursor (cursor pseudo-encoding) support has been polished and
activated.

The RFB settings are now presented as radio button list items in
a drop-down "Settings" menu when using the default controls.

Also, in the settings menu is the ability to select between alternate
style-sheets.

Cookie and stylesheet selection support added to util.js.
Joel Martin 15 năm trước cách đây
mục cha
commit
da6dd8932e
12 tập tin đã thay đổi với 606 bổ sung120 xóa
  1. 3 0
      docs/TODO
  2. 27 5
      include/black.css
  3. 12 7
      include/canvas.js
  4. 232 26
      include/default_controls.js
  5. 21 3
      include/plain.css
  6. 100 27
      include/util.js
  7. 64 35
      include/vnc.js
  8. 14 10
      tests/canvas.html
  9. 123 0
      tests/cursor.html
  10. BIN
      tests/face.png
  11. 2 2
      vnc.html
  12. 8 5
      vnc_auto.html

+ 3 - 0
docs/TODO

@@ -14,6 +14,9 @@ Short Term:
   http://excanvas.sourceforge.net/
   http://code.google.com/p/fxcanvas/
 
+- Fix cursor URI detection in Arora:
+    - allows data URI, but doesn't actually work
+
 
 Medium Term:
 

+ 27 - 5
include/black.css

@@ -1,5 +1,4 @@
 body {
-  background: #ddd;
   margin: 0;
   font-size: 13px;
   color: #111;
@@ -56,7 +55,7 @@ body {
     margin: 0px;
     padding: 1em;
 }
-#VNC_status_bar input {
+.VNC_status_button {
     font-size: 10px;
     margin: 0px;
     padding: 0px;
@@ -64,24 +63,46 @@ body {
 #VNC_status {
     text-align: center;
 }
-#VNC_buttons {
-    text-align: right;
+#VNC_settings_menu {
+    display: none;
+    position: absolute;
+    width: 12em;
+    border: 1px solid #888;
+    background-color: #f0f2f6;
+    padding: 5px; margin: 3px;
+    z-index: 100; opacity: 1;
+    text-align: left; white-space: normal;
+}
+#VNC_settings_menu ul {
+    list-style: none;
+    margin: 0;
+    padding: 0;
 }
 
+.VNC_buttons_right {
+    text-align: right;
+}
+.VNC_buttons_left {
+    text-align: left;
+}
 .VNC_status_normal {
+  background: #111;
   color: #fff;
 }
 .VNC_status_error {
+  background: #111;
   color: #f44;
 }
 .VNC_status_warn {
+  background: #111;
   color: #ff4;
 }
+
 #VNC_screen {
   -webkit-border-radius: 10px;
   -moz-border-radius: 10px;
   border-radius: 10px;
-  background: #000;
+  background: #111;
   padding: 20px;
   margin: 0 auto;
   color: #FFF;
@@ -93,6 +114,7 @@ body {
   table-layout: auto;
 }
 #VNC_canvas {
+  background: #111;
   margin: 0 auto;
 }
 #VNC_clipboard {

+ 12 - 7
include/canvas.js

@@ -29,7 +29,7 @@ Canvas = {
 
 prefer_js    : false, // make private
 force_canvas : false, // make private
-cursor_uri   : true,  // make private, create getter
+cursor_uri   : true,  // make private
 
 true_color : false,
 colourMap  : [],
@@ -47,6 +47,9 @@ 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));
     bmask = 1 << evt.button;
@@ -122,6 +125,9 @@ onKeyUp : function (e) {
 
 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 */
@@ -208,7 +214,7 @@ init: function (id) {
         curDat.push(255);
     }
     curSave = c.style.cursor;
-    Canvas.setCursor(curDat, curDat, 2, 2, 8, 8);
+    Canvas.changeCursor(curDat, curDat, 2, 2, 8, 8);
     if (c.style.cursor) {
         Util.Info("Data URI scheme cursor supported");
     } else {
@@ -561,13 +567,12 @@ getKeysym: function(e) {
 isCursor: function() {
     return Canvas.cursor_uri;
 },
-
-setCursor: function(pixels, mask, hotx, hoty, w, h) {
+changeCursor: function(pixels, mask, hotx, hoty, w, h) {
     var cur = [], cmap, IHDRsz, ANDsz, XORsz, url, idx, x, y;
-    //Util.Debug(">> setCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
+    //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
     
     if (!Canvas.cursor_uri) {
-        Util.Warn("setCursor called but no cursor data URI support");
+        Util.Warn("changeCursor called but no cursor data URI support");
         return;
     }
 
@@ -636,7 +641,7 @@ setCursor: function(pixels, mask, hotx, hoty, w, h) {
 
     url = "data:image/x-icon;base64," + Base64.encode(cur);
     $(Canvas.id).style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
-    //Util.Debug("<< setCursor, cur.length: " + cur.length);
+    //Util.Debug("<< changeCursor, cur.length: " + cur.length);
 }
 
 };

+ 232 - 26
include/default_controls.js

@@ -10,12 +10,16 @@
 
 var DefaultControls = {
 
+settingsOpen : false,
+
+// Render default controls and initialize settings menu
 load: function(target) {
-    var url, html;
+    var url, html, encrypt, cursor, base64, i, sheet, sheets,
+        DC = DefaultControls;
 
     /* Handle state updates */
-    RFB.setUpdateState(DefaultControls.updateState);
-    RFB.setClipboardReceive(DefaultControls.clipReceive);
+    RFB.setUpdateState(DC.updateState);
+    RFB.setClipboardReceive(DC.clipReceive);
 
     /* Populate the 'target' DOM element with default controls */
     if (!target) { target = 'vnc'; }
@@ -27,10 +31,6 @@ load: function(target) {
     html += '    <li>Port: <input id="VNC_port"></li>';
     html += '    <li>Password: <input id="VNC_password"';
     html += '        type="password"></li>';
-    html += '    <li>Encrypt: <input id="VNC_encrypt"';
-    html += '        type="checkbox"></li>';
-    html += '    <li>True Color: <input id="VNC_true_color"';
-    html += '        type="checkbox" checked></li>';
     html += '    <li><input id="VNC_connect_button" type="button"';
     html += '        value="Loading" disabled></li>';
     html += '  </ul>';
@@ -39,8 +39,49 @@ load: function(target) {
     html += '  <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;">';
     html += '    <table border=0 width=100%><tr>';
     html += '      <td><div id="VNC_status">Loading</div></td>';
-    html += '      <td width=10%><div id="VNC_buttons">';
-    html += '        <input type=button value="Send CtrlAltDel"';
+    html += '      <td width=1%><div class="VNC_buttons_right">';
+    html += '        <input type=button class="VNC_status_button" value="Settings"';
+    html += '          id="menuButton"';
+    html += '          onclick="DefaultControls.clickSettingsMenu();">';
+    html += '        <span id="VNC_settings_menu"';
+    html += '          onmouseover="DefaultControls.canvasBlur();"';
+    html += '          onmouseout="DefaultControls.canvasFocus();">';
+    html += '          <ul>';
+    html += '            <li><input id="VNC_encrypt"';
+    html += '                type="checkbox" checked> Encrypt</li>';
+    html += '            <li><input id="VNC_base64"';
+    html += '                type="checkbox" checked> Base64 Encode</li>';
+    html += '            <li><input id="VNC_true_color"';
+    html += '                type="checkbox" checked> True Color</li>';
+    html += '            <li><input id="VNC_cursor"';
+    html += '                type="checkbox" checked> Local Cursor</li>';
+    html += '            <hr>';
+
+    // Stylesheet selection dropdown
+    html += '            <li><select id="VNC_stylesheet" name="vncStyle">';
+    html += '              <option value="default">default</option>';
+    sheet = Util.selectStylesheet();
+    sheets = Util.getStylesheets();
+    for (i = 0; i < sheets.length; i++) {
+        html += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>';
+    }
+    html += '              </select> Style</li>';
+
+    // Logging selection dropdown
+    html += '            <li><select id="VNC_logging" name="vncLogging">';
+    llevels = ['error', 'warn', 'info', 'debug'];
+    for (i = 0; i < llevels.length; i++) {
+        html += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>';
+    }
+    html += '              </select> Logging</li>';
+
+    html += '            <hr>';
+    html += '            <li><input type="button" id="VNC_apply" value="Apply"';
+    html += '                onclick="DefaultControls.settingsApply()"></li>';
+    html += '          </ul>';
+    html += '        </span></div></td>';
+    html += '      <td width=1%><div class="VNC_buttons_right">';
+    html += '        <input type=button class="VNC_status_button" value="Send CtrlAltDel"';
     html += '          id="sendCtrlAltDelButton"';
     html += '          onclick="DefaultControls.sendCtrlAltDel();"></div></td>';
     html += '    </tr></table>';
@@ -57,22 +98,26 @@ load: function(target) {
     html += '      onclick="DefaultControls.clipClear();">';
     html += '  <br>';
     html += '  <textarea id="VNC_clipboard_text" cols=80 rows=5';
-    html += '    onfocus="DefaultControls.clipFocus();"';
-    html += '    onblur="DefaultControls.clipBlur();"';
+    html += '    onfocus="DefaultControls.canvasBlur();"';
+    html += '    onblur="DefaultControls.canvasFocus();"';
     html += '    onchange="DefaultControls.clipSend();"></textarea>';
     html += '</div>';
     $(target).innerHTML = html;
 
+    // Settings with immediate effects
+    DC.initSetting('logging', 'default');
+    Util.init_logging(DC.getSetting('logging'));
+    DC.initSetting('stylesheet', 'default');
+    Util.selectStylesheet(DC.getSetting('stylesheet'));
+
     /* Populate the controls if defaults are provided in the URL */
-    url = document.location.href;
-    $('VNC_host').value = (url.match(/host=([A-Za-z0-9.\-]*)/) ||
-            ['',''])[1];
-    $('VNC_port').value = (url.match(/port=([0-9]*)/) ||
-            ['',''])[1];
-    $('VNC_password').value = (url.match(/password=([^&#]*)/) ||
-            ['',''])[1];
-    $('VNC_encrypt').checked = (url.match(/encrypt=([A-Za-z0-9]*)/) ||
-            ['',''])[1];
+    DC.initSetting('host', '');
+    DC.initSetting('port', '');
+    DC.initSetting('password', '');
+    DC.initSetting('encrypt', true);
+    DC.initSetting('base64', true);
+    DC.initSetting('true_color', true);
+    DC.initSetting('cursor', true);
 
     $('VNC_screen').onmousemove = function () {
             // Unfocus clipboard when over the VNC area
@@ -82,6 +127,154 @@ load: function(target) {
         };
 },
 
+// Read a query string variable
+getQueryVar: function(name) {
+    var re = new RegExp('[\?].*' + name + '=([^\&\#]*)');
+    return (document.location.href.match(re) || ['',null])[1];
+},
+
+// Read form control compatible setting from cookie
+getSetting: function(name) {
+    var val, ctrl = $('VNC_' + name);
+    val = Util.readCookie(name);
+    if (ctrl.type === 'checkbox') {
+        if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
+            val = false;
+        } else {
+            val = true;
+        }
+    }
+    return val;
+},
+
+// Update cookie and form control setting. If value is not set, then
+// updates from control to current cookie setting.
+updateSetting: function(name, value) {
+    var i, ctrl = $('VNC_' + name);
+    // Save the cookie for this session
+    if (typeof value !== 'undefined') {
+        Util.createCookie(name, value);
+    }
+
+    // Update the settings control
+    value = DefaultControls.getSetting(name);
+    if (ctrl.type === 'checkbox') {
+        ctrl.checked = value;
+    } else if (typeof ctrl.options !== 'undefined') {
+        for (i = 0; i < ctrl.options.length; i++) {
+            if (ctrl.options[i].value === value) {
+                ctrl.selectedIndex = i;
+                break;
+            }
+        }
+    } else {
+        ctrl.value = value;
+    }
+},
+
+// Save control setting to cookie
+saveSetting: function(name) {
+    var val, ctrl = $('VNC_' + name);
+    if (ctrl.type === 'checkbox') {
+        val = ctrl.checked;
+    } else if (typeof ctrl.options !== 'undefined') {
+        val = ctrl.options[ctrl.selectedIndex].value;
+    } else {
+        val = ctrl.value;
+    }
+    Util.createCookie(name, val);
+    Util.Debug("Setting saved '" + name + "=" + val + "'");
+    return val;
+},
+
+// Initial page load read/initialization of settings
+initSetting: function(name, defVal) {
+    var val, ctrl = $('VNC_' + name), DC = DefaultControls;
+
+    // Check Query string followed by cookie
+    val = DC.getQueryVar(name);
+    if (val === null) {
+        val = Util.readCookie(name, defVal);
+    }
+    DC.updateSetting(name, val);
+    Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
+    return val;
+},
+
+
+// Toggle the settings menu:
+//   On open, settings are refreshed from saved cookies.
+//   On close, settings are applied
+clickSettingsMenu: function() {
+    var DC = DefaultControls;
+    if (DefaultControls.settingsOpen) {
+        DC.settingsApply();
+
+        DC.closeSettingsMenu();
+    } else {
+        DC.updateSetting('encrypt');
+        DC.updateSetting('base64');
+        DC.updateSetting('true_color');
+        if (Canvas.isCursor()) {
+            DC.updateSetting('cursor');
+        } else {
+            DC.updateSettings('cursor', false);
+            $('VNC_cursor').disabled = true;
+        }
+        DC.updateSetting('stylesheet');
+        DC.updateSetting('logging');
+
+        DC.openSettingsMenu();
+    }
+},
+
+// Open menu
+openSettingsMenu: function() {
+    $('VNC_settings_menu').style.display = "block";
+    DefaultControls.settingsOpen = true;
+},
+
+// Close menu (without applying settings)
+closeSettingsMenu: function() {
+    $('VNC_settings_menu').style.display = "none";
+    DefaultControls.settingsOpen = false;
+},
+
+// Disable/enable controls depending on connection state
+settingsDisabled: function(disabled) {
+    $('VNC_encrypt').disabled = disabled;
+    $('VNC_base64').disabled = disabled;
+    $('VNC_true_color').disabled = disabled;
+    if (Canvas.isCursor()) {
+        $('VNC_cursor').disabled = disabled;
+    } else {
+        DefaultControls.updateSettings('cursor', false);
+        $('VNC_cursor').disabled = true;
+    }
+},
+
+// Save/apply settings when 'Apply' button is pressed
+settingsApply: function() {
+    Util.Debug(">> settingsApply");
+    var curSS, newSS, DC = DefaultControls;
+    DC.saveSetting('encrypt');
+    DC.saveSetting('base64');
+    DC.saveSetting('true_color');
+    if (Canvas.isCursor()) {
+        DC.saveSetting('cursor');
+    }
+    DC.saveSetting('stylesheet');
+    DC.saveSetting('logging');
+
+    // Settings with immediate (non-connected related) effect
+    Util.selectStylesheet(DC.getSetting('stylesheet'));
+    Util.init_logging(DC.getSetting('logging'));
+
+    Util.Debug("<< settingsApply");
+},
+
+
+
 setPassword: function() {
     console.log("setPassword");
     RFB.sendPassword($('VNC_password').value);
@@ -103,6 +296,7 @@ updateState: function(state, msg) {
         case 'fatal':
             c.disabled = true;
             cad.disabled = true;
+            DefaultControls.settingsDisabled(true);
             klass = "VNC_status_error";
             break;
         case 'normal':
@@ -110,6 +304,7 @@ updateState: function(state, msg) {
             c.onclick = DefaultControls.disconnect;
             c.disabled = false;
             cad.disabled = false;
+            DefaultControls.settingsDisabled(true);
             klass = "VNC_status_normal";
             break;
         case 'disconnected':
@@ -119,6 +314,7 @@ updateState: function(state, msg) {
 
             c.disabled = false;
             cad.disabled = true;
+            DefaultControls.settingsDisabled(false);
             klass = "VNC_status_normal";
             break;
         case 'password':
@@ -127,11 +323,13 @@ updateState: function(state, msg) {
 
             c.disabled = false;
             cad.disabled = true;
+            DefaultControls.settingsDisabled(true);
             klass = "VNC_status_warn";
             break;
         default:
             c.disabled = true;
             cad.disabled = true;
+            DefaultControls.settingsDisabled(true);
             klass = "VNC_status_warn";
             break;
     }
@@ -145,28 +343,36 @@ updateState: function(state, msg) {
 },
 
 connect: function() {
-    var host, port, password, encrypt, true_color;
+    var host, port, password, DC = DefaultControls;
+
+    DC.closeSettingsMenu();
+
     host = $('VNC_host').value;
     port = $('VNC_port').value;
     password = $('VNC_password').value;
-    encrypt = $('VNC_encrypt').checked;
-    true_color = $('VNC_true_color').checked;
     if ((!host) || (!port)) {
         throw("Must set host and port");
     }
 
-    RFB.connect(host, port, password, encrypt, true_color);
+    RFB.setEncrypt(DC.getSetting('encrypt'));
+    RFB.setBase64(DC.getSetting('base64'));
+    RFB.setTrueColor(DC.getSetting('true_color'));
+    RFB.setCursor(DC.getSetting('cursor'));
+
+    RFB.connect(host, port, password);
 },
 
 disconnect: function() {
+    DefaultControls.closeSettingsMenu();
+
     RFB.disconnect();
 },
 
-clipFocus: function() {
+canvasBlur: function() {
     Canvas.focused = false;
 },
 
-clipBlur: function() {
+canvasFocus: function() {
     Canvas.focused = true;
 },
 

+ 21 - 3
include/plain.css

@@ -36,7 +36,7 @@
     margin: 0px;
     padding: 0px;
 }
-#VNC_status_bar input {
+.VNC_status_button {
     font-size: 10px;
     margin: 0px;
     padding: 0px;
@@ -44,10 +44,28 @@
 #VNC_status {
     text-align: center;
 }
-#VNC_buttons {
-    text-align: right;
+#VNC_settings_menu {
+    display: none;
+    position: absolute;
+    width: 12em;
+    border: 1px solid #888;
+    background-color: #f0f2f6;
+    padding: 5px; margin: 3px;
+    z-index: 100; opacity: 1;
+    text-align: left; white-space: normal;
+}
+#VNC_settings_menu ul {
+    list-style: none;
+    margin: 0;
+    padding: 0;
 }
 
+.VNC_buttons_right {
+    text-align: right;
+}
+.VNC_buttons_left {
+    text-align: left;
+}
 .VNC_status_normal {
     background: #eee;
 }

+ 100 - 27
include/util.js

@@ -14,37 +14,44 @@
 var Util = {}, $;
 
 
-// Logging/debug routines
-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) {}};
+/*
+ * 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) {};
-
-Util.logging = (document.location.href.match(
-        /logging=([A-Za-z0-9\._\-]*)/) || ['', 'warn'])[1];
-switch (Util.logging) {
-    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 '" + Util.logging + "'");
+    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
+ */
 if (!window.$) {
     $ = function (id) {
         if (document.getElementById) {
@@ -254,3 +261,69 @@ Util.Flash = (function(){
     return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
 }()); 
 
+/*
+ * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
+ */
+// No days means only for this browser session
+Util.createCookie = function(name,value,days) {
+    if (days) {
+        var date = new Date();
+        date.setTime(date.getTime()+(days*24*60*60*1000));
+        var expires = "; expires="+date.toGMTString();
+    }
+    else var expires = "";
+    document.cookie = name+"="+value+expires+"; path=/";
+};
+
+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];
+        while (c.charAt(0)==' ') c = c.substring(1,c.length);
+        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+    }
+    return (typeof defaultValue !== 'undefined') ? defaultValue : null;
+};
+
+Util.eraseCookie = function(name) {
+    createCookie(name,"",-1);
+};
+
+/*
+ * Alternate stylesheet selection
+ */
+Util.getStylesheets = function() { var i, links, sheets = [];
+    links = document.getElementsByTagName("link")
+    for (i = 0; i < links.length; i++) {
+        if (links[i].title &&
+            links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
+            sheets.push(links[i]);
+        }
+    }
+    return sheets;
+};
+
+// No sheet means try and use value from cookie, null sheet used to
+// clear all alternates.
+Util.selectStylesheet = function(sheet) {
+    var i, link, sheets = Util.getStylesheets();
+    if (typeof sheet === 'undefined') {
+        sheet = 'default';
+    }
+    for (i=0; i < sheets.length; i++) {
+        link = sheets[i];
+        if (link.title === sheet) {    
+            Util.Debug("Using stylesheet " + sheet);
+            link.disabled = false;
+        } else {
+            Util.Debug("Skipping stylesheet " + link.title);
+            link.disabled = true;
+        }
+    }
+    return sheet;
+};
+
+// call once to disable alternates and get around webkit bug
+Util.selectStylesheet(null);
+

+ 64 - 35
include/vnc.js

@@ -61,11 +61,11 @@ RFB = {
 host           : '',
 port           : 5900,
 password       : '',
+
 encrypt        : true,
 true_color     : false,
-
 b64encode      : true,  // false means UTF-8 on the wire
-//b64encode      : false,  // false means UTF-8 on the wire
+local_cursor   : true,
 connectTimeout : 2000,  // time to wait for connection
 
 
@@ -77,6 +77,7 @@ encodings      : [
     ['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'],
@@ -85,9 +86,6 @@ encodings      : [
 //    ['compress_hi',      -247, 'set_compress_level']
     ],
 
-encodingCursor :
-    ['Cursor',           -239, 'set_cursor'],
-
 
 setUpdateState: function(externalUpdateState) {
     RFB.externalUpdateState = externalUpdateState;
@@ -101,6 +99,43 @@ 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) {
+    if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
+        RFB.local_cursor = false;
+    } else {
+        if (Canvas.isCursor()) {
+            RFB.local_cursor = true;
+        } else {
+            Util.Warn("Browser does not support local cursor");
+        }
+    }
+},
+
 sendPassword: function(passwd) {
     RFB.password = passwd;
     RFB.state = "Authentication";
@@ -149,14 +184,6 @@ load: function () {
         RFB.updateState('fatal', "No working Canvas");
     }
 
-    // Add Cursor pseudo-encoding if supported
-/*
-    if (Canvas.isCursor()) {
-        Util.Debug("Adding Cursor pseudo-encoding to encoding list");
-        RFB.encodings.push(RFB.encodingCursor);
-    }
-*/
-
     // Populate encoding lookup tables
     RFB.encHandlers = {};
     RFB.encNames = {};
@@ -167,24 +194,12 @@ load: function () {
     //Util.Debug("<< load");
 },
 
-connect: function (host, port, password, encrypt, true_color) {
+connect: function (host, port, password) {
     //Util.Debug(">> connect");
 
     RFB.host       = host;
     RFB.port       = port;
     RFB.password   = (password !== undefined)   ? password : "";
-    RFB.encrypt    = (encrypt !== undefined)    ? encrypt : true;
-    if ((RFB.encrypt === "0") || 
-        (RFB.encrypt === "no") || 
-        (RFB.encrypt === "false")) { 
-        RFB.encrypt = false; 
-    }
-    RFB.true_color = (true_color !== undefined) ? true_color: true;
-    if ((RFB.true_color === "0") || 
-        (RFB.true_color === "no") || 
-        (RFB.true_color === "false")) { 
-        RFB.true_color = false; 
-    }
 
     if ((!RFB.host) || (!RFB.port)) {
         RFB.updateState('failed', "Must set host and port");
@@ -501,7 +516,11 @@ init_msg: function () {
         RFB.timing.history_start = (new Date()).getTime();
         setTimeout(RFB.update_timings, 1000);
 
-        RFB.updateState('normal', "Connected to: " + RFB.fb_name);
+        if (RFB.encrypt) {
+            RFB.updateState('normal', "Connected (encrypted) to: " + RFB.fb_name);
+        } else {
+            RFB.updateState('normal', "Connected (unencrypted) to: " + RFB.fb_name);
+        }
         break;
     }
     //Util.Debug("<< init_msg");
@@ -1051,9 +1070,9 @@ set_cursor: function () {
 
     //Util.Debug("   set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
 
-    Canvas.setCursor(RFB.RQ.shiftBytes(pixelslength),
-                     RFB.RQ.shiftBytes(masklength),
-                     x, y, w, h);
+    Canvas.changeCursor(RFB.RQ.shiftBytes(pixelslength),
+                        RFB.RQ.shiftBytes(masklength),
+                        x, y, w, h);
 
     RFB.FBU.bytes = 0;
     RFB.FBU.rects -= 1;
@@ -1104,14 +1123,24 @@ fixColourMapEntries: function () {
 
 clientEncodings: function () {
     //Util.Debug(">> clientEncodings");
-    var arr, i;
+    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(RFB.encodings.length); // encoding count
-
-    for (i=0; i<RFB.encodings.length; i += 1) {
-        arr.push32(RFB.encodings[i][1]);
+    arr.push16(encList.length); // encoding count
+    for (i=0; i < encList.length; i += 1) {
+        arr.push32(encList[i]);
     }
     //Util.Debug("<< clientEncodings: " + arr);
     return arr;

+ 14 - 10
tests/canvas.html

@@ -1,5 +1,14 @@
 <html>
-    <head><title>Canvas Performance Test</title></head>
+    <head>
+        <title>Canvas Performance Test</title>
+        <meta http-equiv="X-UA-Compatible" content="IE=8,chrome=1">
+        <script type='text/javascript' 
+            src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+        <script src="include/util.js"></script>
+        <script src="include/base64.js"></script>
+        <script src="include/canvas.js"></script>
+        <script src="face.png.js"></script>
+    </head>
     <body>
         Iterations: <input id='iterations' style='width:50' value="100">&nbsp;
 
@@ -22,13 +31,6 @@
         <textarea id="messages" style="font-size: 9;" cols=80 rows=25></textarea>
     </body>
 
-    <!--
-    <script type='text/javascript' 
-        src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-    -->
-    <script src="include/util.js"></script>
-    <script src="include/canvas.js"></script>
-    <script src="face.png.js"></script>
     <script>
         var msg_cnt = 0;
         var start_width = 300, start_height = 100;
@@ -39,17 +41,18 @@
             cell = $('messages');
             cell.innerHTML += msg_cnt + ": " + str + "\n";
             cell.scrollTop = cell.scrollHeight;
+            msg_cnt += 1;
         }
 
         function test_functions () {
             var img, x, y;
             Canvas.fillRect(0, 0, Canvas.c_wx, Canvas.c_wy, [240,240,240]);
 
-            Canvas.blitStringImage("data:image/png;base64," + face64, 150, 40);
+            Canvas.blitStringImage("data:image/png;base64," + face64, 150, 10);
 
             var himg = new Image();
             himg.onload = function () {
-                Canvas.ctx.drawImage(himg, 200, 10); };
+                Canvas.ctx.drawImage(himg, 200, 40); };
             himg.src = "face.png";
 
             /* Test array image data */
@@ -123,6 +126,7 @@
         }
 
         window.onload = function() {
+            message("in onload");
             $('iterations').value = 10;
             Canvas.init('canvas');
             Canvas.resize(start_width, start_height, true);

+ 123 - 0
tests/cursor.html

@@ -0,0 +1,123 @@
+<!DOCTYPE html> 
+<html xmlns="http://www.w3.org/1999/xhtml"> 
+  <head> 
+    <title>Cursor Change test</title> 
+    <meta charset="UTF-8"> 
+    <!--
+    <script type='text/javascript' 
+                    src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+    -->
+    <script src="include/util.js"></script> 
+    <script src="include/base64.js"></script> 
+    <script src="include/canvas.js"></script> 
+  </head> 
+  <body> 
+        <h1>Roll over the buttons to test cursors</h1>
+        <br>
+        <input id=button1 type="button" value="Cursor from file (smiley face)">
+        <input id=button2 type="button" value="Data URI cursor (crosshair)">
+    
+        <br> 
+        <br> 
+        <br> 
+        Debug:<br> 
+        <textarea id="debug" style="font-size: 9px;" cols=80 rows=25></textarea> 
+        <br>
+        <br>
+        <canvas id="testcanvas" width="100px" height="20px">
+            Canvas not supported.
+        </canvas>
+    
+  </body> 
+ 
+  <script> 
+    function debug(str) {
+        console.log(str);
+        cell = $('debug');
+        cell.innerHTML += str + "\n";
+        cell.scrollTop = cell.scrollHeight;
+    }
+
+    function makeCursor() {
+        var arr = [], x, y, w = 32, h = 32, hx = 16, hy = 16;
+
+        var IHDRsz = 40;
+        var ANDsz = w * h * 4;
+        var XORsz = Math.ceil( (w * h) / 8.0 );
+
+
+        // Main header
+        arr.push16le(0);      // Reserved
+        arr.push16le(2);      // .CUR type
+        arr.push16le(1);      // Number of images, 1 for non-animated arr
+
+        // Cursor #1
+        arr.push(w);          // width
+        arr.push(h);          // height
+        arr.push(0);          // colors, 0 -> true-color
+        arr.push(0);          // reserved
+        arr.push16le(hx);     // hotspot x coordinate
+        arr.push16le(hy);     // hotspot y coordinate
+        arr.push32le(IHDRsz + XORsz + ANDsz); // cursor data byte size
+        arr.push32le(22);     // offset of cursor data in the file
+
+        // Infoheader for Cursor #1
+        arr.push32le(IHDRsz); // Infoheader size
+        arr.push32le(w);      // Cursor width
+        arr.push32le(h*2);    // XOR+AND height
+        arr.push16le(1);      // number of planes
+        arr.push16le(32);     // bits per pixel
+        arr.push32le(0);      // type of compression
+        arr.push32le(XORsz + ANDsz); // Size of Image
+        arr.push32le(0);
+        arr.push32le(0);
+        arr.push32le(0);
+        arr.push32le(0);
+
+        // XOR/color data
+        for (y = h-1; y >= 0; y--) {
+            for (x = 0; x < w; x++) {
+                //if ((x === hx) || (y === (h-hy-1))) {
+                if ((x === hx) || (y === hy)) {
+                    arr.push(0xe0);  // blue
+                    arr.push(0x00);  // green
+                    arr.push(0x00);  // red
+                    arr.push(0xff);  // alpha
+                } else {
+                    arr.push(0x05);  // blue
+                    arr.push(0xe6);  // green
+                    arr.push(0x00);  // red
+                    arr.push(0x80);  // alpha
+                }
+            }
+        }
+
+        // AND/bitmask data (seems to be ignored)
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < Math.ceil(w / 8); x++) {
+                arr.push(0x00);
+            }
+        }
+
+        debug("cursor generated");
+        return arr;
+    }
+ 
+    window.onload = function() {
+        debug("onload");
+        var cross, cursor, cursor64;
+
+        Canvas.init("testcanvas");
+        debug("Canvas.init() indicates Data URI cursor support is: " + Canvas.isCursor());
+
+        $('button1').style.cursor="url(face.png), default";
+
+        cursor = makeCursor();
+        cursor64 = Base64.encode(cursor);
+        //debug("cursor: " + cursor.slice(0,100) + " (" + cursor.length + ")");
+        //debug("cursor64: " + cursor64.slice(0,100) + " (" + cursor64.length + ")");
+        $('button2').style.cursor="url(data:image/x-icon;base64," + cursor64 + "), default";
+
+        debug("onload complete");
+    }
+  </script> 

BIN
tests/face.png


+ 2 - 2
vnc.html

@@ -4,8 +4,8 @@ noVNC example: simple example using default controls
 <html>
     <head>
         <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">
+        <link rel="alternate stylesheet" href="include/black.css" TITLE="Black">
         <!--
         <script type='text/javascript' 
             src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>

+ 8 - 5
vnc_auto.html

@@ -21,7 +21,7 @@ Connect parameters are provided in query string:
             <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;">
                 <table border=0 width=100%><tr>
                     <td><div id="VNC_status">Loading</div></td>
-                    <td width=10%><div id="VNC_buttons">
+                    <td width=1%><div id="VNC_buttons">
                         <input type=button value="Send CtrlAltDel"
                             id="sendCtrlAltDelButton"
                             onclick="sendCtrlAltDel();"></div></td>
@@ -79,23 +79,26 @@ Connect parameters are provided in query string:
         }
 
         window.onload = function () {
-            var host, port, password, encrypt;
+            var host, port, password;
 
             url = document.location.href;
             host = (url.match(/host=([A-Za-z0-9.\-]*)/) || ['',''])[1];
             port = (url.match(/port=([0-9]*)/) || ['',''])[1];
             password = (url.match(/password=([^&#]*)/) || ['',''])[1];
-            encrypt = (url.match(/encrypt=([A-Za-z0-9]*)/) || ['',1])[1];
-            true_color = (url.match(/true_color=([A-Za-z0-9]*)/) || ['',1])[1];
             if ((!host) || (!port)) {
                 updateState('failed',
                     "Must specify host and port in URL");
                 return;
             }
 
+            RFB.setEncrypt((url.match(/encrypt=([A-Za-z0-9]*)/) || ['',1])[1]);
+            RFB.setBase64((url.match(/base64=([A-Za-z0-9]*)/) || ['',1])[1]);
+            RFB.setTrueColor((url.match(/true_color=([A-Za-z0-9]*)/) || ['',1])[1]);
+            RFB.setCursor((url.match(/cursor=([A-Za-z0-9]*)/) || ['',true])[1]);
             RFB.setUpdateState(updateState);
+
             RFB.load();
-            RFB.connect(host, port, password, encrypt, true_color);
+            RFB.connect(host, port, password);
         }
     </script>
 </html>