]> git.proxmox.com Git - mirror_novnc.git/commitdiff
API changes. Client cursor and settings menu.
authorJoel Martin <github@martintribe.org>
Thu, 22 Jul 2010 01:34:23 +0000 (20:34 -0500)
committerJoel Martin <github@martintribe.org>
Thu, 22 Jul 2010 01:34:23 +0000 (20:34 -0500)
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.

12 files changed:
docs/TODO
include/black.css
include/canvas.js
include/default_controls.js
include/plain.css
include/util.js
include/vnc.js
tests/canvas.html
tests/cursor.html [new file with mode: 0644]
tests/face.png [new file with mode: 0644]
vnc.html
vnc_auto.html

index 95b60cc4b713235df010a322967469cfdceaa12e..ec76ad69478680a1ecc93a92967b6e76a34f5de2 100644 (file)
--- a/docs/TODO
+++ b/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:
 
index 2d9d0add7c1551d6dcd860b9a6cddca2aa3fb8a2..4cc195efb4182720251eb9b7b169e940e4c6d2bc 100644 (file)
@@ -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 {
index 8e822c48981a670caf924c46967ec07d493c7759..be9e7fe5a6fe96f9ed95a7a7969f1c007284bfde 100644 (file)
@@ -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);
 }
 
 };
index a6c7ab0bf496bec643ed9d7717bfdaaac2a7d310..aa42203d6a06b208f288844206392a454a1d3865 100644 (file)
 
 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;
 },
 
index dff781c17af506240ca7165342716bde276b638a..c8d853ba0e63861d1b96deef990e5fe026be3eef 100644 (file)
@@ -36,7 +36,7 @@
     margin: 0px;
     padding: 0px;
 }
-#VNC_status_bar input {
+.VNC_status_button {
     font-size: 10px;
     margin: 0px;
     padding: 0px;
 #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;
 }
index 64e3f93d9b7eec286ea0206457d58748aa5e4a3d..f7a83b285c473ae9649470c6fa7888e611f4545c 100644 (file)
 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);
+
index 4b77bc6e3ad60a385966cfd3cdb96094789a80b6..93413952016dee0473b807803e1eb77015d38521 100644 (file)
@@ -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;
index 3bc77d20eddf2de6dfc78c9eff6c77e5cd3ce393..bd1a3637272a2871cda489392aff4f1c7a824149 100644 (file)
@@ -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;
 
         <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;
             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 */
         }
 
         window.onload = function() {
+            message("in onload");
             $('iterations').value = 10;
             Canvas.init('canvas');
             Canvas.resize(start_width, start_height, true);
diff --git a/tests/cursor.html b/tests/cursor.html
new file mode 100644 (file)
index 0000000..d244db1
--- /dev/null
@@ -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> 
diff --git a/tests/face.png b/tests/face.png
new file mode 100644 (file)
index 0000000..74c30d8
Binary files /dev/null and b/tests/face.png differ
index 480264a746f8fa98b3b60adadcfa583c35cb6d03..9a4596a5484dcfe83634cc02259aeea12176761e 100644 (file)
--- a/vnc.html
+++ b/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>
index 22ca13538fcdad9fa632e8d54a203bc6398270f0..e5a4c98908b317a8d705e292c489f287ff67dbfa 100644 (file)
@@ -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>