]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Add Cursor pseudo-encoding support (disabled for now).
authorJoel Martin <github@martintribe.org>
Tue, 20 Jul 2010 19:34:44 +0000 (14:34 -0500)
committerJoel Martin <github@martintribe.org>
Tue, 20 Jul 2010 19:34:44 +0000 (14:34 -0500)
To change the appearance of the cursor, we use the CSS cursor style
and set the url to a data URI scheme. The image data sent via the
cursor pseudo-encoding has to be encoded to a CUR format file before
being used in the data URI.

During Canvas initialization we try and set a simple cursor to see if
the browser has support. Opera is missing support for data URI scheme
in cursor URLs.

Disabled for now until we have a better way of specifying settings
overall (too many settings for control bar now).

README.md
docs/TODO
docs/links
include/canvas.js
include/util.js
include/vnc.js

index 02d7c6798c0fb0a31346d8a385cd7a2bc18e9173..5a26a7dee99f99e5a441fb11e309de7457a91f50 100644 (file)
--- a/README.md
+++ b/README.md
@@ -178,7 +178,9 @@ In the following table Jaunty is Ubuntu 9.04 and WinXP is Windows XP.
   is faster than Firefox 3.5, the high variability of web-socket-js
   performance results in overall performance being lower. Middle mouse
   clicks and keyboard events need some work to work properly under
-  Opera.
+  Opera. Also, Opera does not have support for setting the cursor
+  style url to a data URI scheme, so cursor pseudo-encoding is
+  disabled.
 
 
 ### Integration
index c2bda7ffcc63526c1a3c11286844f32bcac8f943..95b60cc4b713235df010a322967469cfdceaa12e 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
@@ -1,5 +1,8 @@
 Short Term:
 
+- Proper Javascript namespacing for Canvas and RFB (using function for
+    true local variables and functions).
+
 - Timing delta between frames in proxy record log, for playback
   support (for demo and test).
 
@@ -14,10 +17,6 @@ Short Term:
 
 Medium Term:
 
-- Implement Cursor pseudo-encoding (CSS cursor)
-    http://en.wikipedia.org/wiki/ICO_(file_format)
-    https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
-
 - Viewport and/or scaling support.
 
 - Status bar buttons:
index 6e180151244114db869dbe2c1cf72f1a8bf56a9b..64ea7546c009a7f31e79747c507edd0cf9c56554 100644 (file)
@@ -29,6 +29,11 @@ TLS Protocol:
 Generate self-signed certificate:
     http://docs.python.org/dev/library/ssl.html#certificates
 
+Cursor appearance/style (for Cursor pseudo-encoding):
+    http://en.wikipedia.org/wiki/ICO_(file_format)
+    http://www.daubnet.com/en/file-format-cur
+    https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
+
 
 Related projects:
     
index af52227a089501d5d7cc0350b69d1b3be37765ca..8e822c48981a670caf924c46967ec07d493c7759 100644 (file)
@@ -27,8 +27,9 @@ var Canvas, Canvas_native;
 // Everything namespaced inside Canvas
 Canvas = {
 
-prefer_js  : false,
-force_canvas : false,
+prefer_js    : false, // make private
+force_canvas : false, // make private
+cursor_uri   : true,  // make private, create getter
 
 true_color : false,
 colourMap  : [],
@@ -138,7 +139,7 @@ onMouseDisable: function (e) {
 
 
 init: function (id) {
-    var c, imgTest, arora;
+    var c, imgTest, tval, i, curTest, curSave;
     Util.Debug(">> Canvas.init");
 
     Canvas.id = id;
@@ -198,6 +199,25 @@ init: function (id) {
         Canvas._cmapImage = Canvas._cmapImageFill;
     }
 
+    /*
+     * Determine browser support for setting the cursor via data URI
+     * scheme
+     */
+    curDat = [];
+    for (i=0; i < 8 * 8 * 4; i++) {
+        curDat.push(255);
+    }
+    curSave = c.style.cursor;
+    Canvas.setCursor(curDat, curDat, 2, 2, 8, 8);
+    if (c.style.cursor) {
+        Util.Info("Data URI scheme cursor supported");
+    } else {
+        Canvas.cursor_uri = false;
+        Util.Warn("Data URI scheme cursor not supported");
+    }
+    c.style.cursor = curSave;
+
+
     Canvas.colourMap = [];
     Canvas.prevStyle = "";
     Canvas.focused = true;
@@ -263,6 +283,11 @@ stop: function () {
     /* Work around right and middle click browser behaviors */
     Util.removeEvent(document, 'click', Canvas.onMouseDisable);
     Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
+
+    // Turn off cursor rendering
+    if (Canvas.cursor_uri) {
+        c.style.cursor = "default";
+    }
 },
 
 /*
@@ -530,8 +555,89 @@ getKeysym: function(e) {
     } 
 
     return keysym;
-}
+},
+
 
+isCursor: function() {
+    return Canvas.cursor_uri;
+},
+
+setCursor: 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);
+    
+    if (!Canvas.cursor_uri) {
+        Util.Warn("setCursor called but no cursor data URI support");
+        return;
+    }
+
+    cmap = Canvas.colourMap;
+    IHDRsz = 40;
+    ANDsz = w * h * 4;
+    XORsz = Math.ceil( (w * h) / 8.0 );
+
+    // Main header
+    cur.push16le(0);      // Reserved
+    cur.push16le(2);      // .CUR type
+    cur.push16le(1);      // Number of images, 1 for non-animated ico
+
+    // Cursor #1 header
+    cur.push(w);          // width
+    cur.push(h);          // height
+    cur.push(0);          // colors, 0 -> true-color
+    cur.push(0);          // reserved
+    cur.push16le(hotx);   // hotspot x coordinate
+    cur.push16le(hoty);   // hotspot y coordinate
+    cur.push32le(IHDRsz + XORsz + ANDsz); // cursor data byte size
+    cur.push32le(22);     // offset of cursor data in the file
+
+    // Cursor #1 InfoHeader
+    cur.push32le(IHDRsz); // Infoheader size
+    cur.push32le(w);      // Cursor width
+    cur.push32le(h*2);    // XOR+AND height
+    cur.push16le(1);      // number of planes
+    cur.push16le(32);     // bits per pixel
+    cur.push32le(0);      // Type of compression
+    cur.push32le(XORsz + ANDsz); // Size of Image
+    cur.push32le(0);
+    cur.push32le(0);
+    cur.push32le(0);
+    cur.push32le(0);
+
+    // XOR/color data
+    for (y = h-1; y >= 0; y--) {
+        for (x = 0; x < w; x++) {
+            idx = y * Math.ceil(w / 8) + Math.floor(x/8);
+            alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
+
+            if (Canvas.true_color) {
+                idx = ((w * y) + x) * 4;
+                cur.push(pixels[idx + 2]); // blue
+                cur.push(pixels[idx + 1]); // green
+                cur.push(pixels[idx + 0]); // red
+                cur.push(alpha); // red
+            } else {
+                idx = (w * y) + x;
+                rgb = cmap[pixels[idx]];
+                cur.push(rgb[2]);          // blue
+                cur.push(rgb[1]);          // green
+                cur.push(rgb[0]);          // red
+                cur.push(alpha);           // alpha
+            }
+        }
+    }
+
+    // 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++) {
+            cur.push(0x00);
+        }
+    }
+
+    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);
+}
 
 };
 
index 98cfeef1bd2046f900c73fe5bd442e811f3c94ea..64e3f93d9b7eec286ea0206457d58748aa5e4a3d 100644 (file)
@@ -77,6 +77,10 @@ Array.prototype.push16 = function (num) {
     this.push((num >> 8) & 0xFF,
               (num     ) & 0xFF  );
 };
+Array.prototype.push16le = function (num) {
+    this.push((num     ) & 0xFF,
+              (num >> 8) & 0xFF  );
+};
 
 
 Array.prototype.shift32 = function () {
@@ -97,6 +101,13 @@ Array.prototype.push32 = function (num) {
               (num >>  8) & 0xFF,
               (num      ) & 0xFF  );
 };
+Array.prototype.push32le = function (num) {
+    this.push((num      ) & 0xFF,
+              (num >>  8) & 0xFF,
+              (num >> 16) & 0xFF,
+              (num >> 24) & 0xFF  );
+};
+
 
 Array.prototype.shiftStr = function (len) {
     var arr = this.splice(0, len);
index 9f6468f1479541482cc4d8b42cc1cf76a98f2bff..030e2b09a8641cb1b33b92357258646a90cb3c93 100644 (file)
@@ -85,6 +85,10 @@ encodings      : [
 //    ['compress_hi',      -247, 'set_compress_level']
     ],
 
+encodingCursor :
+    ['Cursor',           -239, 'set_cursor'],
+
+
 setUpdateState: function(externalUpdateState) {
     RFB.externalUpdateState = externalUpdateState;
 },
@@ -145,6 +149,14 @@ 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 = {};
@@ -1013,10 +1025,40 @@ set_desktopsize : function () {
     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");
+},
+
+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.setCursor(RFB.RQ.shiftBytes(pixelslength),
+                     RFB.RQ.shiftBytes(masklength),
+                     x, y, w, h);
 
     RFB.FBU.bytes = 0;
     RFB.FBU.rects -= 1;
+
+    //Util.Debug("<< set_cursor");
 },
 
 set_jpeg_quality : function () {